思路:
SQL優(yōu)化我們從以下三個方面進(jìn)行:
1、數(shù)據(jù)庫設(shè)計;
2、索引設(shè)計;
3、查詢優(yōu)化;
一、數(shù)據(jù)庫設(shè)計
1.盡可能在數(shù)據(jù)庫設(shè)計時,不要使用NULL,盡可能使用NOT NULL;
備注、描述、評論之類的可以設(shè)置為 NULL,其他的,最好不要使用NULL。不要以為 NULL 不需要空間,比如:char(100) 型,在字段建立時,空間就固定了, 不管是否插入值(NULL也包含在內(nèi)),都是占用 100個字符的空間的,如果是varchar這樣的變長字段, NULL不占用空間。
2.盡可能使用VARCHAR/NVARCHAR 代替CHAR/NCHAR;
首先變長字段存儲空間小,可以節(jié)省存儲空間,其次對于查詢來說,在一個相對較小的字段內(nèi)搜索效率顯然要高些。
3.盡可能使用數(shù)字型字段,若只含數(shù)值信息的字段盡量不要設(shè)計為字符型;
若只含數(shù)值信息的字段盡量不要設(shè)計為字符型,這會降低查詢和連接的性能,并會增加存儲開銷。這是因為引擎在處理查詢和連 接時會逐個比較字符串中每一個字符,而對于數(shù)字型而言只需要比較一次就夠了。
4.使用utf8mb4字符集;
二、索引設(shè)計
1.選擇唯一性索引
唯一性索引的值是唯一的,可以更快速的通過該索引來確定某條記錄。例如,學(xué)生表中學(xué)號是具有唯一性的字段。為該字段建立唯一性索引可以很快的確定某個學(xué)生的信息。如果使用姓名的話,可能存在同名現(xiàn)象,從而降低查詢速度。
2.為經(jīng)常需要排序、分組和聯(lián)合操作的字段建立索引
經(jīng)常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作會浪費很多時間。如果為其建立索引,可以有效地避免排序操作。
3.為常作為查詢條件的字段建立索引
如果某個字段經(jīng)常用來做查詢條件,那么該字段的查詢速度會影響整個表的查詢速度。因此,為這樣的字段建立索引,可以提高整個表的查詢速度。
4.限制索引的數(shù)目
索引的數(shù)目不是越多越好。每個索引都需要占用磁盤空間,索引越多,需要的磁盤空間就越大。修改表時,對索引的重構(gòu)和更新很麻煩。越多的索引,會使更新表變得很浪費時間,一般不超過6個。
5.盡量使用數(shù)據(jù)量少的索引
如果索引的值很長,那么查詢的速度會受到影響。例如,對一個CHAR(100)類型的字段進(jìn)行全文檢索需要的時間肯定要比對CHAR(10)類型的字段需要的時間要多。
6.盡量使用前綴來索引
如果索引字段的值很長,最好使用值的前綴來索引。例如,TEXT和BLOG類型的字段,進(jìn)行全文檢索會很浪費時間。如果只檢索字段的前面的若干個字符,這樣可以提高檢索速度。
7.刪除不再使用或者很少使用的索引
表中的數(shù)據(jù)被大量更新,或者數(shù)據(jù)的使用方式被改變后,原有的一些索引可能不再需要。數(shù)據(jù)庫管理員應(yīng)當(dāng)定期找出這些索引,將它們刪除,從而減少索引對更新操作的影響。
8 . 最左前綴匹配原則,非常重要的原則。
MySQL會一直向右匹配直到遇到范圍查詢(>、 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調(diào)整。
9 .=和in可以亂序。
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優(yōu)化器會幫你優(yōu)化成索引可以識別的形式
10 . 盡量選擇區(qū)分度高的列作為索引。
區(qū)分度的公式是count(distinct col)/count(*),表示字段不重復(fù)的比例,比例越大我們掃描的記錄數(shù)越少,唯一鍵的區(qū)分度是1,而一些狀態(tài)、性別字段可能在大數(shù)據(jù)面前區(qū)分度就是0,那可能有人會問,這個比例有什么經(jīng)驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條 記錄
11 .索引列不能參與計算,保持列“干凈”。
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數(shù)據(jù)表中的字段值,但進(jìn)行檢索時,需要把所有元素都應(yīng)用函數(shù)才能比較,顯然成本太大。所以語句應(yīng)該寫成create_time = unix_timestamp(’2014-05-29’);
12 .盡量的擴(kuò)展索引,不要新建索引。
比如表中已經(jīng)有a的索引,現(xiàn)在要加(a,b)的索引,那么只需要修改原來的索引即可
注意:選擇索引的最終目的是為了使查詢的速度變快。上面給出的原則是最基本的準(zhǔn)則,但不能拘泥于上面的準(zhǔn)則。讀者要在以后的學(xué)習(xí)和工作中進(jìn)行不斷的實踐。根據(jù)應(yīng)用的實際情況進(jìn)行分析和判斷,選擇最合適的索引方式。
13.理解單列索引和多列索引的使用
(1)創(chuàng)建復(fù)合索引時,應(yīng)該仔細(xì)考慮列的順序。對索引中的所有列執(zhí)行搜索或僅對前幾列執(zhí)行搜索時,復(fù)合索引非常有用;僅對后面的任意列執(zhí)行搜索時,復(fù)合索引則沒有用處。
(2)如果很可能僅對一個列多次執(zhí)行搜索,則該列應(yīng)該是復(fù)合索引中的第一列。如果您很可能對一個兩列索引中的兩個列執(zhí)行單獨的搜索,則應(yīng)該創(chuàng)建另一個僅包含第二列的索引。
(3)包含多個列的主鍵始終會自動以復(fù)合索引的形式創(chuàng)建索引,其列的順序是它們在表定義中出現(xiàn)的順序,而不是在主鍵定義中指定的順序。在考慮將來通過主鍵執(zhí)行的搜索,確定哪一列應(yīng)該排在最前面。請注意,創(chuàng)建復(fù)合索引應(yīng)當(dāng)包含少數(shù)幾個列,并且這些列經(jīng)常在select查詢里使用。在復(fù)合索引里包含太多的列不僅不會給帶來太多好處。而且由于使用相當(dāng)多的內(nèi)存來存儲復(fù)合索引的列的值,其后果是內(nèi)存溢出和性能降低。
(4)復(fù)合索引只對和索引中排序相同或相反的order by 語句優(yōu)化。
三、查詢優(yōu)化
(1)首先需要了解SQL的執(zhí)行順序:(1)from (2) on (3) join (4) where (5)group by(開始使用select中的別名,后面的語句中都可以使用) (6) avg,sum.... (7)having (8) select (9) distinct (10) order by (11) limit;
(2)然后學(xué)會使用explain進(jìn)行具體分析優(yōu)化;
(3)了解mysql索引背后的數(shù)據(jù)結(jié)構(gòu)及算法原理;
(4)通過--log-slow-queries,--log-queries-not-using-indexes兩個參數(shù)的開啟,通過慢查詢?nèi)罩具M(jìn)行分析;
1.操作符
(1)采用OR、<>或!=操作符時,查詢語句不會使用索引;
(2)采用NOT操作符時,查詢語句不會使用索引;
(3)采用HAVING操作符時,查詢語句不會使用索引;
HAVING 子句使你能夠指定過濾條件,從而控制查詢結(jié)果中哪些組可以出現(xiàn)在最終結(jié)果里面。
WHERE 子句對被選擇的列施加條件,而 HAVING 子句則對 GROUP BY 子句所產(chǎn)生的組施加條件。
(4)避免在索引列上使用IS NULL和IS NOT NULL;
(5)避免在索引列上出現(xiàn)數(shù)據(jù)類型轉(zhuǎn)換;
2.當(dāng)列參與計算和標(biāo)記時,索引也無法使用;
3.統(tǒng)一SQL語句的寫法,大小寫會影響SQL查詢分析器的解析,會認(rèn)為是兩個語句;
4.不要一條SQL寫得太長;
5.一條select語句盡量別超過3個嵌套;
6.盡量避免大的事務(wù)操作,只在必要的時候使用,提高系統(tǒng)并發(fā)能力,因為mysql執(zhí)行事務(wù)時候會鎖表,會阻礙其他連接訪問;
7.盡量避免向客戶端返回大數(shù)據(jù)量,多使用limit,若數(shù)據(jù)量過大,應(yīng)該考慮相應(yīng)需求是否合理;
8.拆分大的DELETE或INSERT語句,批量提交SQL語句;
9.盡量避免使用游標(biāo),因為游標(biāo)的效率較差,如果游標(biāo)操作的數(shù)據(jù)超過1萬行,那么就應(yīng)該考慮改寫。
10.任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段;
11.Update 的時候,如果只更改1、2個字段,不要Update全部字段,否則頻繁調(diào)用會引起明顯的性能消耗,同時帶來大量日志;
12.應(yīng)盡量避免在where子句中對字段進(jìn)行函數(shù)操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描;
13.IN要慎用,多數(shù)時可以用exists替換;
14.在可以使用UNION ALL的語句里,盡量別使用UNION;
因為UNION會對兩個表進(jìn)行比較,會有大的消耗
15.避免使用模糊查詢,因為查詢語句不會使用索引;
16.不要以字符格式聲明數(shù)字,要以數(shù)字格式聲明字符值。(日期同樣)否則會使索引無效,產(chǎn)生全表掃描;
17.OLTP系統(tǒng)SQL語句采用綁定變量;
select*from orderheader where changetime >'2010-10-20 00:00:01'
select*from orderheader where changetime >'2010-09-22 00:00:01’
以上兩句語句,查詢優(yōu)化器認(rèn)為是不同的SQL語句,需要解析兩次。如果采用綁定變量:
select*from orderheader where changetime >@chgtime
但注意綁定變量對大多數(shù)OLTP處理是適用的,但是也有例外。比如在where條件中的字段是“傾斜字段”的時候。“傾斜字段”指該列中的絕大多數(shù)的值都是相同的,
18.對于多張大數(shù)據(jù)量(這里幾百條就算大了)的表JOIN,要先分頁再JOIN,否則邏輯讀會很高,性能很差
19.避免使用耗費資源的操作,帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啟動SQL引擎 執(zhí)行,耗費資源的排序(SORT)功能;
DISTINCT需要一次排序操作, 而其他的至少需要執(zhí)行兩次排序
20.盡量使用表變量來代替臨時表。如果表變量包含大量數(shù)據(jù),請注意索引非常有限(只有主鍵索引);
21.避免頻繁創(chuàng)建和刪除臨時表,以減少系統(tǒng)表資源的消耗。臨時表并不是不可使用,適當(dāng)?shù)厥褂盟鼈兛梢允鼓承├谈行В纾?dāng)需要重復(fù)引用大型表或常用表中的某個數(shù)據(jù)集時。但是,對于一次性事件, 最好使用導(dǎo)出表;
22.在新建臨時表時,如果一次性插入數(shù)據(jù)量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數(shù)據(jù)量不大,為了緩和系統(tǒng)表的資源,應(yīng)先create table,然后insert;
23.如果使用到了臨時表,在存儲過程的最后務(wù)必將所有的臨時表顯式刪除,先 truncate table ,然后 drop table ,這樣可以避免系統(tǒng)表的較長時間鎖定
24.與臨時表一樣,游標(biāo)并不是不可使用。對小型數(shù)據(jù)集使用 FAST_FORWARD 游標(biāo)通常要優(yōu)于其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數(shù)據(jù)時。在結(jié)果集中包括“合計”的例程通常要比使用游標(biāo)執(zhí)行的速度快。如果開發(fā)時 間允許,基于游標(biāo)的方法和基于集的方法都可以嘗試一下,看哪一種方法的效果更好
25.在所有的存儲過程和觸發(fā)器的開始處設(shè)置 SET NOCOUNT ON ,在結(jié)束時設(shè)置 SET NOCOUNT OFF 。無需在執(zhí)行存儲過程和觸發(fā)器的每個語句后向客戶端發(fā)送 DONE_IN_PROC 消息;
26.應(yīng)盡可能的避免更新 clustered 索引數(shù)據(jù)列,因為 clustered 索引數(shù)據(jù)列的順序就是表記錄的物理存儲順序,一旦該列值改變將導(dǎo)致整個表記錄的順序的調(diào)整,會耗費相當(dāng)大的資源。若應(yīng)用系統(tǒng)需要頻繁更新 clustered 索引數(shù)據(jù)列,那么需要考慮是否應(yīng)將該索引建為 clustered 索引;
本文參考:
索引的設(shè)計原則
Mysql常用30種SQL查詢語句優(yōu)化方法
高手詳解SQL性能優(yōu)化十條經(jīng)驗
高性能SQL語句權(quán)威指南
MYSQL 優(yōu)化常用方法
優(yōu)化SQL查詢:如何寫出高性能SQL語句
MySQL索引背后的數(shù)據(jù)結(jié)構(gòu)及算法原理