0、序言
本文我們來談談項目中常用的 20 條 MySQL 優化方法,效率至少提高 3倍!
具體如下:
1、使⽤ EXPLAIN 分析 SQL 語句是否合理
使⽤ EXPLAIN 判斷 SQL 語句是否合理使用索引,盡量避免 extra 列出現:Using File Sort、Using Temporary 等。
2、必須被索引
重要SQL必須被索引:update、delete 的 where 條件列、order by、group by、distinct 字段、多表 join 字段。
3、聯合索引
對於聯合索引來說,如果存在范圍查詢,比如between、>、<等條件時,會造成后面的索引字段失效。
對於聯合索引來說,要遵守最左前綴法則:舉列來說索引含有字段 id、name、school
,可以直接用 id 字段,也可以 id、name 這樣的順序,但是 name; school 都無法使用這個索引。所以在創建聯合索引的時候一定要注意索引字段順序,常用的查詢字段放在最前面。
4、強制索引
必要時可以使用 force index
來強制查詢走某個索引: 有的時候MySQL優化器采取它認為合適的索引來檢索 SQL 語句,但是可能它所采用的索引並不是我們想要的。這時就可以采用 forceindex 來強制優化器使用我們制定的索引。
5、日期時間類型
對於非標准的日期字段,例如字符串的日期字段,進行分區裁剪查詢時會導致無法識辨,依舊走全表掃描。
盡管 TIMESTAMEP 存儲空間只需要 datetime 的一半,然而由於類型 TIMESTAMP 存在性能問題,建議你還是盡可能使用類型 DATETIME。(TIMESTAMP 日期存儲的上限為 2038-01-19 03:14:07,業務用 TIMESTAMP 存在風險;)
6、禁止使用 SELECT *
SELECT 只獲取必要的字段,禁止使用 SELECT *。這樣能減少不必要的消耗(CPU、IO、內存、網絡帶寬),增加使用覆蓋索引的可能性;當表結構發生改變時,表結構變更對前端程序基本無影響。
7、避免出現某些字段
SQL 中避免出現 now()
、rand()
、sysdate()
、current_user()
等不確定結果的函數。在語句級復制場景下,引起主從數據不一致;不確定值的函數,產生的 SQL 語句無法使用 QUERY CACHE。
8、where 子句
避免在 where 子句中對字段進行 null 值
判斷:對於 null 的判斷會導致引擎放棄使用索引而進行全表掃描。
避免在where子句中對字段進行表達式操作:因為對字段就行了算術運算,這會造成引擎放棄使用索引。
9、like
禁止使用 % 前導查詢,例如:like “%abc”,⽆法利⽤到索引。
在日常中你會發現全模糊匹配的查詢,由於 MySQL 的索引是 B+ 樹結構,所以當查詢條件為全模糊時,例如 %AB%
、%AB
,索引無法使用,這時需要通過添加其他選擇度高的列或者條件作為一種補充,從而加快查詢速度。僅AB%
形式的可以避免通配符引起索引屏蔽。
10、用 IN 代替 OR
OR 兩邊的字段中,如果有一個不是索引字段,而其它條件也不是索引字段,會造成該查詢不走索引的情況。很多時候都會使用 IN 進行替代,或者使用 union all 或者是 union(必要的時候)的方式來代替“or”也會得到更好的效果。但 SQL 語句中 IN 包含的值不宜過多,應少於 1000 個。過多會使隨機 IO 增大,影響性能。
使用 IN 是因為 MySQL 對其做了相應的優化,即將 IN 中的常量全部存儲在一個數組里面,而且這個數組是排好序的。但是如果數值較多,產生的消耗比較大。
再例如:
select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了;再或者使用連接來替換。
11、禁止使⽤負向查詢
禁止使⽤負向查詢,例如:not in、!=、<>、not like。
12、范圍查詢
在對字符串類型的索引進行大於運算時,會導致全表掃描。所以應改為區間between區間范圍運算。
13、order by/group by
另外 order by/group by
的 SQL 涉及排序,盡量在索引中包含排序字段,並讓排序字段的排序順序與索引列中的順序相同,這樣可以避免排序或減少排序次數。如果排序字段沒有用到索引,就盡量少排序。
14、禁止使用 order by rand()
order by rand() 會為表增加幾個偽列,然后用 rand() 函數為每一行數據計算 rand() 值,最后基於該行排序,這通常都會生成磁盤上的臨時表,因此效率非常低。建議先使用 rand() 函數獲得隨機的主鍵值,然后通過主鍵獲取數據。
15、盡量用union all代替union
union 和 union all 的差異主要是前者需要將結果集合並后再進行唯一性過濾操作,這就會涉及到排序,增加大量的CPU運算,加大資源消耗及延遲。當然,union all 的前提條件是兩個結果集沒有重復數據。
16、減少與數據庫交互
盡量采用批量 SQL 語句,減少與數據庫交互次數。
獲取⼤量數據時,建議分批次獲取數據,每次獲取數據少於 5000 條,結果集應⼩於 1M。
17、復雜查詢還是簡單查詢?
不要用一個SQL解決所有事情,可以分步驟做,省時、易理解、優化。且 MySQL 也十分擅長處理短而簡單的 SQL,總體耗時會更短,而且也不會產生臃腫的 SQL,讓人難以理解和優化。
拆分復雜 SQL 為多個 小SQL,避免⼤事務。簡單的 SQL 容易使用到 MySQL 的 QUERY CACHE;減少鎖表時間特別是 MyISAM;可以使用多核 CPU。
18、刪除全表數據
delete from table_name;
會產生大量 undo 和 redo 日志,執行時間很長,可采用 TRUNCATE TABLE tablename;
19、字符集問題
col_utf8mb4 = col_utf8
關聯類型都是 varchar ,但字符集不同,無法使用索引。使用過程中要特別注意。
20、count 優化
這也是一個被面試中經常會問到的問題,對於下面的四條 SELECT 語句:
select count(*) from table … ;
select count(1) from table … ;
select count(primary key) from table … ;
select count(index key) from table …;
哪一條的執行效率最高呢?這個問題需要具體問題具體分析,不能一概而論。這里舉 SELECT count(1) 這條 SQL 為例。
優化前和優化后,執行效率相差2倍。就添加了一個索引。
優化思路 : 是選擇索引 key_len
最短的二級索引效率高,不要使用全表掃描(PK 聚族索引會全表掃描),因為索引 key_len
越短,讀取頁面越少,進而 IO_COST
越小。
小結
- 大量的更新/刪除操作需要控制頻度,例如:每秒操作2000行以下
- 使用
prepared statement
和綁定變量,可以提升性能並避免 SQL 注入 - 程序應有捕獲 SQL 異常的處理機制,必要時通過 rollback 顯示回滾
- 盡量少使用 distinct、order by、group by、union 等 SQL,排序需求可以放到前端(分頁的就不方便交給前端排序)。
- 大事務或者長查詢的需求根據業務特點拆分
- 杜絕程序中在處理事務時夾雜 RPC,會造成資源長時間不釋放。有很多鎖超時、並發數上漲都是由於事務中有 RPC 造成的。
- 關注軟件本身的優化同時,也需要關注硬件的性能指標和優化,以及硬件的發展方向。MySQL 屬於 IO 密集型的應用,對存儲硬件的 IO 性能要求比較高,在高並發的場景中,建議使用 PCI-e。
重點總結一下:SQL 的執行過程->查詢優化器的工作原理->SQL 執行計划的解讀->MySQL 慢查詢日志和分析->SQL 常用的優化手段->SQL 編寫規范->深入實際業務對數據庫訪問進行優化。
參考:
- 《數據庫高效優化:架構、規范與SQL技巧》
- 《拉勾教育專欄:高性能MySQL實戰》