幾個影響sql語句性能的例子
-
案例一:假設你現在維護了一個交易系統,其中交易記錄表tradelog包含交易流水號(tradeid)、交易員id(operator)、交易時間(t_modified)等字段。為了便於描述,我們先忽略其他字段。這個表的建表語句如下:
mysql> CREATE TABLE `tradelog` ( `id` int(11) NOT NULL, `tradeid` varchar(32) DEFAULT NULL, `operator` int(11) DEFAULT NULL, `t_modified` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `tradeid` (`tradeid`), KEY `t_modified` (`t_modified`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;假設,現在已經記錄了從2016年初到2018年底的所有數據,運營部門有一個需求是,要統計發生在所有年份中7月份的交易記錄總數。這個邏輯看上去並不復雜,你的SQL語句可能會這么寫:
mysql> select count(*) from tradelog where month(t_modified)=7;由於t_modified字段上有索引,於是你就很放心地在生產庫中執行了這條語句,但卻發現執行了特別久,才返回了結果。
如果對字段做了函數計算,就用不上索引了,這是MySQL的規定。
可以再追問一句為什么?為什么條件是where t_modified='2018-7-1’的時候可以用上索引,而改成where month(t_modified)=7的時就不行了?
對索引字段做函數操作,可能會破壞索引值的有序性,因此優化器就決定放棄走樹搜
索功能。需要注意的是,優化器並不是要放棄使用這個索引。在這個例子里,放棄了樹搜索功能,優化器可以選擇遍歷主鍵索引,也可以選擇遍歷索引t_modified,優化器對比索引大小后發現,索引t_modified更小,遍歷這個索引比遍歷主鍵索引來得更快。因此最終還是會選擇索引t_modified。

Extra字段的Using index,表示的是使用了覆蓋索引。也就是說,由於在t_modified字段加了month()函數操作,導致了全索引掃描。
按照下面這個寫法,優化器就能按照我們預期的,用上t_modified索引的快速定位能力了。
mysql> select count(*) from tradelog where
‑> (t_modified >= '2016‑7‑1' and t_modified<'2016‑8‑1') or
‑> (t_modified >= '2017‑7‑1' and t_modified<'2017‑8‑1') or
‑> (t_modified >= '2018‑7‑1' and t_modified<'2018‑8‑1');
-
案例二:隱式類型轉換
我們一起看一下這條SQL語句:
mysql> select * from tradelog where tradeid=110717;交易編號tradeid這個字段上,本來就有索引,但是explain的結果卻顯示,這條語句需要走全表掃描。你可能也發現了,tradeid的字段類型是varchar(32),而輸入的參數卻是整型,所以需要做類型轉換。
-
數據類型轉換的規則是什么
- 在MySQL中,字符串和數字做比較的話,是將字符串轉換成數字。
-
為什么有數據類型轉換,就要走全索引掃描。
-
以上sql語句相當於
mysql> select * from tradelog where CAST(tradid AS signed int) = 110717; -
也就是說,這條語句觸發了我們上面說到的規則:對索引字段做函數操作,優化器會放棄走樹搜索功能。
-
-
同樣的例子比如:,字符集utf8mb4是utf8的超集,所以當這兩個類型的字
符串在做比較的時候,MySQL內部的操作是,先把utf8字符串轉成utf8mb4字符集,再做比較。-
這個設定很好理解,utf8mb4是utf8的超集。類似地,在程序設計語言里面,做自動類型轉換的時候,為了避免數據在轉換過程中由於截斷導致數據錯誤,也都
是“按數據長度增加的方向”進行轉換的。 -
也就是說,實際上這個語句等同於下面這個寫法:
select * from trade_detail where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value; -
CONVERT()函數,在這里的意思是把輸入的字符串轉成utf8mb4字符集。這就再次觸發了我們上面說到的原則:對索引字段做函數操作,優化器會放棄走樹搜索功能。
-
-
-
小結:
- 對於給不同年份中的月份加索引,我們要指定年份才能走索引,因為不同年份可能會有相同的月份,mysql優化器不知如何選擇,不會走指定字段索引。
- 保持字段類型一致,比如字段和數字,mysql會自動的調用函數將字符串轉化為數字,會走全表掃描。
- 保持字符集一致,charset=utf8,如果跨表查詢,兩張表的字符集應該相同,否則mysql內部還是會調用函數,因此會走全表掃描。
- 在每次代碼升級的時候我們都應該調用explain來看一下sql語句是否按索引來走的。
- 調用函數就不會走我們已經建好的索引字段。
查一行的sql語句執行很慢
-
第一類 查詢長時間不返回
-
mysql> select * from t where id=1; -
執行show processlist命令,查看當前語句處於什么狀態。
-
等釋放
-

- 可以看到這個語句目前的狀態是waiting for table metadata lock,表示有一個線程正在表t上請求或者持有MDL寫鎖,把select語句堵住了
-
等行鎖
-
mysql> select * from t where id=1 lock in share mode; -
由於訪問id=1這個記錄時要加讀鎖,如果這時候已經有一個事務在這行記錄上持有一個寫鎖,我們的select語句就會被堵住。
-

-
顯然,session A啟動了事務,占有寫鎖,還不提交,是導致session B被堵住的原因。
-
-
-
第二類:查詢慢
-
mysql> select * from t where c=50000 limit 1;- 由於字段c上沒有索引,這個語句只能走id主鍵順序掃描,因此需要掃描5萬行。
-
mysql> select * from t where id=1; # 假如這條語句慢查詢日志返回的是800毫秒select * from t where id=1 lock in share mode # 這條語句返回的查詢時間是0.2毫秒
為什么結果相差會這么大呢?
-

-
session A先用start transaction with consistent snapshot命令啟動了一個事務,之
后session B才開始執行update 語句。 -
session B執行完100萬次update語句后,id=1這一行處於什么狀態呢?
-

-
session B更新完100萬次,生成了100萬個回滾日志(undo log)。
-
帶lock in share mode的SQL語句,是當前讀,因此會直接讀到1000001這個結果,所以速度很快;而select * from t where id=1這個語句,是一致性讀,因此需要從1000001開始,依次執行undo log,執行了100萬次以后,才將1這個結果返回。所以返回查詢時間很長
-
-
