1. 程式人生 > >讓天下沒有難用的資料庫 » 淺談mysql的子查詢

讓天下沒有難用的資料庫 » 淺談mysql的子查詢

mysql的子查詢的優化一直不是很友好,一直有受業界批評比較多,也是我在sql優化中遇到過最多的問題之一,你可以點選這裡 ,這裡來獲得一些資訊,mysql在處理子查詢的時候,會將子查詢改寫,通常情況下,我們希望由內到外,也就是先完成子查詢的結果,然後在用子查詢來驅動外查詢的表,完成查詢,但是恰恰相反,子查詢不會先被執行;今天希望通過介紹一些實際的案例來加深對mysql子查詢的理解:

案例:使用者反饋資料庫響應較慢,許多業務動更新被卡住;登入到資料庫中觀察,發現長時間執行的sql;

| 10437 | usr0321t9m9 | 10.242.232.50:51201 | oms | Execute | 1179

 | Sending

Sql為:
select tradedto0_.* from a1 tradedto0_ where tradedto0_.tradestatus='1'
and (tradedto0_.tradeoid in (select orderdto1_.tradeoid from a2 orderdto1_ where
orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')) and tradedto0_.undefine4='1'
and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;


2.其他表的更新被阻塞:
update a1 set tradesign='DAB67634-795C-4EAC-B4A0-78F0D531D62F',
markColor=' #CD5555', memotime='2012-09- 22', markPerson='??' where tradeoid in ('gy2012092204495100032') ;

為了儘快恢復應用,將其長時間執行的sql kill掉後,應用恢復正常;
3.分析執行計劃:
[email protected] :explain select tradedto0_.* from a1 tradedto0_ where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid in (select orderdto1_.tradeoid
from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
+----+--------------------+------------+------+---------------+------+---------+------+-------+-----
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+------+---------------+------+---------+------+-------+-----
| 1 | PRIMARY | tradedto0_ | ALL | NULL | NULL | NULL | NULL | 27454 | Using where; Using filesort |
| 2 | DEPENDENT SUBQUERY | orderdto1_ | ALL | NULL | NULL | NULL | NULL | 40998 | Using where |
+----+--------------------+------------+------+---------------+------+---------+------+-------+-----

從執行計劃上,我們開始一步一步地進行優化:
首先,我們看看執行計劃的第二行,也就是子查詢的那部分,orderdto1_進行了全表的掃描,我們看看能不能新增適當的索引:
A.使用覆蓋索引:
[email protected]:alter table a2 add index ind_a2(proname,procode,tradeoid);
ERROR 1071 (42000): Specified key was too long; max key length is 1000 bytes

新增組合索引超過了最大key length限制:
B.檢視該表的欄位定義:

 [email protected]DESC  a2 ;
+---------------------+---------------+------+-----+---------+-------+
| FIELD               | TYPE          | NULL | KEY | DEFAULT | Extra |
+---------------------+---------------+------+-----+---------+-------+
| OID                 | VARCHAR(50)   | NO   | PRI | NULL    |       |
| TRADEOID            | VARCHAR(50)   | YES  |     | NULL    |       |
| PROCODE             | VARCHAR(50)   | YES  |     | NULL    |       |
| PRONAME             | VARCHAR(1000) | YES  |     | NULL    |       |
| SPCTNCODE           | VARCHAR(200)  | YES  |     | NULL    |       |

C.查看錶欄位的平均長度:

[email protected]SELECT MAX(LENGTH(PRONAME)),avg(LENGTH(PRONAME)) FROM a2;
+----------------------+----------------------+
| MAX(LENGTH(PRONAME)) | avg(LENGTH(PRONAME)) |
+----------------------+----------------------+
|    95              |       24.5588 |

D.縮小欄位長度

ALTER TABLE MODIFY COLUMN PRONAME VARCHAR(156);

再進行執行計劃分析:
[email protected] :explain select tradedto0_.* from a1 tradedto0_ where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid in (select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
+----+--------------------+------------+-------+-----------------+----------------------+---------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+-------+-----------------+----------------------+---------+
| 1 | PRIMARY | tradedto0_ | ref | ind_tradestatus | ind_tradestatus | 345 | const,const,const,const | 8962 | Using where; Using filesort |
| 2 | DEPENDENT SUBQUERY | orderdto1_ | index | NULL | ind_a2 | 777 | NULL | 41005 | Using where; Using index |
+----+--------------------+------------+-------+-----------------+----------------------+---------+

發現效能還是上不去,關鍵在兩個表掃描的行數並沒有減小(8962*41005),上面新增的索引沒有太大的效果,現在檢視t表的執行結果:
[email protected] :select orderdto1_.tradeoid from t orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%';
Empty set (0.05 sec)

結果集為空,所以需要將t表的結果集做作為驅動表;
4.通過上面測試驗證,普通的mysql子查詢寫法效能上是很差的,為mysql的子查詢天然的弱點,需要將sql進行改寫為關聯的寫法:
select tradedto0_.* from a1 tradedto0_ ,(select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')t2 where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid=t2.tradeoid ) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
5.檢視執行計劃:
[email protected] :explain select tradedto0_.* from a1 tradedto0_ ,(select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')t2 where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid=t2.tradeoid ) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
+----+-------------+------------+-------+---------------+----------------------+---------+------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+---------------+----------------------+---------+------+
| 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
| 2 | DERIVED | orderdto1_ | index | NULL | ind_a2 | 777 | NULL | 41005 | Using where; Using index |
+----+-------------+------------+-------+---------------+----------------------+---------+------+

6.執行時間:

[email protected] :select tradedto0_.* from a1 tradedto0_ ,(select orderdto1_.tradeoid from a2 orderdto1_ where orderdto1_.proname like '%??%' or orderdto1_.procode like '%??%')t2 where tradedto0_.tradestatus='1' and (tradedto0_.tradeoid=t2.tradeoid ) and tradedto0_.undefine4='1' and tradedto0_.invoicetype='1' and tradedto0_.tradestep='0' and (tradedto0_.orderCompany like '0002%') order by tradedto0_.tradesign ASC, tradedto0_.makertime desc limit 15;
Empty set (0.03 sec)

縮短到了毫秒;

總結:
1. mysql子查詢在執行計劃上有著明顯的弱點,需要將子查詢進行改寫
可以參考:
a. 生產庫中遇到mysql的子查詢:http://hidba.org/?p=412
b. 內建的builtin InnoDB,子查詢阻塞更新:http://hidba.org/?p=456
2. 在表結構設計上,不要隨便使用varchar(N)的大欄位,導致無法使用索引
可以參考:
a. JDBC記憶體管理—varchar2(4000)的影響:http://hidba.org/?p=31
b. innodb中大欄位的限制:http://hidba.org/?p=144
c. innodb使用大欄位text,blob的一些優化建議: http://hidba.org/?p=551