排序sql升級5.6變慢原因分析
背景:
某業務DB從5.5升級5.6後,同一sql執行時間由毫秒級飆升到了20秒,sql文字如下
摸索過程:
表上索引分佈
PRIMARY KEY (`id`),
KEY `idx_xx_id` (`xx_id`,`plan_type`,`user_id`),
……
KEY `idx_gmt_create` (`limit_type`,`xx_id`,`gmt_create`)
該sql選擇索引idx_gmt_create,因其符合最左字首策略,故排序沒有使用到filesort,其訪問路徑大致如下:
1 反向掃描idx_gmt_create葉子結點,搜尋(limit_type,xx_id)=(10,25773208367)的元組;
2 回表,驗證該元組對應的主鍵記錄是否滿足約束(plan_type,user_id)=(1 or 3, 133174222100),滿足則計數加1否則丟棄,繼續掃描下一個元組;
3 當計數達到10時,停止掃描,將對應的10條記錄返回給客戶端;
作為複合索引的引導列,limit_type欄位的選擇性驚人的低,這是查詢變慢的主要原因之一。
之所以要強調”之一”,是因為針對本例,只要其他欄位足夠給力,即便limit_type=10也能很快執行完畢,
檢視xx_id的分佈情況,也是比較畸形,該sql又很不幸的選擇了候選行最多的那個
如果xx_id=1,該sql最多隻需要比較2100條記錄即可返回,會很快執行完畢;
即便xx= 25773208367,如果能快速找出滿足非索引欄位約束的主鍵記錄,sql也會很快執行完畢,mysql是在驗證了海量的(limit_type,xx_id)=(10,25773208367)元組後,才湊齊10條同時滿足(plan_type,user_id)約束的主鍵記錄,據此我們可以反推出最早滿足所有約束條件的user_id,其查詢邏輯如下
隨便挑一個替換25773208367,比如1851362558,執行計劃沒有變,原本需要執行20多秒的sql卻在200毫秒內執行完畢。
而5.5版本的執行計劃為
雖然採用了filesort,但是經索引idx_xx_id過濾後的候選行非常少,故執行時間很短。
解決方案
1 修改sql,新增force index (idx_xx_id),此方案不夠靈活;
2 修改sql,將排序欄位從gmt_create改為gmt_modified,因無法採用索引排序5.6會選擇idx_xx_id,此方案可能造成返回資料有誤;
3 修改sql,將t.limit_TYPE in (10)改為t.limit_TYPE >9 and t.limit_TYPE <11,最佳化器會認為sql沒有滿足索引最左字首便不再使用idx_gmt_create,這招似乎有點賤,同時說明mysql最佳化器還不夠智慧。
結束語
5.6最佳化器做了大量改進,以本sql為例,讓其選擇了idx_gmt_create從而省去了filesort,之所以執行變慢了是因為表欄位資料分佈太不均勻,而本sql又湊巧滿足了各種坑,這算是一個意外吧。
某業務DB從5.5升級5.6後,同一sql執行時間由毫秒級飆升到了20秒,sql文字如下
點選(此處)摺疊或開啟
-
select * from big_table as t
-
where ( t.plan_TYPE = 1 or t.plan_TYPE=3 )
-
and t.limit_TYPE in (10)
-
and t.xx_ID = 25773208367
-
and t.USER_ID in (133174222100)
- plan by t.gmt_create desc , t.ID desc limit 1,10
摸索過程:
點選(此處)摺疊或開啟
-
檢視當前執行計劃
-
*************************** 1. row ***************************
-
id: 1
-
select_type: SIMPLE
-
table: t
-
type: range
-
possible_keys: idx_xx_id,idx_gmt_create
-
key: idx_gmt_create
-
key_len: 17
-
ref: NULL
-
rows: 6816016
-
Extra: Using index condition; Using where
- 1 row in set (0.00 sec)
表上索引分佈
PRIMARY KEY (`id`),
KEY `idx_xx_id` (`xx_id`,`plan_type`,`user_id`),
……
KEY `idx_gmt_create` (`limit_type`,`xx_id`,`gmt_create`)
該sql選擇索引idx_gmt_create,因其符合最左字首策略,故排序沒有使用到filesort,其訪問路徑大致如下:
1 反向掃描idx_gmt_create葉子結點,搜尋(limit_type,xx_id)=(10,25773208367)的元組;
2 回表,驗證該元組對應的主鍵記錄是否滿足約束(plan_type,user_id)=(1 or 3, 133174222100),滿足則計數加1否則丟棄,繼續掃描下一個元組;
3 當計數達到10時,停止掃描,將對應的10條記錄返回給客戶端;
點選(此處)摺疊或開啟
-
root@ 03:20:56>select limit_type,count(*) from big_table group by limit_type;
-
+-------------+----------+
-
| limit_type | count(*) |
-
+-------------+----------+
-
| NULL | 226865 |
-
| 9 | 463346 |
-
| 10 | 13353116 |
-
+-------------+----------+
- 3 rows in set (3.13 sec)
作為複合索引的引導列,limit_type欄位的選擇性驚人的低,這是查詢變慢的主要原因之一。
之所以要強調”之一”,是因為針對本例,只要其他欄位足夠給力,即便limit_type=10也能很快執行完畢,
檢視xx_id的分佈情況,也是比較畸形,該sql又很不幸的選擇了候選行最多的那個
點選(此處)摺疊或開啟
-
root@ 04:01:12>select xx_id,count(*) from big_table where limit_type =10 group by xx_id order by xx_id desc;
-
+-------------+----------+
-
| xx_id | count(*) |
-
+-------------+----------+
-
| 25773208367 | 13352433 |
-
| 25770261347 | 2 |
-
| 258809681 | 148 |
-
| 1 | 2100 |
-
+-------------+----------+
- 4 rows in set (5.79 sec)
如果xx_id=1,該sql最多隻需要比較2100條記錄即可返回,會很快執行完畢;
即便xx= 25773208367,如果能快速找出滿足非索引欄位約束的主鍵記錄,sql也會很快執行完畢,mysql是在驗證了海量的(limit_type,xx_id)=(10,25773208367)元組後,才湊齊10條同時滿足(plan_type,user_id)約束的主鍵記錄,據此我們可以反推出最早滿足所有約束條件的user_id,其查詢邏輯如下
點選(此處)摺疊或開啟
-
select user_id,count(*) from big_table t
-
where limit_type =10 and xx_id =25773208367
-
and ( t.plan_TYPE = 1 or t.plan_TYPE=3 )
-
group by user_id having count(*)>=10 plan by gmt_create desc limit 1,5;
-
執行結果
-
+------------+----------+
-
| user_id | count(*) |
-
+------------+----------+
-
| 1851362558 | 15 |
-
| 2118141658 | 11 |
-
| 2641244918 | 14 |
-
| 2448823838 | 17 |
-
| 16375410 | 32 |
-
+------------+----------+
- 5 rows in set (1 min 12.42 sec)
隨便挑一個替換25773208367,比如1851362558,執行計劃沒有變,原本需要執行20多秒的sql卻在200毫秒內執行完畢。
而5.5版本的執行計劃為
點選(此處)摺疊或開啟
-
*************************** 1. row ***************************
-
id: 1
-
select_type: SIMPLE
-
table: t
-
type: range
-
possible_keys: idx_xx_id
-
key: idx_xx_id
-
key_len: 18
-
ref: NULL
-
rows: 116
- Extra: Using where; Using filesort
雖然採用了filesort,但是經索引idx_xx_id過濾後的候選行非常少,故執行時間很短。
解決方案
1 修改sql,新增force index (idx_xx_id),此方案不夠靈活;
2 修改sql,將排序欄位從gmt_create改為gmt_modified,因無法採用索引排序5.6會選擇idx_xx_id,此方案可能造成返回資料有誤;
3 修改sql,將t.limit_TYPE in (10)改為t.limit_TYPE >9 and t.limit_TYPE <11,最佳化器會認為sql沒有滿足索引最左字首便不再使用idx_gmt_create,這招似乎有點賤,同時說明mysql最佳化器還不夠智慧。
結束語
5.6最佳化器做了大量改進,以本sql為例,讓其選擇了idx_gmt_create從而省去了filesort,之所以執行變慢了是因為表欄位資料分佈太不均勻,而本sql又湊巧滿足了各種坑,這算是一個意外吧。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15480802/viewspace-1815984/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MySQL升級會變慢?MySql
- Windows變慢原因分析及解決方法(轉)Windows
- MySQL:5.6 升級 5.7MySql
- mysql5.1升級5.6MySql
- 搬瓦工香港VPS主機變慢的原因分析
- 某條SQL突然變慢的問題分析SQL
- 資料庫——慢sql的原因資料庫SQL
- php5.6 升級至7.2.7PHP
- 如何使用效能分析工具定位SQL執行慢的原因?SQL
- POSTGRESQL 小版本升級失敗後的原因分析SQL
- Oracle9i 升級到10g 後,大多數SQL變慢的問題OracleSQL
- Weblogic 補丁升級慢Web
- gc伺服器慢的原因分析GC伺服器
- centos7 mysql5.6升級5.7CentOSMySql
- Windows下Mysql5.6升級到5.7WindowsMySql
- 解析智慧手機會變慢的真正原因
- SAP系統執行慢的原因分析
- 線上環境MySQL5.5升級至5.6MySql
- CentOS 5.6 升級 10.2.0.5 錯誤解決CentOS
- 升級win10系統後網速變慢的解決方法Win10
- ADSL網速變慢的原因如何解決
- 升級win10 1903系統開機速度變慢如何解決Win10
- SQL語句響應緩慢原因及優化SQL優化
- centos下升級php5.3到php5.6CentOSPHP
- Mysql慢SQL分析及優化MySql優化
- MySQL information_schema.columns表查詢慢原因分析MySqlORM
- SQL Server查詢速度慢原因及優化方法SQLServer優化
- 【資料庫】mysql5.6升級至5.7(物理方式)資料庫MySql
- MySQL 5.5升級5.6 單例項簡易實戰MySql單例
- 升級check SQL01SQL
- mysql 5.6 升級 到 5.7 的二進位制升級方法 另一個簡便思路.MySql
- 網站速度慢,網站速度慢,網站速度慢的幾種原因分析網站
- sql語句執行緩慢分析SQL
- 如何查詢和排除ADSL網速變慢的原因
- 給國產資料庫廠商提個建議:把慢SQL監控升級為關鍵SQL管理資料庫SQL
- SQL Server查詢速度慢的原因及優化方法SQLServer優化
- Centos5.6簡單方法升級php到php5.3.3CentOSPHP
- centos 5.6核心升級到 linux-3.2.23 報錯!CentOSLinux