SQL優化教程(詳細)

一、基礎數據準備

 

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

二、百萬數據寫入

 上面插入幾條測試數據,在使用索引時還需要插入更多的數據作爲測試數據,下面就通過存儲過程插入500W條數據作爲測試數據

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

三、簡單舉例比較使用和不使用索引的區別

 沒有添加索引前一個簡單的查詢用了1.79秒

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

創建索引,然後再查詢可以看到耗時0.00秒,這就是索引的威力

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

四、explain命令

 

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

explain命令用於查看sql執行時是否使用了索引,是優化SQL語句的一個非常常用而且非常重要的一個命令, 上面中的key字段表示查詢使用到的索引即使用了idx_username索引

id: SELECT識別符。這是SELECT的查詢序列號

select_type: 查詢類型

simple: 簡單表即不適用表連接或者子查詢

primary: 主查詢,即外層的查詢

subquery: 子查詢內層第一個SELECT,結果不依賴於外部查詢

dependent subquery: 子查詢內層第一個

select: 依賴於外部查詢

union: UNION語句中第二個SELECT開始後面所有SELECT

union result union 中合併結果

DERIVED

table:查詢的表

partitions

type:掃描的方式,all表示全表掃描

all : 全表掃描

index: 掃描所有索引

range: 索引範圍掃描,常見於< <=、>、>=、between、

const: 表最多有一個匹配行, 常見於根據主鍵或唯一索引進行查詢

system: 表僅有一行(=系統表)。這是const聯接類型的一個特例

ref

possible_keys: 該查詢可以利用的索引,可能同一個查詢有多個索引可以使用,如果沒有任何索引顯示null

key: 實際使用到的索引,從Possible_key中所選擇使用索引,當有多個索引時,mysql會挑出一個最優的索引來使用

key_len: 被選中使用索引的索引長度

ref:

多表連接時的外鍵字段

const

rows: 估算出結果集行數,該sql語句掃描了多少行,可能得到的結果,MySQL認爲它執行查詢時必須檢查的行數

filtered:

Extra:額外重要的信息

no tables: Query語句中使用FROM DUAL 或不含任何FROM子句

using filesort : 使用文件排序,最好能避免這種情況

Using temporary: 某些操作必須使用臨時表,常見 GROUP BY ; ORDER BY

Using where: 不用讀取表中所有信息,僅通過索引就可以獲取所需數據;

Using join buffer (Block Nested Loop)

Using index condition

Using sort_union(索引名)

查看索引的使用情況:

show status like ‘Handler_read%’;

Handler_read_key: 越高越好

Handler_read_rnd_next:越低越好

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

查詢優化器:

重新定義表的關聯順序(優化器會根據統計信息來決定表的關聯順序)

將外連接轉化成內連接(當外連接等於內連接)

使用等價變換規則(如去掉1=1)

優化count()、min()、max()

子查詢優化

提前終止查詢

in條件優化

mysql可以通過 EXPLAIN EXTENDED 和 SHOW WARNINGS 來查看mysql優化器改寫後的sql語句

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

 

五、多種條件下索引使用與否的比較

1. in走索引

in操作能避免則避免,若實在避免不了,需要仔細評估in後邊的集合元素數量,控制在1000個之內。

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

 

2. 範圍查詢走索引

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

 

3. 模糊查詢只有左前綴使用索引

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

4. 反向條件不走索引 != 、 <> 、 NOT IN、IS NOT NULL

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

5. 對條件計算(使用函數或者算數表達式)不走索引

使用函數計算不走索引,無論是對字段使用了函數還是值使用了函數都不走索引,解決辦法通過應用程序計算好,將計算的結果傳遞給sql,而不是讓數據庫去計算

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

id是主鍵,id/10使用了算數表達式不走索引

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

6. 查詢時必須使用正確的數據類型

如果索引字段是字符串類型,那麼查詢條件的值必須使用引號,否則不走索引

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

7. or 使用索引和不使用索引的情況

or 只有兩邊都有索引才走索引,如果都沒有或者只有一個是不走索引的

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

8. 用union少用or

儘量避免使用or,因爲大部分or連接的兩個條件同時都進行索引的情況機率比較小,應使用uninon代替,這樣能走索引的走索引,不能走索引的就全表掃描。

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

9. 能用union all就不用union

union all 不去重複,union去重複,union使用了臨時表,應儘量避免使用臨時表

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

10. 複合索引

對於複合索引,如果單獨使用右邊的索引字段作爲條件時不走索引的。即複合索引如果不滿足最左原則leftmost不會走複合索引

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

11. 覆蓋索引

覆蓋索引是select的數據列只用從索引中就能夠取得,不必讀取數據行,換句話說查詢列要被所建的索引覆蓋。

當能通過讀取索引就可以得到想要的數據,那就不需要讀取行了。一個索引包含了(或覆蓋了)滿足查詢結果的數據就叫做覆蓋索引。

它包括在查詢裏的Select、Join和Where子句用到的所有列(即建索引的字段正好是覆蓋查詢條件中所涉及的字段,也即,索引包含了查詢正在查找的數據)

覆蓋索引:根據關鍵字就能夠直接獲取查詢所需要的所有數據,不必要讀取數據行的數據,所有數據是指where、select從句、order by、 group by從句的值

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

如果索引字段是字符串那麼查詢條件必須加引號,但是如果查詢的列都在索引中,即使不滿足走索引的條件,此時也會使用索引。示例中order_code=666666,是數字類型,沒有加引號,按說是不走索引的,但是select * 而test表只有兩個字段,id和order_code而這兩個字段都創建了索引,這種情況也會走索引

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

12. order by

mysql有兩種排序方式:

通過有序索引順序掃描直接返回有序數據,通過explain分析顯示Using Index,不需要額外的排序,操作效率比較高。

通過對返回數據進行排序,也就是Filesort排序,所有不是通過索引直接返回排序結果的都叫Filesort排序。Filesort是通過相應的排序算法將取得的數據在sort_buffer_size系統變量設置的內存排序中進行排序,如果內存裝載不下,就會將磁盤上的數據進行分塊,再對各個數據塊進行排序,然後將各個塊合併成有序的結果集

order by 使用索引的嚴格要求:

索引的順序和order by子句的順序完全一致

索引中所有列的方向(升續、降續)和order by 子句完全一致

當多表連接查詢時order by中的字段必須在關聯表中的第一張表中

如果有 order by 的場景,請注意利用索引的有序性。order by 最後的字段是組合 索引的一部分,並且放在索引組合順序的最後,避免出現 file_sort 的情況,影響查詢性能。

正例:where a=? and b=? order by c; 索引:a_b_c

反例:索引中有範圍查找,那麼索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 無法排序

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

order by如果根據多個值進行排序,那麼排序方式必須保持一致,要麼同時升續,要麼同時降續,排序方式不一致不走索引

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

 

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

13. group by

默認情況下,group by column;有兩個作用,第一個就是根據指定的列進行分組,第二作用group by 不但分組,而且還爲分組中的數據按照列來排序,如果分組的字段創建了索引,那麼排序也沒什麼畢竟排序走索引也很快

但是如果group by指定的列沒有走索引,而我們通常情況下只對分組中的數據進行統計,例如對分組中的數據求和,通常順序無關緊要,此時就要關閉group by 的排序功能,使用Order By NULL;來關閉排序功能,避免排序對性能的影響。

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

14. 分頁limit

分頁查詢一般會全表掃描,優化的目的應儘可能減少掃描;

第一種思路:在索引上完成排序分頁的操作,最後根據主鍵關聯回原表查詢原來所需要的其他列。這種思路是使用覆蓋索引儘快定位出需要的記錄的id,覆蓋索引效率高些

第二中思路:limit m,n 轉換爲 n

之前分頁查詢是傳pageNo頁碼, pageSize分頁數量,

當前頁的最後一行對應的id即last_row_id,以及pageSize,這樣先根據條件過濾掉last_row_id之前的數據,然後再去n挑記錄,此種方式只能用於排序字段不重複唯一的列,如果用於重複的列,那麼分頁數據將不準確

當只一行數據使用limit 1

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

多表連接查詢連接條件(也就是外鍵必須創建索引,否則大數據查詢直接卡死)

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

如果全表掃描比使用索引快,就不會使用索引,比如 表的數量很少或者滿足條件的數據量比較大也不走索引, 查詢數據庫記錄時,查詢到的條目數儘量小,當通過索引獲取到的數據庫記錄> 數據庫總記錄的1/3時,SQL將有可能直接全表掃描,索引就失去了應有的作用。

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

where條件將能過濾掉多的條件寫在前面,過濾掉少部分的數據寫在後面,這樣先排除一大部分不滿足條件的數據,然後剩下一小部分數據,然後再從中找出滿足條件的記錄

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

數據類型不匹配是不會走索引,例如對字符串類型創建索引,where條件值卻沒有使用引號,就不走索引,join語句中join條件字段類型不一致的時候MYSQL無法使用索引

15 in和exists

查詢所有下過訂單的用戶(tbl_user 500w條數據,tbl_order 3條數據), 有兩種方式,一種使用in另一種使用exists,但是兩者效率相差很大

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

in的僞代碼:

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

SELECT * FROM A WHERE id IN (SELECT a_id FROM B) 當A表中的數據量遠大於B表中的數據量時使用in

查詢用戶id大於10的用戶的訂單信息

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

å¦æ­¤è¯¦ç»çSQLä¼åæç¨ï¼æ¯ä½ éè¦çåï¼

從以上邏輯可以看到exists每次循環一下外表都要查詢一下內表,即使外表數量很少,但是每循環一次外表都要去查詢一個大表,這樣的效率是不高的,除非子查詢走索引,最好走覆蓋索引。

SELECT * FROM B o WHERE exists(SELECT 1 FROM A WHERE o.user_id > 10) 網上說當B表的數據量小A表的數據量很大時用exists。上面那個例子沒有測試出來,實際優化時in和exists具體那個好根據具體執行情況來選擇,也不能一味的就說某種情況下用這個就比那個快

16. 強制索引

當查詢時不走索引時可以通過force index 強制mysql使用指定的索引,一般情況下如果mysql不走索引它是認爲全表掃描會更快些,可以通過強制走索引看一下查詢時間,如果強制索引效果更好,查詢速度更快就使用強制索引,如果強制索引沒有明顯效果就沒必要使用了。

mysql強制使用索引:force index(索引名或者主鍵PRI)

-- 強制使用主鍵索引

select * from table force index(PRI);

-- 強制使用索引"idx_xxx"

select * from table force index(idx_xxx);

-- 強制使用索引"PRI"和"idx_xxx"

select * from table force index(PRI,idx_xxx);

mysql禁止某個索引:ignore index(索引名或者主鍵PRI)

-- 禁止使用主鍵

select * from table ignore index(PRI)

-- 禁止使用索引"idx_xxx"

select * from table ignore index(idx_xxx);

-- 禁止使用索引"PRI","idx_xxx"

select * from table ignore index(PRI,idx_xxx);

六、其它優化

 

禁止使用select *,需要什麼字段就去取哪些字段

超過三個表禁止join。需要join的字段數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段需要有索引。說明:即使雙表 join 也要注意表索引、SQL 性能。儘可能避免複雜的join和子查詢。儘量使用左右連接,少使用內連接。永遠小結果集驅動大結果集(這點mysql會自動優化)

不要使用count(列名)或 count(常量)來替代 count(),count()是SQL92定義的標準統計行數的語法,跟數據庫無關,跟 NULL和非NULL無關。說明:count(*)會統計值爲NULL 的行,而count(列名)不會統計此列爲NULL值的行。

不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。

禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。避免使用存儲過程、觸發器。

使用表連接來優化子查詢。儘量避免使用子查詢,建議將子查詢轉換成關聯查詢,子查詢的效率並沒有連接join查詢快(並不絕對),連接查詢之所以更有效率一些,是因爲mysql不需要再內存中創建臨時表來完成這個邏輯上需要兩個步驟的查詢工作。對於多表連接,如果連接條件創建索引效率更高。

對於列類型是字符串,值必須使用單引號括住,如果沒有引號引住就不走索引, 結論:字符串必須使用‘’引住

拒絕大SQL,拆分成小SQL

優先優化高併發的SQL,而不是執行頻率低某些“大”SQL

在所有的存儲過程和觸發器的開始處設置 SET NOCOUNT ON ,在結束時設置 SET NOCOUNT OFF 。無需在執行存儲過程和觸發器的每個語句後向客戶端發送 DONE_IN_PROC 消息。

減少 IO 次數

IO永遠是數據庫最容易瓶頸的地方,這是由數據庫的職責所決定的,大部分數據庫操作中超過90%的時間都是 IO 操作所佔用的,減少 IO 次數是 SQL 優化中需要第一優先考慮,當然,也是收效最明顯的優化手段。

降低 CPU 計算

除了 IO 瓶頸之外,SQL優化中需要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操作基本上都是 CPU 處理內存中的數據比較運算)。當我們的 IO 優化做到一定階段之後,降低 CPU 計算也就成爲了我們 SQL 優化的重要目標

大表的數據修改最好要分批處理,例如1000萬行的記錄刪除更新 100萬行記錄,可以一次只刪除更新5000行記錄,暫停幾秒,然後再執行剩下的數據。

如何修改大表的表結構:

先在從服務器上修改,然後將從服務器改爲主服務器,然後再從主服務器上修改,然後再切換回來,此種方式需要切換主從,有一定的風險。

在主服務器上創建一張新表,將老表的數據遷移到新表,創建一個觸發器,將老表的新數據同步到新表中,對老表加一個排它鎖,重新命名新表和老表,然後刪除掉老表(整個操作過程比較複雜,可以通過工具來實現)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章