1. 程式人生 > >讓天下沒有難用的資料庫 » SQL優化的一些總結

讓天下沒有難用的資料庫 » SQL優化的一些總結

SQL的優化是DBA日常工作中不可缺少的一部分,記得在學生時期,曾經在ITPUB上看到一篇帖子,當時樓主在介紹SQL優化的時候,用一個公式來講解他在做sql優化的時候遵循的原則:

          T=S/V(T代表時間,S代表路程,V代表速度)

S指SQL所需訪問的資源總量,V指SQL單位時間所能訪問的資源量,T自然就是SQL執行所需時間了;我們為了獲得SQL最快的執行時間,可以根據公式定義上去反推:

  1. 在S不變的情況下,我們可以提升V來降低T:通過適當的索引調整,我們可以將大量的速度較慢的隨機IO轉換為速度較快的順序IO;通過提升伺服器的記憶體,使得將更多的資料放到記憶體中,會比資料放到磁碟上會得到明顯的速度提升;採用電子儲存介質進行資料儲存和讀取的SSD,突破了傳統機械硬碟的效能瓶頸,使其擁有極高的儲存效能;在提升V上我們可以採用較高配置的硬體來完成速度的提升;
  2. 在V不變的情況下,我們可以減小S來降低T:這是SQL優化中非常核心的一個環節,在減小S環節上,DBA可以做的可以有很多,通常可以在查詢條件中建立適當的索引,來避免全表掃描;有時候可以改寫SQl,新增一些適當的提示符,來改變SQL的執行計劃,使SQL以最少的掃描路徑完成查詢;當這些方法都使用完了之後,你是否還有其他方案來優化喃?在阿里系的DBA職位描述中有條就是要求DBA需要深入的瞭解業務,當DBA深入的瞭解業務之後,這個時候能站在業務上,又站DB角度上考慮,這個時候在去做優化,有時候能達到事半功倍的效果。

案例一:通過降低S,來提升T

原理介紹:
我們知道B+索引葉子節點的值是按照索引欄位升序的,比如我們對(nick,appkey)兩個欄位做了索引,那麼在索引中的則是按照nick,appkey的升序排列;如果我們現在的一條sql:
select count(distinct nick) from xxxx_nickapp_09_29;
用於查詢統計某天日誌表中的UV,優化器選擇了該表上索引ind_nick_appkey(nick,appkey)來完成查詢,則開始從nick1開始一條條掃描下來,直到掃描到最後一個nick_n,那麼中間過程會掃描很多重複的nick(最左邊普通掃描),如果我們能夠跳過中間重複的nick,則效能會優化非常多(最右邊的鬆散掃描):

從上面的可以得到一個結論:

如果這條統計uv的sql能夠按照右邊的loose index scan的方式來掃描話,會大大的減小我們上面提到的S;所以需要通過改寫sql來達到偽loose index scan:(MySql優化器不能直接的對count(distinct column)做優化)

[email protected] 09:41:30>select count(*) from ( select distinct(nick) from xxxx_nickapp_09_29)t ;
+———-+
| count(*) |
+———-+
| 806934 |
+———-+
Sql內查詢中先選出不同的nick,最後在外面套一層count,就可以得到nick的distinct值總和;
最重要的是在子查詢中:select distinct(nick) 實現了上圖中的偽loose index scan,優化器在這個時候的執行計劃為Using index for group-by ,這樣mysql就把distinct優化為group by,首先利用索引來分組,然後掃描索引,對需要的nick只掃描一條記錄。

真實案例:

該案例選自我們的一個線上的生產系統,該系統每天有大量的日誌資料入庫,單表的容量在10G-50G之間,然後做彙總分析,計算日誌資料中的uv就是其中一個邏輯,sql如下:

select count(distinct nick) from xxxx_nickapp_09_29;

即使在_xxxx分表上加上nick的索引,通過檢視執行計劃,為全索引掃描,由於單表的資料量過大,sql在執行的時候,會對整個伺服器帶來抖動,需要對原來的SQL進行改寫,使其支援loose index scan;

優化前:

[email protected] 09:41:30>select count(distinct nick) from xxxx_nickapp_09_29;
+———-+
| count(*) |
+———-+
| 806934 |

1 row in set (52.78 sec)
執行一次sql需要花費52.78s

優化後:

[email protected] 09:41:30>select count(*) from ( select distinct(nick) from xxxx_nickapp_09_29)t ;

+———-+
| count(*) |
+———-+
| 806934 |
+———-+
1 row in set (5.81 sec)

由52.78秒降至5.81秒,速度提升了差不多10倍;

檢視SQL的執行計劃:

優化寫法:

[email protected] 09:41:30>explain select count(*) from ( select distinct(nick) from xxxx_nickapp_09_29)t ;

+—-+————-+——————————+——-+—————+———————————+———+—–
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+——————————+——-+—————+———————————+———+—–
| 1 | SIMPLE | xxxx_nickapp_09_29 | range | NULL |ind_nick_appkey | 67 | NULL | 2124695 | Using index for group-by |
+—-+————-+——————————+——-+—————+———————————+———+—–
原始寫法:
[email protected] 09:41:50>explain select count(distinct nick) from xxxx_nickapp_09_29;
+—-+————-+——————————+——-+—————+—————————-+———+——+–
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+——————————+——-+—————+—————————-+———+——+–
| 1 | SIMPLE | xxxx_nickapp_09_29 | index | NULL | ind_nick_appkey | 177 | NULL | 19546123 | Using index |
+—-+————-+——————————+——-+————–+—————————-+———+——+–

可以看到我們的路程由19546123減小到2124695,減小了9倍多.^_^

案例二:結合業務遞增的寫入特點,巧妙優化UV統計count(*)

有時候覺得,優化一條sql的最高境界就是讓這sql能夠從把這條從系統中拿掉,不管怎樣,這些都是建立在你足夠的瞭解業務上,就能夠推動一些業務產品的升級或者下線,這樣的DBA你能做到嗎?

下面看一個案例:應用每天都會對入庫的分表統計一個總數:select count(*) from xx_01_01;
隨著單表的資料量越來越大(單表在20G左右),每次進行count的時候,速度越來越慢,同時需要掃描較多的資料頁塊,導致整個資料庫效能的抖動,通過分析業務的特點,由於每張表採用自增id的方式進行插入,並且沒有資料的刪除,所以統計全表的總數就可以變通一下:

所以這條sql:select count(*) from xx_01_01;
可以變通為: select max(id)-min(id)+1 from xx_01_01;
執行速度可以得到質的飛躍 ^_^.

案例三:通過提升V,來降低T—隨機IO  VS  順序IO

            在前面我們提到,提升V的一些方法,通常可以採用提升伺服器硬體的方式來達到,但是很多中小型企業來說,現在比較高的成本對於他們來說還是望塵莫及,同時沒有成熟的使用經驗,對於他們可能還是一件壞事情。總的來說,你的伺服器硬體無論在牛,如果SQL寫的爛,索引建的不好,那還是不行的。

真實線上案例:在我們的一個核心產品庫上,承載著非常大量的隨機讀,就叫它讀庫好了。一天讀庫的load非常的高,通過慢日誌發現,有一條sql頻繁的出現在慢日中,這條sql的查詢條件很複雜,同時該表上的類似相同的索引也非常的多,當時是懷疑索引走錯,通過explain 來檢視SQL的執行計劃:發現執行計劃中的using where代表查詢回表了,同時由於回表的記錄rows較大,所以帶來了大量的隨機IO:

所以我們只需要在原來的索引冗餘掉is_detail欄位就可以通過覆蓋索引的方法優化掉該sql,避免了查詢回表而導致的隨機io,用順序io替換了原來的隨機io,SQL的執行速度得到極大提升:(下圖會去掉is_detail欄位的測試)

總結:SQL優化是很有趣的一件事情,我們在日常工作中可以按照t=s/v的思路來進行優化,也許你第一次運用它的時候有些陌生,但是隻要不斷的練習,善於總結,你也會發現其中的規律,真是妙哉妙哉。還有一點很重要的是,你的SQL優化不要脫離實際業務,也許你在哪裡優化一條sql花了1個小時,但是去和開發同學討論優化成果的時候,開發同學說這條sql其實可以下線了,那時候真的哭笑不得了 ^_^.