資料庫效能優化詳解
一、問題的提出
在應用系統開發初期,由於開發資料庫資料比較少,對於查詢SQL語句,複雜檢視的的編寫等體會不出SQL語句各種寫法的效能優劣,但是如果將應用系統提交實際應用後,隨著資料庫中資料的增加,系統的響應速度就成為目前系統需要解決的最主要的問題之一。系統優化中一個很重要的方面就是SQL語句的優化。對於海量資料,劣質SQL語句和優質SQL語句之間的速度差別可以達到上百倍,可見對於一個系統不是簡單地能實現其功能就可,而是要寫出高質量的SQL語句,提高系統的可用性。
在多數情況下,Oracle使用索引來更快地遍歷表,優化器主要根據定義的索引來提高效能。但是,如果在SQL語句的where子句中寫的SQL程式碼不合理,就會造成優化器刪去索引而使用全表掃描
二、SQL語句編寫注意問題
下面就某些SQL語句的where子句編寫中需要注意的問題作詳細介紹。在這些where子句中,即使某些列存在索引,但是由於編寫了劣質的SQL,系統在執行該SQL語句時也不能使用該索引,而同樣使用全表掃描,這就造成了響應速度的極大降低。
1. 操作符優化
(a) IN 操作符
用IN寫出來的SQL的優點是比較容易寫及清晰易懂,這比較適合現代軟體開發的風格。但是用IN的SQL效能總是比較低的,從Oracle執行的步驟來分析用IN的SQL與不用IN的SQL有以下區別:
ORACLE試圖將其轉換成多個表的連線,如果轉換不成功則先執行IN裡面的子查詢,再查詢外層的表記錄,如果轉換成功則直接採用多個表的連線方式查詢。由此可見用IN的SQL至少多了一個轉換的過程。一般的SQL都可以轉換成功,但對於含有分組統計等方面的SQL就不能轉換了。
推薦方案:在業務密集的SQL當中儘量不採用IN操作符,用EXISTS 方案代替。
(b) NOT IN操作符
此操作是強列不推薦使用的,因為它不能應用表的索引。
推薦方案:用NOT EXISTS 方案代替
(c) IS NULL 或IS NOT NULL操作(判斷欄位是否為空)
判斷欄位是否為空一般是不會應用索引的,因為索引是不索引空值的。不能用null作索引,任何包含null值的列都將不會被包含在索引中。即使索引有多列這樣的情況下,只要這些列中有一列含有null,該列就會從索引中排除。也就是說如果某列存在空值,即使對該列建索引也不會提高效能。任何在where子句中使用is null或is not null的語句優化器是不允許使用索引的。
推薦方案:用其它相同功能的操作運算代替,如:a is not null 改為 a>0 或a>’’等。不允許欄位為空,而用一個預設值代替空值,如申請中狀態欄位不允許為空,預設為申請。
(d) > 及 < 操作符(大於或小於操作符)
大於或小於操作符一般情況下是不用調整的,因為它有索引就會採用索引查詢,但有的情況下可以對它進行優化,如一個表有100萬記錄,一個數值型欄位A,30萬記錄的A=0,30萬記錄的A=1,39萬記錄的A=2,1萬記錄的A=3。那麼執行A>2與A>=3的效果就有很大的區別了,因為A>2時ORACLE會先找出為2的記錄索引再進行比較,而A>=3時ORACLE則直接找到=3的記錄索引。
(e) LIKE操作符
LIKE操作符可以應用萬用字元查詢,裡面的萬用字元組合可能達到幾乎是任意的查詢,但是如果用得不好則會產生效能上的問題,如LIKE ‘%5400%’ 這種查詢不會引用索引,而LIKE ‘X5400%’則會引用範圍索引。
一個實際例子:用YW_YHJBQK表中營業編號後面的戶標識號可來查詢營業編號 YY_BH LIKE ‘%5400%’ 這個條件會產生全表掃描,如果改成YY_BH LIKE ’X5400%’ OR YY_BH LIKE ’B5400%’ 則會利用YY_BH的索引進行兩個範圍的查詢,效能肯定大大提高。
帶萬用字元(%)的like語句:
同樣以上面的例子來看這種情況。目前的需求是這樣的,要求在職工表中查詢名字中包含cliton的人。可以採用如下的查詢SQL語句:
select * from employee where last_name like '%cliton%';
這裡由於萬用字元(%)在搜尋詞首出現,所以Oracle系統不使用last_name的索引。在很多情況下可能無法避免這種情況,但是一定要心中有底,萬用字元如此使用會降低查詢速度。然而當萬用字元出現在字串其他位置時,優化器就能利用索引。在下面的查詢中索引得到了使用:
select * from employee where last_name like 'c%';
(f) UNION操作符
UNION在進行錶鏈接後會篩選掉重複的記錄,所以在錶鏈接後會對所產生的結果集進行排序運算,刪除重複的記錄再返回結果。實際大部分應用中是不會產生重複的記錄,最常見的是過程表與歷史表UNION。如: select * from gc_dfys union select * from ls_jg_dfys 這個SQL在執行時先取出兩個表的結果,再用排序空間進行排序刪除重複的記錄,最後返回結果集,如果表資料量大的話可能會導致用磁碟進行排序。
推薦方案:採用UNION ALL操作符替代UNION,因為UNION ALL操作只是簡單的將兩個結果合併後就返回。
select * from gc_dfys union all select * from ls_jg_dfys
(g) 聯接列
對於有聯接的列,即使最後的聯接值為一個靜態值,優化器是不會使用索引的。我們一起來看一個例子,假定有一個職工表(employee),對於一個職工的姓和名分成兩列存放(FIRST_NAME和LAST_NAME),現在要查詢一個叫比爾.克林頓(Bill Cliton)的職工。
下面是一個採用聯接查詢的SQL語句:
select * from employss where first_name||''||last_name ='Beill Cliton';
上面這條語句完全可以查詢出是否有Bill Cliton這個員工,但是這裡需要注意,系統優化器對基於last_name建立的索引沒有使用。當採用下面這種SQL語句的編寫,Oracle系統就可以採用基於last_name建立的索引。
*** where first_name ='Beill' and last_name ='Cliton';
(h) Order by語句
ORDER BY語句決定了Oracle如何將返回的查詢結果排序。Order by語句對要排序的列沒有什麼特別的限制,也可以將函式加入列中(象聯接或者附加等)。任何在Order by語句的非索引項或者有計算表示式都將降低查詢速度。
仔細檢查order by語句以找出非索引項或者表示式,它們會降低效能。解決這個問題的辦法就是重寫order by語句以使用索引,也可以為所使用的列建立另外一個索引,同時應絕對避免在order by子句中使用表示式。
(i) NOT
我們在查詢時經常在where子句使用一些邏輯表示式,如大於、小於、等於以及不等於等等,也可以使用and(與)、or(或)以及not(非)。NOT可用來對任何邏輯運算子號取反。下面是一個NOT子句的例子:
... where not (status ='VALID')
如果要使用NOT,則應在取反的短語前面加上括號,並在短語前面加上NOT運算子。NOT運算子包含在另外一個邏輯運算子中,這就是不等於(<>)運算子。換句話說,即使不在查詢where子句中顯式地加入NOT詞,NOT仍在運算子中,見下例:
... where status <>'INVALID';
對這個查詢,可以改寫為不使用NOT:
select * from employee where salary<3000 or salary>3000;
雖然這兩種查詢的結果一樣,但是第二種查詢方案會比第一種查詢方案更快些。第二種查詢允許Oracle對salary列使用索引,而第一種查詢則不能使用索引。
2. SQL書寫的影響
(a) 同一功能同一性能不同寫法SQL的影響。
如一個SQL在A程式設計師寫的為 Select * from zl_yhjbqk
B程式設計師寫的為 Select * from dlyx.zl_yhjbqk(帶表所有者的字首)
C程式設計師寫的為 Select * from DLYX.ZLYHJBQK(大寫表名)
D程式設計師寫的為 Select * from DLYX.ZLYHJBQK(中間多了空格)
以上四個SQL在ORACLE分析整理之後產生的結果及執行的時間是一樣的,但是從ORACLE共享記憶體SGA的原理,可以得出ORACLE對每個SQL 都會對其進行一次分析,並且佔用共享記憶體,如果將SQL的字串及格式寫得完全相同,則ORACLE只會分析一次,共享記憶體也只會留下一次的分析結果,這不僅可以減少分析SQL的時間,而且可以減少共享記憶體重複的資訊,ORACLE也可以準確統計SQL的執行頻率。
(b) WHERE後面的條件順序影響
WHERE子句後面的條件順序對大資料量表的查詢會產生直接的影響。如: Select * from zl_yhjbqk where dy_dj = '1KV以下' and xh_bz=1 Select * from zl_yhjbqk where xh_bz=1 and dy_dj = '1KV以下' 以上兩個SQL中dy_dj(電壓等級)及xh_bz(銷戶標誌)兩個欄位都沒進行索引,所以執行的時候都是全表掃描,第一條SQL的dy_dj = '1KV以下'條件在記錄集內比率為99%,而xh_bz=1的比率只為0.5%,在進行第一條SQL的時候99%條記錄都進行dy_dj及xh_bz的比較,而在進行第二條SQL的時候0.5%條記錄都進行dy_dj及xh_bz的比較,以此可以得出第二條SQL的CPU佔用率明顯比第一條低。
(c) 查詢表順序的影響
在FROM後面的表中的列表順序會對SQL執行效能影響,在沒有索引及ORACLE沒有對錶進行統計分析的情況下,ORACLE會按表出現的順序進行連結,由此可見表的順序不對時會產生十分耗服物器資源的資料交叉。(注:如果對錶進行了統計分析,ORACLE會自動先進小表的連結,再進行大表的連結)
3. SQL語句索引的利用
(a) 對條件欄位的一些優化
採用函式處理的欄位不能利用索引,如:
substr(hbs_bh,1,4)=’5400’,優化處理:hbs_bh like ‘5400%’
trunc(sk_rq)=trunc(sysdate), 優化處理:sk_rq>=trunc(sysdate) and sk_rq<trunc(sysdate+1)
進行了顯式或隱式的運算的欄位不能進行索引,如:ss_df+20>50,優化處理:ss_df>30
‘X’ || hbs_bh>’X5400021452’,優化處理:hbs_bh>’5400021542’
sk_rq+5=sysdate,優化處理:sk_rq=sysdate-5
hbs_bh=5401002554,優化處理:hbs_bh=’ 5401002554’,注:此條件對hbs_bh 進行隱式的to_number轉換,因為hbs_bh欄位是字元型。
條件內包括了多個本表的欄位運算時不能進行索引,如:
ys_df>cx_df,無法進行優化 qc_bh || kh_bh=’5400250000’,優化處理:qc_bh=’5400’ and kh_bh=’250000’
4. 更多方面SQL優化資料分享
(1) 選擇最有效率的表名順序(只在基於規則的優化器中有效):
ORACLE 的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表。如果有3個以上的表連線查詢, 那就需要選擇交叉表(intersection table)作為基礎表, 交叉表是指那個被其他表所引用的表.
(2) WHERE子句中的連線順序:
ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連線必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾.
(3) SELECT子句中避免使用 ‘ * ‘:
ORACLE在解析的過程中, 會將'*' 依次轉換成所有的列名, 這個工作是通過查詢資料字典完成的, 這意味著將耗費更多的時間。
(4) 減少訪問資料庫的次數:
ORACLE在內部執行了許多工作: 解析SQL語句, 估算索引的利用率, 繫結變數 , 讀資料塊等。
(5) 在SQL*Plus , SQL*Forms和Pro*C中重新設定ARRAYSIZE引數, 可以增加每次資料庫訪問的檢索資料量 ,建議值為200。
(6) 使用DECODE函式來減少處理時間:
使用DECODE函式可以避免重複掃描相同記錄或重複連線相同的表.
(7) 整合簡單,無關聯的資料庫訪問:
如果你有幾個簡單的資料庫查詢語句,你可以把它們整合到一個查詢中(即使它們之間沒有關係) 。
(8) 刪除重複記錄:
最高效的刪除重複記錄方法 ( 因為使用了ROWID)例子: DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO)。
(9) 用TRUNCATE替代DELETE:
當刪除表中的記錄時,在通常情況下, 回滾段(rollback segments ) 用來存放可以被恢復的資訊. 如果你沒有COMMIT事務,ORACLE會將資料恢復到刪除之前的狀態(準確地說是恢復到執行刪除命令之前的狀況) 而當運用TRUNCATE時, 回滾段不再存放任何可被恢復的資訊.當命令執行後,資料不能被恢復.因此很少的資源被呼叫,執行時間也會很短. (譯者按: TRUNCATE只在刪除全表適用,TRUNCATE是DDL不是DML) 。
(10) 儘量多使用COMMIT:
只要有可能,在程式中儘量多使用COMMIT, 這樣程式的效能得到提高,需求也會因為COMMIT所釋放的資源而減少,COMMIT所釋放的資源: a. 回滾段上用於恢復資料的資訊. b. 被程式語句獲得的鎖 c. redo log buffer 中的空間 d. ORACLE為管理上述3種資源中的內部花費
(11) 用Where子句替換HAVING子句:
避免使用HAVING子句, HAVING 只會在檢索出所有記錄之後才對結果集進行過濾. 這個處理需要排序,總計等操作. 如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷. (非oracle中)on、where、having這三個都可以加條件的子句中,on是最先執行,where次之,having最後,因為on是先把不符合條件的記錄過濾後才進行統計,它就可以減少中間運算要處理的資料,按理說應該速度是最快的,where也應該比having快點的,因為它過濾資料後才進行sum,在兩個表聯接時才用on的,所以在一個表的時候,就剩下where跟having比較了。在這單表查詢統計的情況下,如果要過濾的條件沒有涉及到要計算欄位,那它們的結果是一樣的,只是where可以使用rushmore技術,而having就不能,在速度上後者要慢如果要涉及到計算的字 段,就表示在沒計算之前,這個欄位的值是不確定的,根據上篇寫的工作流程,where的作用時間是在計算之前就完成的,而having就是在計算後才起作 用的,所以在這種情況下,兩者的結果會不同。在多表聯接查詢時,on比where更早起作用。系統首先根據各個表之間的聯接條件,把多個表合成一個臨時表 後,再由where進行過濾,然後再計算,計算完後再由having進行過濾。由此可見,要想過濾條件起到正確的作用,首先要明白這個條件應該在什麼時候起作用,然後再決定放在那裡。
(12) 減少對錶的查詢:
在含有子查詢的SQL語句中,要特別注意減少對錶的查詢.例子: SELECT TAB_NAME FROM TABLES WHERE (TAB_NAME,DB_VER) = ( SELECT TAB_NAME,DB_VER FROM TAB_COLUMNS WHERE VERSION = 604)
(13) 通過內部函式提高SQL效率:
複雜的SQL往往犧牲了執行效率. 能夠掌握上面的運用函式解決問題的方法在實際工作中是非常有意義的。
(14) 使用表的別名(Alias):
當在SQL語句中連線多個表時, 請使用表的別名並把別名字首於每個Column上.這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。
(15) 用EXISTS替代IN、用NOT EXISTS替代NOT IN:
在許多基於基礎表的查詢中,為了滿足一個條件,往往需要對另一個表進行聯接.在這種情況下, 使用EXISTS(或NOT EXISTS)通常將提高查詢的效率. 在子查詢中,NOT IN子句將執行一個內部的排序和合並. 無論在哪種情況下,NOT IN都是最低效的 (因為它對子查詢中的表執行了一個全表遍歷). 為了避免使用NOT IN ,我們可以把它改寫成外連線(Outer Joins)或NOT EXISTS。 例子: (高效)SELECT * FROM EMP (基礎表) WHERE EMPNO > 0 AND EXISTS (SELECT ‘X' FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB') (低效)SELECT * FROM EMP (基礎表) WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC = ‘MELB')
(16) 識別'低效執行'的SQL語句:
雖然目前各種關於SQL優化的圖形化工具層出不窮,但是寫出自己的SQL工具來解決問題始終是一個最好的方法: SELECT EXECUTIONS , DISK_READS, BUFFER_GETS, ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2) Hit_radio, ROUND(DISK_READS/EXECUTIONS,2) Reads_per_run, SQL_TEXT FROM V$SQLAREA WHERE EXECUTIONS>0 AND BUFFER_GETS > 0 AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8 ORDER BY 4 DESC;
(17) 用索引提高效率:
索引是表的一個概念部分,用來提高檢索資料的效率,ORACLE使用了一個複雜的自平衡B-tree結構. 通常,通過索引查詢資料比全表掃描要快. 當ORACLE找出執行查詢和Update語句的最佳路徑時, ORACLE優化器將使用索引. 同樣在聯結多個表時使用索引也可以提高效率. 另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證.。那些LONG或LONG RAW資料型別, 你可以索引幾乎所有的列. 通常, 在大型表中使用索引特別有效. 當然,你也會發現, 在掃描小表時,使用索引同樣能提高效率. 雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價. 索引需要空間來儲存,也需要定期維護, 每當有記錄在表中增減或索引列被修改時, 索引本身也會被修改. 這意味著每條記錄的INSERT , DELETE , UPDATE將為此多付出4 , 5 次的磁碟I/O . 因為索引需要額外的儲存空間和處理,那些不必要的索引反而會使查詢反應時間變慢.。定期的重構索引是有必要的: ALTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>
(18) 用EXISTS替換DISTINCT:
當提交一個包含一對多表資訊(比如部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT. 一般可以考慮用EXIST替換, EXISTS 使查詢更為迅速,因為RDBMS核心模組將在子查詢的條件一旦滿足後,立刻返回結果. 例子: (低效): SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D , EMP E WHERE D.DEPT_NO = E.DEPT_NO (高效): SELECT DEPT_NO,DEPT_NAME FROM DEPT D WHERE EXISTS ( SELECT ‘X' FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO);
(19) sql語句用大寫的;因為oracle總是先解析sql語句,把小寫的字母轉換成大寫的再執行。
(20) 在java程式碼中儘量少用連線符“+”連線字串!
(21) 避免在索引列上使用NOT,通常我們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函式相同的影響. 當ORACLE”遇到”NOT,他就會停止使用索引轉而執行全表掃描。
(22) 避免在索引列上使用計算 WHERE子句中,如果索引列是函式的一部分.優化器將不使用索引而使用全表掃描.舉例: 低效: SELECT … FROM DEPT WHERE SAL * 12 > 25000; 高效: SELECT … FROM DEPT WHERE SAL > 25000/12;
(23) 用>=替代> 高效: SELECT * FROM EMP WHERE DEPTNO >=4 低效: SELECT * FROM EMP WHERE DEPTNO >3 兩者的區別在於, 前者DBMS將直接跳到第一個DEPT等於4的記錄而後者將首先定位到DEPTNO=3的記錄並且向前掃描到第一個DEPT大於3的記錄。
(24) 用UNION替換OR (適用於索引列)
通常情況下, 用UNION替換WHERE子句中的OR將會起到較好的效果. 對索引列使用OR將造成全表掃描. 注意, 以上規則只針對多個索引列有效. 如果有column沒有被索引, 查詢效率可能會因為你沒有選擇OR而降低. 在下面的例子中, LOC_ID 和REGION上都建有索引. 高效: SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE LOC_ID = 10 UNION SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE REGION = “MELBOURNE” 低效: SELECT LOC_ID , LOC_DESC , REGION FROM LOCATION WHERE LOC_ID = 10 OR REGION = “MELBOURNE” 如果你堅持要用OR, 那就需要返回記錄最少的索引列寫在最前面.
(25) 用IN來替換OR
這是一條簡單易記的規則,但是實際的執行效果還須檢驗,在ORACLE8i下,兩者的執行路徑似乎是相同的. 低效: SELECT…. FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30 高效 SELECT… FROM LOCATION WHERE LOC_IN IN (10,20,30);
(26) 避免在索引列上使用IS NULL和IS NOT NULL
避免在索引中使用任何可以為空的列,ORACLE將無法使用該索引.對於單列索引,如果列包含空值,索引中將不存在此記錄. 對於複合索引,如果每個列都為空,索引中同樣不存在此記錄. 如果至少有一個列不為空,則記錄存在於索引中.舉例: 如果唯一性索引建立在表的A列和B列上, 並且表中存在一條記錄的A,B值為(123,null) , ORACLE將不接受下一條具有相同A,B值(123,null)的記錄(插入). 然而如果所有的索引列都為空,ORACLE將認為整個鍵值為空而空不等於空. 因此你可以插入1000 條具有相同鍵值的記錄,當然它們都是空! 因為空值不存在於索引列中,所以WHERE子句中對索引列進行空值比較將使ORACLE停用該索引. 低效: (索引失效) SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL; 高效: (索引有效) SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0;
(27) 總是使用索引的第一個列:
如果索引是建立在多個列上, 只有在它的第一個列(leading column)被where子句引用時,優化器才會選擇使用該索引. 這也是一條簡單而重要的規則,當僅引用索引的第二個列時,優化器使用了全表掃描而忽略了索引。
(28) 用UNION-ALL 替換UNION ( 如果有可能的話):
當SQL 語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合併, 然後在輸出最終結果前進行排序. 如果用UNION ALL替代UNION, 這樣排序就不是必要了. 效率就會因此得到提高. 需要注意的是,UNION ALL 將重複輸出兩個結果集合中相同記錄. 因此各位還是要從業務需求分析使用UNION ALL的可行性. UNION 將對結果集合排序,這個操作會使用到SORT_AREA_SIZE這塊記憶體. 對於這塊記憶體的優化也是相當重要的. 下面的SQL可以用來查詢排序的消耗量 低效: SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE = '31-DEC-95' UNION SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE = '31-DEC-95' 高效: SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE = '31-DEC-95' UNION ALL SELECT ACCT_NUM, BALANCE_AMT FROM DEBIT_TRANSACTIONS WHERE TRAN_DATE = '31-DEC-95'
(29) 用WHERE替代ORDER BY:
ORDER BY 子句只在兩種嚴格的條件下使用索引. ORDER BY中所有的列必須包含在相同的索引中並保持在索引中的排列順序. ORDER BY中所有的列必須定義為非空. WHERE子句使用的索引和ORDER BY子句中所使用的索引不能並列. 例如: 表DEPT包含以下列: DEPT_CODE PK NOT NULL DEPT_DESC NOT NULL DEPT_TYPE NULL 低效: (索引不被使用) SELECT DEPT_CODE FROM DEPT ORDER BY DEPT_TYPE 高效: (使用索引) SELECT DEPT_CODE FROM DEPT WHERE DEPT_TYPE > 0
(30) 避免改變索引列的型別:
當比較不同資料型別的資料時, ORACLE自動對列進行簡單的型別轉換. 假設 EMPNO是一個數值型別的索引列. SELECT … FROM EMP WHERE EMPNO = ‘123' 實際上,經過ORACLE型別轉換, 語句轉化為: SELECT … FROM EMP WHERE EMPNO = TO_NUMBER(‘123') 幸運的是,型別轉換沒有發生在索引列上,索引的用途沒有被改變. 現在,假設EMP_TYPE是一個字元型別的索引列. SELECT … FROM EMP WHERE EMP_TYPE = 123 這個語句被ORACLE轉換為: SELECT … FROM EMP WHERE TO_NUMBER(EMP_TYPE)=123 因為內部發生的型別轉換, 這個索引將不會被用到! 為了避免ORACLE對你的SQL進行隱式的型別轉換, 最好把型別轉換用顯式表現出來. 注意當字元和數值比較時, ORACLE會優先轉換數值型別到字元型別。
分析select emp_name form employee where salary > 3000 在此語句中若salary是Float型別的,則優化器對其進行優化為Convert(float,3000),因為3000是個整數,我們應在程式設計時使用3000.0而不要等執行時讓DBMS進行轉化。同樣字元和整型資料的轉換。
(31) 需要當心的WHERE子句:
某些SELECT 語句中的WHERE子句不使用索引. 這裡有一些例子. 在下面的例子裡, (1)‘!=' 將不使用索引. 記住, 索引只能告訴你什麼存在於表中, 而不能告訴你什麼不存在於表中. (2) ‘ ¦ ¦'是字元連線函式. 就象其他函式那樣, 停用了索引. (3) ‘+'是數學函式. 就象其他數學函式那樣, 停用了索引. (4)相同的索引列不能互相比較,這將會啟用全表掃描.
(32) a. 如果檢索資料量超過30%的表中記錄數.使用索引將沒有顯著的效率提高. b. 在特定情況下, 使用索引也許會比全表掃描慢, 但這是同一個數量級上的區別. 而通常情況下,使用索引比全表掃描要塊幾倍乃至幾千倍!
(33) 避免使用耗費資源的操作:
帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啟動SQL引擎執行耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序. 通常, 帶有UNION, MINUS , INTERSECT的SQL語句都可以用其他方式重寫. 如果你的資料庫的SORT_AREA_SIZE調配得好, 使用UNION , MINUS, INTERSECT也是可以考慮的, 畢竟它們的可讀性很強。
(34) 優化GROUP BY:
提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉.下面兩個查詢返回相同結果但第二個明顯就快了許多. 低效: SELECT JOB , AVG(SAL) FROM EMP GROUP by JOB HAVING JOB = ‘PRESIDENT' OR JOB = ‘MANAGER' 高效: SELECT JOB , AVG(SAL) FROM EMP WHERE JOB = ‘PRESIDENT' OR JOB = ‘MANAGER' GROUP by JOB
SQL效能優化二
- 減少 IO 次數 IO永遠是資料庫最容易瓶頸的地方,這是由資料庫的職責所決定的,大部分資料庫操作中超過90%的時間都是 IO 操作所佔用的,減少 IO 次數是 SQL 優化中需要第一優先考慮,當然,也是收效最明顯的優化手段。
- 降低 CPU 計算 除了 IO 瓶頸之外,SQL優化中需要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操作基本上都是 CPU 處理記憶體中的資料比較運算)。當我們的 IO 優化做到一定階段之後,降低 CPU 計算也就成為了我們 SQL 優化的重要目標
- 改變 SQL 執行計劃 明確了優化目標之後,我們需要確定達到我們目標的方法。對於 SQL 語句來說,達到上述2個目標的方法其實只有一個,那就是改變 SQL 的執行計劃,讓他儘量“少走彎路”,儘量通過各種“捷徑”來找到我們需要的資料,以達到 “減少 IO 次數” 和 “降低 CPU 計算” 的目標
- count(1)和count(primary_key) 優於 count(*) 很多人為了統計記錄條數,就使用 count(1) 和 count(primary_key) 而不是 count(*) ,他們認為這樣效能更好,其實這是一個誤區。對於有些場景,這樣做可能效能會更差,應為資料庫對 count(*) 計數操作做了一些特別的優化。
- count(column) 和 count(*) 是一樣的 這個誤區甚至在很多的資深工程師或者是 DBA 中都普遍存在,很多人都會認為這是理所當然的。實際上,count(column) 和 count(*) 是一個完全不一樣的操作,所代表的意義也完全不一樣。 count(column) 是表示結果集中有多少個column欄位不為空的記錄 count(*) 是表示整個結果集有多少條記錄
- select a,b from … 比 select a,b,c from … 可以讓資料庫訪問更少的資料量 這個誤區主要存在於大量的開發人員中,主要原因是對資料庫的儲存原理不是太瞭解。 實際上,大多數關係型資料庫都是按照行(row)的方式儲存,而資料存取操作都是以一個固定大小的IO單元(被稱作 block 或者 page)為單位,一般為4KB,8KB… 大多數時候,每個IO單元中儲存了多行,每行都是儲存了該行的所有欄位(lob等特殊型別欄位除外)。 所以,我們是取一個欄位還是多個欄位,實際上資料庫在表中需要訪問的資料量其實是一樣的。 當然,也有例外情況,那就是我們的這個查詢在索引中就可以完成,也就是說當只取 a,b兩個欄位的時候,不需要回表,而c這個欄位不在使用的索引中,需要回表取得其資料。在這樣的情況下,二者的IO量會有較大差異。
- order by 一定需要排序操作 我們知道索引資料實際上是有序的,如果我們的需要的資料和某個索引的順序一致,而且我們的查詢又通過這個索引來執行,那麼資料庫一般會省略排序操作,而直接將資料返回,因為資料庫知道資料已經滿足我們的排序需求了。 實際上,利用索引來優化有排序需求的 SQL,是一個非常重要的優化手段 延伸閱讀:MySQL ORDER BY 的實現分析 ,MySQL 中 GROUP BY 基本實現原理 以及 MySQL DISTINCT 的基本實現原理 這3篇文章中有更為深入的分析,尤其是第一篇
- 執行計劃中有 filesort 就會進行磁碟檔案排序 有這個誤區其實並不能怪我們,而是因為 MySQL 開發者在用詞方面的問題。filesort 是我們在使用 explain 命令檢視一條 SQL 的執行計劃的時候可能會看到在 “Extra” 一列顯示的資訊。 實際上,只要一條 SQL 語句需要進行排序操作,都會顯示“Using filesort”,這並不表示就會有檔案排序操作。 延伸閱讀:理解 MySQL Explain 命令輸出中的filesort,我在這裡有更為詳細的介紹
- 儘量少 join MySQL 的優勢在於簡單,但這在某些方面其實也是其劣勢。MySQL 優化器效率高,但是由於其統計資訊的量有限,優化器工作過程出現偏差的可能性也就更多。對於複雜的多表 Join,一方面由於其優化器受限,再者在 Join 這方面所下的功夫還不夠,所以效能表現離 Oracle 等關係型資料庫前輩還是有一定距離。但如果是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些資料庫前輩。
- 儘量少排序
排序操作會消耗較多的 CPU 資源,所以減少排序可以在快取命中率高等 IO 能力足夠的場景下會較大影響 SQL 的響應時間。
對於MySQL來說,減少排序有多種辦法,比如:
- 上面誤區中提到的通過利用索引來排序的方式進行優化
- 減少參與排序的記錄條數
- 非必要不對資料進行排序
- …
- 儘量避免 select * 很多人看到這一點後覺得比較難理解,上面不是在誤區中剛剛說 select 子句中欄位的多少並不會影響到讀取的資料嗎? 是的,大多數時候並不會影響到 IO 量,但是當我們還存在 order by 操作的時候,select 子句中的欄位多少會在很大程度上影響到我們的排序效率,這一點可以通過我之前一篇介紹 MySQL ORDER BY 的實現分析 的文章中有較為詳細的介紹。 此外,上面誤區中不是也說了,只是大多數時候是不會影響到 IO 量,當我們的查詢結果僅僅只需要在索引中就能找到的時候,還是會極大減少 IO 量的。
- 儘量用 join 代替子查詢 雖然 Join 效能並不佳,但是和 MySQL 的子查詢比起來還是有非常大的效能優勢。MySQL 的子查詢執行計劃一直存在較大的問題,雖然這個問題已經存在多年,但是到目前已經發布的所有穩定版本中都普遍存在,一直沒有太大改善。雖然官方也在很早就承認這一問題,並且承諾儘快解決,但是至少到目前為止我們還沒有看到哪一個版本較好的解決了這一問題。
- 儘量少 or 當 where 子句中存在多個條件以“或”並存的時候,MySQL 的優化器並沒有很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,造成了其效能比較低下,很多時候使用 union all 或者是union(必要的時候)的方式來代替“or”會得到更好的效果。
- 儘量用 union all 代替 union union 和 union all 的差異主要是前者需要將兩個(或者多個)結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的 CPU 運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重複結果集或者不在乎重複結果集的時候,儘量使用 union all 而不是 union。
- 儘量早過濾 這一優化策略其實最常見於索引的優化設計中(將過濾性更好的欄位放得更靠前)。 在 SQL 編寫中同樣可以使用這一原則來優化一些 Join 的 SQL。比如我們在多個表進行分頁資料查詢的時候,我們最好是能夠在一個表上先過濾好資料分好頁,然後再用分好頁的結果集與另外的表 Join,這樣可以儘可能多的減少不必要的 IO 操作,大大節省 IO 操作所消耗的時間。
- 避免型別轉換
這裡所說的“型別轉換”是指 where 子句中出現 column 欄位的型別和傳入的引數型別不一致的時候發生的型別轉換:
- 人為在column_name 上通過轉換函式進行轉換 直接導致 MySQL(實際上其他資料庫也會有同樣的問題)無法使用索引,如果非要轉換,應該在傳入的引數上進行轉換
- 由資料庫自己進行轉換 如果我們傳入的資料型別和欄位型別不一致,同時我們又沒有做任何型別轉換處理,MySQL 可能會自己對我們的資料進行型別轉換操作,也可能不進行處理而交由儲存引擎去處理,這樣一來,就會出現索引無法使用的情況而造成執行計劃問題。
- 優先優化高併發的 SQL,而不是執行頻率低某些“大”SQL 對於破壞性來說,高併發的 SQL 總是會比低頻率的來得大,因為高併發的 SQL 一旦出現問題,甚至不會給我們任何喘息的機會就會將系統壓跨。而對於一些雖然需要消耗大量 IO 而且響應很慢的 SQL,由於頻率低,即使遇到,最多就是讓整個系統響應慢一點,但至少可能撐一會兒,讓我們有緩衝的機會。
- 從全域性出發優化,而不是片面調整 SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中所有的 SQL,尤其是在通過調整索引優化 SQL 的執行計劃的時候,千萬不能顧此失彼,因小失大。
- 儘可能對每一條執行在資料庫中的SQL進行 explain 優化 SQL,需要做到心中有數,知道 SQL 的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對資料庫中執行的 SQL 進行了一段時間的優化之後,很明顯的問題 SQL 可能已經很少了,大多都需要去發掘,這時候就需要進行大量的 explain 操作收集執行計劃,並判斷是否需要進行優化
Mysql效能優化之引擎的選擇
MySQL 的儲存引擎可能是所有關係型資料庫產品中最具有特色的了,不僅可以同時使用多種儲存引擎,而且每種儲存引擎和MySQL之間使用外掛方式這種非常鬆的耦合關係。
由於各儲存引擎功能特性差異較大,這篇文章主要是介紹如何來選擇合適的儲存引擎來應對不同的業務場景。
- MyISAM
- 特性
- 不支援事務:MyISAM儲存引擎不支援事務,所以對事務有要求的業務場景不能使用
- 表級鎖定:其鎖定機制是表級索引,這雖然可以讓鎖定的實現成本很小但是也同時大大降低了其併發效能
- 讀寫互相阻塞:不僅會在寫入的時候阻塞讀取,MyISAM還會在讀取的時候阻塞寫入,但讀本身並不會阻塞另外的讀
- 只會快取索引:MyISAM可以通過key_buffer快取以大大提高訪問效能減少磁碟IO,但是這個快取區只會快取索引,而不會快取資料
- 適用場景
- 不需要事務支援(不支援)
- 併發相對較低(鎖定機制問題)
- 資料修改相對較少(阻塞問題)
- 以讀為主
- 資料一致性要求不是非常高
- 最佳實踐
- 儘量索引(快取機制)
- 調整讀寫優先順序,根據實際需求確保重要操作更優先
- 啟用延遲插入改善大批量寫入效能
- 儘量順序操作讓insert資料都寫入到尾部,減少阻塞
- 分解大的操作,降低單個操作的阻塞時間
- 降低併發數,某些高併發場景通過應用來進行排隊機制
- 對於相對靜態的資料,充分利用Query Cache可以極大的提高訪問效率
- MyISAM的Count只有在全表掃描的時候特別高效,帶有其他條件的count都需要進行實際的資料訪問
- 特性
- InnoDB
- 特性
- 具有較好的事務支援:支援4個事務隔離級別,支援多版本讀
- 行級鎖定:通過索引實現,全表掃描仍然會是表鎖,注意間隙鎖的影響
- 讀寫阻塞與事務隔離級別相關
- 具有非常高效的快取特性:能快取索引,也能快取資料
- 整個表和主鍵以Cluster方式儲存,組成一顆平衡樹
- 所有Secondary Index都會儲存主鍵資訊
- 適用場景
- 需要事務支援(具有較好的事務特性)
- 行級鎖定對高併發有很好的適應能力,但需要確保查詢是通過索引完成
- 資料更新較為頻繁的場景
- 資料一致性要求較高
- 硬體裝置記憶體較大,可以利用InnoDB較好的快取能力來提高記憶體利用率,儘可能減少磁碟 IO
- 最佳實踐
- 主鍵儘可能小,避免給Secondary index帶來過大的空間負擔
- 避免全表掃描,因為會使用表鎖
- 儘可能快取所有的索引和資料,提高響應速度
- 在大批量小插入的時候,儘量自己控制事務而不要使用autocommit自動提交
- 合理設定innodb_flush_log_at_trx_commit引數值,不要過度追求安全性
- 避免主鍵更新,因為這會帶來大量的資料移動
- 特性
- NDBCluster
- 特性
- 分散式:分散式儲存引擎,可以由多個NDBCluster儲存引擎組成叢集分別存放整體資料的一部分
- 支援事務:和Innodb一樣,支援事務
- 可與mysqld不在一臺主機:可以和mysqld分開存在於獨立的主機上,然後通過網路和mysqld通訊互動
- 記憶體需求量巨大:新版本索引以及被索引的資料必須存放在記憶體中,老版本所有資料和索引必須存在與記憶體中
- 適用場景
- 具有非常高的併發需求
- 對單個請求的響應並不是非常的critical
- 查詢簡單,過濾條件較為固定,每次請求資料量較少,又不希望自己進行水平Sharding
- 最佳實踐
- 儘可能讓查詢簡單,避免資料的跨節點傳輸
- 儘可能滿足SQL節點的計算效能,大一點的叢集SQL節點會明顯多餘Data節點
- 在各節點之間儘可能使用萬兆網路環境互聯,以減少資料在網路層傳輸過程中的延時
- 特性
Mysql效能優化 --- 包含SQL、表結構、索引和快取
- 優化目標
- 減少 IO 次數 IO永遠是資料庫最容易瓶頸的地方,這是由資料庫的職責所決定的,大部分資料庫操作中超過90%的時間都是 IO 操作所佔用的,減少 IO 次數是 SQL 優化中需要第一優先考慮,當然,也是收效最明顯的優化手段。
- 降低 CPU 計算 除了 IO 瓶頸之外,SQL優化中需要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操作基本上都是 CPU 處理記憶體中的資料比較運算)。當我們的 IO 優化做到一定階段之後,降低 CPU 計算也就成為了我們 SQL 優化的重要目標
- 優化方法
- 改變 SQL 執行計劃 明確了優化目標之後,我們需要確定達到我們目標的方法。對於 SQL 語句來說,達到上述2個目標的方法其實只有一個,那就是改變 SQL 的執行計劃,讓他儘量“少走彎路”,儘量通過各種“捷徑”來找到我們需要的資料,以達到 “減少 IO 次數” 和 “降低 CPU 計算” 的目標
- 常見誤區
- count(1)和count(primary_key) 優於 count(*) 很多人為了統計記錄條數,就使用 count(1) 和 count(primary_key) 而不是 count(*) ,他們認為這樣效能更好,其實這是一個誤區。對於有些場景,這樣做可能效能會更差,應為資料庫對 count(*) 計數操作做了一些特別的優化。
- count(column) 和 count(*) 是一樣的 這個誤區甚至在很多的資深工程師或者是 DBA 中都普遍存在,很多人都會認為這是理所當然的。實際上,count(column) 和 count(*) 是一個完全不一樣的操作,所代表的意義也完全不一樣。 count(column) 是表示結果集中有多少個column欄位不為空的記錄 count(*) 是表示整個結果集有多少條記錄
- select a,b from … 比 select a,b,c from … 可以讓資料庫訪問更少的資料量 這個誤區主要存在於大量的開發人員中,主要原因是對資料庫的儲存原理不是太瞭解。 實際上,大多數關係型資料庫都是按照行(row)的方式儲存,而資料存取操作都是以一個固定大小的IO單元(被稱作 block 或者 page)為單位,一般為4KB,8KB… 大多數時候,每個IO單元中儲存了多行,每行都是儲存了該行的所有欄位(lob等特殊型別欄位除外)。 所以,我們是取一個欄位還是多個欄位,實際上資料庫在表中需要訪問的資料量其實是一樣的。 當然,也有例外情況,那就是我們的這個查詢在索引中就可以完成,也就是說當只取 a,b兩個欄位的時候,不需要回表,而c這個欄位不在使用的索引中,需要回表取得其資料。在這樣的情況下,二者的IO量會有較大差異。
- order by 一定需要排序操作 我們知道索引資料實際上是有序的,如果我們的需要的資料和某個索引的順序一致,而且我們的查詢又通過這個索引來執行,那麼資料庫一般會省略排序操作,而直接將資料返回,因為資料庫知道資料已經滿足我們的排序需求了。 實際上,利用索引來優化有排序需求的 SQL,是一個非常重要的優化手段 延伸閱讀:MySQL ORDER BY 的實現分析 ,MySQL 中 GROUP BY 基本實現原理 以及 MySQL DISTINCT 的基本實現原理 這3篇文章中有更為深入的分析,尤其是第一篇
- 執行計劃中有 filesort 就會進行磁碟檔案排序 有這個誤區其實並不能怪我們,而是因為 MySQL 開發者在用詞方面的問題。filesort 是我們在使用 explain 命令檢視一條 SQL 的執行計劃的時候可能會看到在 “Extra” 一列顯示的資訊。 實際上,只要一條 SQL 語句需要進行排序操作,都會顯示“Using filesort”,這並不表示就會有檔案排序操作。 延伸閱讀:理解 MySQL Explain 命令輸出中的filesort,我在這裡有更為詳細的介紹
- 基本原則
- 儘量少 join MySQL 的優勢在於簡單,但這在某些方面其實也是其劣勢。MySQL 優化器效率高,但是由於其統計資訊的量有限,優化器工作過程出現偏差的可能性也就更多。對於複雜的多表 Join,一方面由於其優化器受限,再者在 Join 這方面所下的功夫還不夠,所以效能表現離 Oracle 等關係型資料庫前輩還是有一定距離。但如果是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些資料庫前輩。
- 儘量少排序
排序操作會消耗較多的 CPU 資源,所以減少排序可以在快取命中率高等 IO 能力足夠的場景下會較大影響 SQL 的響應時間。
對於MySQL來說,減少排序有多種辦法,比如:
- 上面誤區中提到的通過利用索引來排序的方式進行優化
- 減少參與排序的記錄條數
- 非必要不對資料進行排序
- 避免使用耗費資源的操作,帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啟動SQL引擎 執行,耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序
- …
- 儘量避免 select * 很多人看到這一點後覺得比較難理解,上面不是在誤區中剛剛說 select 子句中欄位的多少並不會影響到讀取的資料嗎? 是的,大多數時候並不會影響到 IO 量,但是當我們還存在 order by 操作的時候,select 子句中的欄位多少會在很大程度上影響到我們的排序效率,這一點可以通過我之前一篇介紹 MySQL ORDER BY 的實現分析 的文章中有較為詳細的介紹。 此外,上面誤區中不是也說了,只是大多數時候是不會影響到 IO 量,當我們的查詢結果僅僅只需要在索引中就能找到的時候,還是會極大減少 IO 量的。
- 儘量用 join 代替子查詢 雖然 Join 效能並不佳,但是和 MySQL 的子查詢比起來還是有非常大的效能優勢。MySQL 的子查詢執行計劃一直存在較大的問題,雖然這個問題已經存在多年,但是到目前已經發布的所有穩定版本中都普遍存在,一直沒有太大改善。雖然官方也在很早就承認這一問題,並且承諾儘快解決,但是至少到目前為止我們還沒有看到哪一個版本較好的解決了這一問題。
- 儘量少 or 當 where 子句中存在多個條件以“或”並存的時候,MySQL 的優化器並沒有很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,造成了其效能比較低下,很多時候使用 union all 或者是union(必要的時候)的方式來代替“or”會得到更好的效果。
- 儘量用 union all 代替 union union 和 union all 的差異主要是前者需要將兩個(或者多個)結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的 CPU 運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重複結果集或者不在乎重複結果集的時候,儘量使用 union all 而不是 union。
- 儘量早過濾 這一優化策略其實最常見於索引的優化設計中(將過濾性更好的欄位放得更靠前)。 在 SQL 編寫中同樣可以使用這一原則來優化一些 Join 的 SQL。比如我們在多個表進行分頁資料查詢的時候,我們最好是能夠在一個表上先過濾好資料分好頁,然後再用分好頁的結果集與另外的表 Join,這樣可以儘可能多的減少不必要的 IO 操作,大大節省 IO 操作所消耗的時間。
- 避免型別轉換
這裡所說的“型別轉換”是指 where 子句中出現 column 欄位的型別和傳入的引數型別不一致的時候發生的型別轉換:
- 人為在column_name 上通過轉換函式進行轉換 直接導致 MySQL(實際上其他資料庫也會有同樣的問題)無法使用索引,如果非要轉換,應該在傳入的引數上進行轉換
-
SELECT emp.ename, emp.job FROM emp WHERE emp.empno = 7369; 不要使用:SELECT emp.ename, emp.job FROM emp WHERE emp.empno = ‘7369
- 由資料庫自己進行轉換 如果我們傳入的資料型別和欄位型別不一致,同時我們又沒有做任何型別轉換處理,MySQL 可能會自己對我們的資料進行型別轉換操作,也可能不進行處理而交由儲存引擎去處理,這樣一來,就會出現索引無法使用的情況而造成執行計劃問題。
- 優先優化高併發的 SQL,而不是執行頻率低某些“大”SQL 對於破壞性來說,高併發的 SQL 總是會比低頻率的來得大,因為高併發的 SQL 一旦出現問題,甚至不會給我們任何喘息的機會就會將系統壓跨。而對於一些雖然需要消耗大量 IO 而且響應很慢的 SQL,由於頻率低,即使遇到,最多就是讓整個系統響應慢一點,但至少可能撐一會兒,讓我們有緩衝的機會。
- 從全域性出發優化,而不是片面調整 SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中所有的 SQL,尤其是在通過調整索引優化 SQL 的執行計劃的時候,千萬不能顧此失彼,因小失大。
- 儘可能對每一條執行在資料庫中的SQL進行 explain 優化 SQL,需要做到心中有數,知道 SQL 的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對資料庫中執行的 SQL 進行了一段時間的優化之後,很明顯的問題 SQL 可能已經很少了,大多都需要去發掘,這時候就需要進行大量的 explain 操作收集執行計劃,並判斷是否需要進行優化。
- 資料型別選擇
資料庫操作中最為耗時的操作就是 IO 處理,大部分資料庫操作 90% 以上的時間都花在了 IO 讀寫上面。所以儘可能減少 IO 讀寫量,可以在很大程度上提高資料庫操作的效能。我們無法改變資料庫中需要儲存的資料,但是我們可以在這些資料的儲存方式方面花一些心思。下面的這些關於欄位型別的優化建議主要適用於記錄條數較多,資料量較大的場景,因為精細化的資料型別設定可能帶來維護成本的提高,過度優化也可能會帶來其他的問題:
- 數字型別:非萬不得已不要使用DOUBLE,不僅僅只是儲存長度的問題,同時還會存在精確性的問題。同樣,固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數儲存,可以大大節省儲存空間,且不會帶來任何附加維護成本。對於整數的儲存,在資料量較大的情況下,建議區分開 TINYINT / INT / BIGINT 的選擇,因為三者所佔用的儲存空間也有很大的差別,能確定不會使用負數的欄位,建議新增unsigned定義。當然,如果資料量較小的資料庫,也可以不用嚴格區分三個整數型別。
- 字元型別:非萬不得已不要使用 TEXT 資料型別,其處理方式決定了他的效能要低於char或者是varchar型別的處理。定長欄位,建議使用 CHAR 型別,不定長欄位儘量使用 VARCHAR,且僅僅設定適當的最大長度,而不是非常隨意的給一個很大的最大長度限定,因為不同的長度範圍,MySQL也會有不一樣的儲存處理。
- 時間型別:儘量使用TIMESTAMP型別,因為其儲存空間只需要 DATETIME 型別的一半。對於只需要精確到某一天的資料型別,建議使用DATE型別,因為他的儲存空間只需要3個位元組,比TIMESTAMP還少。不建議通過INT型別類儲存一個unix timestamp 的值,因為這太不直觀,會給維護帶來不必要的麻煩,同時還不會帶來任何好處。
- ENUM & SET:對於狀態欄位,可以嘗試使用 ENUM 來存放,因為可以極大的降低儲存空間,而且即使需要增加新的型別,只要增加於末尾,修改結構也不需要重建表資料。如果是存放可預先定義的屬性資料呢?可以嘗試使用SET型別,即使存在多種屬性,同樣可以遊刃有餘,同時還可以節省不小的儲存空間。
- LOB型別:強烈反對在資料庫中存放 LOB 型別資料,雖然資料庫提供了這樣的功能,但這不是他所擅長的,我們更應該讓合適的工具做他擅長的事情,才能將其發揮到極致。在資料庫中儲存 LOB 資料就像讓一個多年前在學校學過一點Java的營銷專業人員來寫 Java 程式碼一樣。
- 字元編碼
字符集直接決定了資料在MySQL中的儲存編碼方式,由於同樣的內容使用不同字符集表示所佔用的空間大小會有較大的差異,所以通過使用合適的字符集,可以幫助我們儘可能減少資料量,進而減少IO操作次數。
- 純拉丁字元能表示的內容,沒必要選擇 latin1 之外的其他字元編碼,因為這會節省大量的儲存空間
- 如果我們可以確定不需要存放多種語言,就沒必要非得使用UTF8或者其他UNICODE字元型別,這回造成大量的儲存空間浪費
- MySQL的資料型別可以精確到欄位,所以當我們需要大型資料庫中存放多位元組資料的時候,可以通過對不同表不同欄位使用不同的資料型別來較大程度減小資料儲存量,進而降低 IO 操作次數並提高快取命中率
- 適當拆分 有些時候,我們可能會希望將一個完整的物件對應於一張資料庫表,這對於應用程式開發來說是很有好的,但是有些時候可能會在效能上帶來較大的問題。當我們的表中存在類似於 TEXT 或者是很大的 VARCHAR型別的大欄位的時候,如果我們大部分訪問這張表的時候都不需要這個欄位,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用資料所佔用的儲存空間。這樣做的一個明顯好處就是每個資料塊中可以儲存的資料條數可以大大增加,既減少物理 IO 次數,也能大大提高記憶體中的快取命中率。
- 適度冗餘
為什麼我們要冗餘?這不是增加了每條資料的大小,減少了每個資料塊可存放記錄條數嗎?確實,這樣做是會增大每條記錄的大小,降低每條記錄中可存放資料的條數,但是在有些場景下我們仍然還是不得不這樣做:
- 被頻繁引用且只能通過 Join 2張(或者更多)大表的方式才能得到的獨立小欄位 這樣的場景由於每次Join僅僅只是為了取得某個小欄位的值,Join到的記錄又大,會造成大量不必要的 IO,完全可以通過空間換取時間的方式來優化。不過,冗餘的同時需要確保資料的一致性不會遭到破壞,確保更新的同時冗餘欄位也被更新
- 儘量使用 NOT NULL NULL 型別比較特殊,SQL 難優化。雖然 MySQL NULL型別和 Oracle 的NULL 有差異,會進入索引中,但如果是一個組合索引,那麼這個NULL 型別的欄位會極大影響整個索引的效率。此外,NULL 在索引中的處理也是特殊的,也會佔用額外的存放空間。 很多人覺得 NULL 會節省一些空間,所以儘量讓NULL來達到節省IO的目的,但是大部分時候這會適得其反,雖然空間上可能確實有一定節省,倒是帶來了很多其他的優化問題,不但沒有將IO量省下來,反而加大了SQL的IO量。所以儘量確保 DEFAULT 值不是 NULL,也是一個很好的表結構設計優化習慣。
- 索引為什麼能提高資料訪問效能? 很多人只知道索引能夠提高資料庫的效能,但並不是特別瞭解其原理,其實我們可以用一個生活中的示例來理解。我們讓一位不太懂計算機的朋友去圖書館確認一本叫做《MySQL效能調優與架構設計》的書是否在藏,這樣對他說:“請幫我借一本計算機類的資料庫書籍,是屬於 MySQL 資料庫範疇的,叫做《MySQL效能調優與架構設計》”。朋友會根據所屬類別,前往存放“計算機”書籍區域的書架,然後再尋找“資料庫”類存放位置,再找到一堆講述“MySQL”的書籍,最後可能發現目標在藏(也可能已經借出不在書架上)。在這個過程中: “計算機”->“資料庫”->“MySQL”->“在藏”->《MySQL效能調優與架構設計》其實就是一個“根據索引查詢資料”的典型案例,“計算機”->“資料庫”->“MySQL”->“在藏” 就是朋友查詢書籍的索引。假設沒有這個索引,那查詢這本書的過程會變成怎樣呢?朋友只能從圖書館入口一個書架一個書架的“遍歷”,直到找到《MySQL效能調優與架構設計》這本書為止。如果幸運,可能在第一個書架就找到。但如果不幸呢,那就慘了,可能要將整個圖書館所有的書架都找一遍才能找到我們想要的這本書。注:這個例子中的“索引”是記錄在朋友大腦中的,實際上,每個圖書館都會有一個非常全的實際存在的索引系統(大多位於入口顯眼處),由很多個貼上了明顯標籤的小抽屜構成。這個索引系統中存放這非常齊全詳盡的索引資料,標識出我們需要查詢的“目標”在某個區域的某個書架上。而且每當有新的書籍入庫,舊的書籍銷燬以及書記資訊修改,都需要對索引系統進行及時的修正。
- 索引有哪些“副作用”?
- 圖書的變更(增,刪,改)都需要修訂索引,索引存在額外的維護成本
- 查詢翻閱索引系統需要消耗時間,索引存在額外的訪問成本
- 這個索引系統需要一個地方來存放,索引存在額外的空間成本
- 索引是不是越多越好?
- 如果我們的這個圖書館只是一個進出中轉站,裡面的新書進來後很快就會轉發去其他圖書館而從這個館藏中“清除”,那我們的索引就只會不斷的修改,而很少會被用來查詢圖書 所以,對於類似於這樣的存在非常大更新量的資料,索引的維護成本會非常高,如果其檢索需求很少,而且對檢索效率並沒有非常高的要求的時候,我們並不建議建立索引,或者是儘量減少索引。
- 如果我們的書籍量少到只有幾本或者就只有一個書架,索引並不會帶來什麼作用,甚至可能還會浪費一些查詢索引所花費的時間。 所以,對於資料量極小到通過索引檢索還不如直接遍歷來得快的資料,也並不適合使用索引。
- 如果我們的圖書館只有一個10平方的面積,現在連放書架都已經非常擁擠,而且館藏還在不斷增加,我們還能考慮建立索引嗎? 所以,當我們連儲存基礎資料的空間都捉襟見肘的時候,我們也應該儘量減少低效或者是去除索引。