1. 程式人生 > >讓天下沒有難用的資料庫 » RDS最佳實踐(四)

讓天下沒有難用的資料庫 » RDS最佳實踐(四)

早上值班同事在旺旺群裡面貼了一條非常複雜的SQL,使用者從本地遷移到RDS Mysql出現嚴重效能下降,同樣的資料和表結構下,在本地的資料庫上只要不到1s的時間,但是在rds上好幾分鐘都沒響應。

碰到這類問題需要考慮以下一些因素:

a.資料庫的版本不同(不同的版本優化器策略不一樣,或者異構資料庫間的遷移:oracle–>mysql,sqlserver–>mysql),導致sql執行計劃不同,最後導致sql執行時間不同;

b.資料庫的配置不同(不同的記憶體配置,引數設定–query cache是否開啟),導致sql執行時間不同;

c.資料庫的資料量不同(系統遇到bug,生成了大量的垃圾資料),導致sql執行時間不同;

根據以上線索,使用者是剛剛從線下遷移到RDS的,所以資料量和表結構是相同的;

RDS配置為:2400M記憶體,1200IOPS,本地是膝上型電腦:4000M的記憶體,5600轉的膝上型電腦,所以資料庫配置來說區別並不大;

所以就剩下資料庫版本了,RDS的版本是Mysql 5.5,而使用者使用的資料庫版本是5.6,所以問題很可能出現在這裡,mysql 5.6和5.5在優化器上最大的改進就是對子查詢的優化改進:

a.5.0、5.1、5.5對子查詢處理:不會將子查詢的結果集計算出來用作與其他表做join,所以很有可能outer 表每掃描一條資料,子查詢都會被重新執行一遍,這樣就導致效能下降;所以在5.5之前的版本中,處理子查詢的問題通常採用sql改寫:將子查詢改寫為join的方式;

b.5.6對子查詢處理:將子查詢的結果集cache到臨時表裡,臨時表索引的主要目的是用來移除重複記錄,並且隨後也可能用於在做join時做查詢使用,這種技術在5.6中叫做Subquery Materialize.物化的子查詢可以看到select_type欄位為SUBQUERY,而在MySQL5.5裡為DEPENDENT SUBQUERY

5.5的執行計劃:
mysql> explain select count(*) from test_pic as bi where bi.time in (select MAX(time) from test_pic where PIC_TYPE=1 GROUP BY BUILDING_ID) GROUP BY bi.BUILDING_ID;
+—-+——————–+————–+——-+—————+—————+———+——+——-+————-+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+——————–+————–+——-+—————+—————+———+——+——-+————-+
| 1 | PRIMARY | bi | index | NULL | IDX_BPIC_0001 | 7 | NULL | 50226 | Using where |
| 2 | DEPENDENT SUBQUERY | test_pic | index | NULL | IDX_BPIC_0001 | 7 | NULL | 43 | Using where |
+—-+——————–+————–+——-+—————+—————+———+——+——-+————-+
2 rows in set (0.00 sec)

explain extended結果可以看到優化器的詳細執行步驟採用exists的方式將外表與子查詢的表關聯起來,這樣會大大增加子查詢的執行頻率:

  • select count(0) AS `count(*)` from `test_db`.`test_pic` `bi` where <in_optimizer>(`test_db`.`bi`.`time`,<exists>(select max(`test_db`.`test_pic`.`time`) from `test_db`.`test_pic` where (`test_db`.`test_pic`.`PIC_TYPE` = 1) group by `test_db`.`test_pic`.`BUILDING_ID` having (<cache>(`test_db`.`bi`.`time`) = <ref_null_helper>(max(`test_db`.`test_pic`.`time`))))) group by `test_db`.`bi`.`BUILDING_ID`

5.6的執行計劃:

mysql> explain select count(*) from test_pic as bi where bi.time in (select MAX(time) from test_pic where PIC_TYPE = 1 GROUP BY BUILDING_ID) GROUP BY bi.BUILDING_ID;
+—-+————-+————–+——-+—————+—————+———+——+——-+————-+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+————–+——-+—————+—————+———+——+——-+————-+
| 1 | PRIMARY | bi | index | IDX_BPIC_0001 | IDX_BPIC_0001 | 7 | NULL | 46595 | Using where |
| 2 | SUBQUERY | test_pic | index | IDX_BPIC_0001 | IDX_BPIC_0001 | 7 | NULL | 46595 | Using where |
+—-+————-+————–+——-+—————+—————+———+——+——-+————-+
2 rows in set (0.00 sec)

explain extended結果可以看到優化器將子查詢的結果集計算出來存放到一張臨時表中,然後在與表做join:

/* select#1 */
select count(0) AS `count(*)` from `test56`.`test_pic` `bi` where <in_optimizer>(`test56`.`bi`.`time`,`test56`.`bi`.`time` in (
<materialize>
(/* select#2 */ select max(`test56`.`test_pic`.`time`) from `test56`.`test_pic`
where (`test56`.`test_pic`.`PIC_TYPE` = 1) group by `test56`.`test_pic`.`BUILDING_ID` having 1 ),
<primary_index_lookup>(`test56`.`bi`.`time` in <temporary table> on <auto_key>
where ((`test56`.`bi`.`time` = `materialized-subquery`.`MAX(time)`)))))
group by `test56`.`bi`.`BUILDING_ID`

所以針對該問題的解決方案就是將子查詢改寫為關聯:

mysql> explain select count(*) from test_pic as bi , (select MAX(time) as time from test_pic where PIC_TYPE=1 GROUP BY BUILDING_ID) b where bi.time = b.time GROUP BY bi.BUILDING_ID;
+—-+————-+————–+——-+—————+—————+———+——+——-+———————————+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+————–+——-+—————+—————+———+——+——-+———————————+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | Using temporary; Using filesort |
| 1 | PRIMARY | bi | ALL | NULL | NULL | NULL | NULL | 50226 | Using where; Using join buffer |
| 2 | DERIVED | test_pic | index | NULL | IDX_BPIC_0001 | 7 | NULL | 50226 | Using where |
+—-+————-+————–+——-+—————+—————+———+——+——-+———————————+
3 rows in set (0.06 sec)

explain extended的詳細執行結果:

select count(0) AS `count(*)` from `test_db`.`test_pic` `bi` join (select max(`test_db`.`test_pic`.`time`) AS `time` from `test_db`.`test_pic` where (`test_db`.`test_pic`.`PIC_TYPE` = 1) group by `test_db`.`test_pic`.`BUILDING_ID`) `b` where (`test_db`.`bi`.`time` = `b`.`time`) group by `test_db`.`bi`.`BUILDING_ID`

SQL很快就執行得到結果;RDS很快將會推出5.6的版本,屆時可以選擇購買5.6的例項,同樣也可以將5.5,5.1的例項升級到5.6,解決讓人詬病的子查詢效能問題。

PS.最佳實踐:在oracle遷移到mysql的時候,請選用Mysql 5.6的版本,這樣就可以避免麻煩的子查詢改寫了。