<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>8 種常見 SQL 錯誤用法，你避開了嗎？</title>
        <description>1. SQL語句的執行順序：2. 1、LIMIT 語句3. 2、隱式轉換4. 3、關聯更新、刪除5. 4、混合排序6. 5、EXISTS語句7. 6、條件下推8. 7、提前縮小範圍9. 8、中間結果集下推10. 總結1. SQL語句的執行順序：

FROM 
ON 
JOIN 
WHERE 
GROUP BY 
HAVING 
SELECT 
DISTINCT 
ORDER BY 
LIMIT 

2. 1、LIMIT 語句

分頁查詢是最常用的場景之一，但也通常也是最容易出問題的地方。比如對於下面簡單的語句，一般 DBA 想到的辦法是在 type, name, create_time 欄位上加組合索引。這樣條件排序都能有效的利用到索引，效能迅速提升。

SELECT * 
FROM   operation 
WHERE  type = 'SQLStats' 
       AND name = 'SlowLog' 
ORDER  BY create_time 
LIMIT  1000, 10; 

好吧，可能90%以上的 DBA 解決該問題就到此為止。但當 LIMIT 子句變成 “LIMIT 1000000,10” 時，程式設計師仍然會抱怨：我只取10條記錄為什麼還是慢？

要知道資料庫也並不知道第1000000條記錄從什麼地方開始，即使有索引也需要從頭計算一次。出現這種效能問題，多數情形下是程式設計師偷懶了。

在前端資料瀏覽翻頁，或者大資料分批匯出等場景下，是可以將上一頁的最大值當成引數作為查詢條件的。SQL 重新設計如下：

SELECT   * 
FROM     operation 
WHERE    type = 'SQLStats' 
AND      name = 'SlowLog' 
AND      create_time &gt; '2017-03-16 14:00:00' 
ORDER BY create_time limit 10;

在新設計下查詢時間基本固定，不會隨著資料量的增長而發生變化。

3. 2、隱式轉換

SQL語句中查詢變數和欄位定義型別不匹配是另一個常見的錯誤。比如下面的語句：

mysql&gt; explain extended SELECT * 
     &gt; FROM   my_balance b 
     &gt; WHERE  b.bpn = 14000000123 
     &gt;       AND b.isverified IS NULL ;
mysql&gt; show warnings;
| Warning | 1739 | Cannot use ref access on index 'bpn' due to type or collation conversion on field 'bpn'

其中欄位 bpn 的定義為 varchar(20)，MySQL 的策略是將字串轉換為數字之後再比較。函式作用於表字段，索引失效。

上述情況可能是應用程式框架自動填入的引數，而不是程式設計師的原意。現在應用框架很多很繁雜，使用方便的同時也小心它可能給自己挖坑。

4. 3、關聯更新、刪除

雖然 MySQL5.6 引入了物化特性，但需要特別注意它目前僅僅針對查詢語句的最佳化。對於更新或刪除需要手工重寫成 JOIN。

比如下面 UPDATE 語句，MySQL 實際執行的是迴圈/巢狀子查詢（DEPENDENT SUBQUERY)，其執行時間可想而知。

UPDATE operation o 
SET    status = 'applying' 
WHERE  o.id IN (SELECT id 
                FROM   (SELECT o.id, 
                               o.status 
                        FROM   operation o 
                        WHERE  o.group = 123 
                               AND o.status NOT IN ( 'done' ) 
                        ORDER  BY o.parent, 
                                  o.id 
                        LIMIT  1) t); 

執行計劃：

+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
| id | select_type        | table | type  | possible_keys | key     | key_len | ref   | rows | Extra                                               |
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
| 1  | PRIMARY            | o     | index |               | PRIMARY | 8       |       | 24   | Using where; Using temporary                        |
| 2  | DEPENDENT SUBQUERY |       |       |               |         |         |       |      | Impossible WHERE noticed after reading const tables |
| 3  | DERIVED            | o     | ref   | idx_2,idx_5   | idx_5   | 8       | const | 1    | Using where; Using filesort                         |
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+

重寫為 JOIN 之後，子查詢的選擇模式從 DEPENDENT SUBQUERY 變成 DERIVED，執行速度大大加快，從7秒降低到2毫秒。

UPDATE operation o 
       JOIN  (SELECT o.id, 
                            o.status 
                     FROM   operation o 
                     WHERE  o.group = 123 
                            AND o.status NOT IN ( 'done' ) 
                     ORDER  BY o.parent, 
                               o.id 
                     LIMIT  1) t
         ON o.id = t.id 
SET    status = 'applying' 

執行計劃簡化為：

+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key   | key_len | ref   | rows | Extra                                               |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
| 1  | PRIMARY     |       |      |               |       |         |       |      | Impossible WHERE noticed after reading const tables |
| 2  | DERIVED     | o     | ref  | idx_2,idx_5   | idx_5 | 8       | const | 1    | Using where; Using filesort                         |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+

5. 4、混合排序

MySQL 不能利用索引進行混合排序。但在某些場景，還是有機會使用特殊方法提升效能的。

SELECT * 
FROM   my_order o 
       INNER JOIN my_appraise a ON a.orderid = o.id 
ORDER  BY a.is_reply ASC, 
          a.appraise_time DESC 
LIMIT  0, 20 

執行計劃顯示為全表掃描：

+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
| id | select_type | table | type   | possible_keys     | key     | key_len | ref      | rows    | Extra    
+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
|  1 | SIMPLE      | a     | ALL    | idx_orderid | NULL    | NULL    | NULL    | 1967647 | Using filesort |
|  1 | SIMPLE      | o     | eq_ref | PRIMARY     | PRIMARY | 122     | a.orderid |       1 | NULL           |
+----+-------------+-------+--------+---------+---------+---------+-----------------+---------+-+

由於 is_reply 只有0和1兩種狀態，我們按照下面的方法重寫後，執行時間從1.58秒降低到2毫秒。

SELECT * 
FROM   ((SELECT *
         FROM   my_order o 
                INNER JOIN my_appraise a 
                        ON a.orderid = o.id 
                           AND is_reply = 0 
         ORDER  BY appraise_time DESC 
         LIMIT  0, 20) 
        UNION ALL 
        (SELECT *
         FROM   my_order o 
                INNER JOIN my_appraise a 
                        ON a.orderid = o.id 
                           AND is_reply = 1 
         ORDER  BY appraise_time DESC 
         LIMIT  0, 20)) t 
ORDER  BY  is_reply ASC, 
          appraisetime DESC 
LIMIT  20; 

6. 5、EXISTS語句

MySQL 對待 EXISTS 子句時，仍然採用巢狀子查詢的執行方式。如下面的 SQL 語句：

SELECT *
FROM   my_neighbor n 
       LEFT JOIN my_neighbor_apply sra 
              ON n.id = sra.neighbor_id 
                 AND sra.user_id = 'xxx' 
WHERE  n.topic_status </description>
        <link>http://mepopedia.com/forum/read.php?516,94670,94670#msg-94670</link>
        <lastBuildDate>Fri, 23 Oct 2026 13:40:22 +0800</lastBuildDate>
        <generator>Phorum 5.2.7</generator>
        <item>
            <guid>http://mepopedia.com/forum/read.php?516,94670,94670#msg-94670</guid>
            <title>8 種常見 SQL 錯誤用法，你避開了嗎？</title>
            <link>http://mepopedia.com/forum/read.php?516,94670,94670#msg-94670</link>
            <description><![CDATA[<div class='message-body' style='float:right; margin: 0; border: none; padding: 0 0 1em 1em; max-width: 216px'><div class='notice' style='padding: 10px 14px'><ol style='font-size: 0.8em; margin:0; padding: 0; list-style-type: none;'><li><a style='text-decoration:none' href=#SQL.E8.AA.9E.E5.8F.A5.E7.9A.84.E5.9F.B7.E8.A1.8C.E9.A0.86.E5.BA.8F.EF.BC.9A>1. SQL語句的執行順序：</a><li><a style='text-decoration:none' href=#1.E3.80.81LIMIT+.E8.AA.9E.E5.8F.A5>2. 1、LIMIT 語句</a><li><a style='text-decoration:none' href=#2.E3.80.81.E9.9A.B1.E5.BC.8F.E8.BD.89.E6.8F.9B>3. 2、隱式轉換</a><li><a style='text-decoration:none' href=#3.E3.80.81.E9.97.9C.E8.81.AF.E6.9B.B4.E6.96.B0.E3.80.81.E5.88.AA.E9.99.A4>4. 3、關聯更新、刪除</a><li><a style='text-decoration:none' href=#4.E3.80.81.E6.B7.B7.E5.90.88.E6.8E.92.E5.BA.8F>5. 4、混合排序</a><li><a style='text-decoration:none' href=#5.E3.80.81EXISTS.E8.AA.9E.E5.8F.A5>6. 5、EXISTS語句</a><li><a style='text-decoration:none' href=#6.E3.80.81.E6.A2.9D.E4.BB.B6.E4.B8.8B.E6.8E.A8>7. 6、條件下推</a><li><a style='text-decoration:none' href=#7.E3.80.81.E6.8F.90.E5.89.8D.E7.B8.AE.E5.B0.8F.E7.AF.84.E5.9C.8D>8. 7、提前縮小範圍</a><li><a style='text-decoration:none' href=#8.E3.80.81.E4.B8.AD.E9.96.93.E7.B5.90.E6.9E.9C.E9.9B.86.E4.B8.8B.E6.8E.A8>9. 8、中間結果集下推</a><li><a style='text-decoration:none' href=#.E7.B8.BD.E7.B5.90>10. 總結</a></ol></div></div><h2 id='SQL.E8.AA.9E.E5.8F.A5.E7.9A.84.E5.9F.B7.E8.A1.8C.E9.A0.86.E5.BA.8F.EF.BC.9A'>1. SQL語句的執行順序：</h2><br />
<br />
FROM <1><br />
ON <2><br />
JOIN <3><br />
WHERE <4><br />
GROUP BY <5><br />
HAVING <6><br />
SELECT <7><br />
DISTINCT <8><br />
ORDER BY <9><br />
LIMIT <10><br />
<br />
<h2 id='1.E3.80.81LIMIT+.E8.AA.9E.E5.8F.A5'>2. 1、LIMIT 語句</h2><br />
<br />
分頁查詢是最常用的場景之一，但也通常也是最容易出問題的地方。比如對於下面簡單的語句，一般 DBA 想到的辦法是在 type, name, create_time 欄位上加組合索引。這樣條件排序都能有效的利用到索引，效能迅速提升。<br />
<br />
SELECT * <br />
FROM   operation <br />
WHERE  type = 'SQLStats' <br />
       AND name = 'SlowLog' <br />
ORDER  BY create_time <br />
LIMIT  1000, 10; <br />
<br />
好吧，可能90%以上的 DBA 解決該問題就到此為止。但當 LIMIT 子句變成 “LIMIT 1000000,10” 時，程式設計師仍然會抱怨：我只取10條記錄為什麼還是慢？<br />
<br />
要知道資料庫也並不知道第1000000條記錄從什麼地方開始，即使有索引也需要從頭計算一次。出現這種效能問題，多數情形下是程式設計師偷懶了。<br />
<br />
在前端資料瀏覽翻頁，或者大資料分批匯出等場景下，是可以將上一頁的最大值當成引數作為查詢條件的。SQL 重新設計如下：<br />
<br />
SELECT   * <br />
FROM     operation <br />
WHERE    type = 'SQLStats' <br />
AND      name = 'SlowLog' <br />
AND      create_time > '2017-03-16 14:00:00' <br />
ORDER BY create_time limit 10;<br />
<br />
在新設計下查詢時間基本固定，不會隨著資料量的增長而發生變化。<br />
<br />
<h2 id='2.E3.80.81.E9.9A.B1.E5.BC.8F.E8.BD.89.E6.8F.9B'>3. 2、隱式轉換</h2><br />
<br />
SQL語句中查詢變數和欄位定義型別不匹配是另一個常見的錯誤。比如下面的語句：<br />
<br />
mysql> explain extended SELECT * <br />
     > FROM   my_balance b <br />
     > WHERE  b.bpn = 14000000123 <br />
     >       AND b.isverified IS NULL ;<br />
mysql> show warnings;<br />
| Warning | 1739 | Cannot use ref access on index 'bpn' due to type or collation conversion on field 'bpn'<br />
<br />
其中欄位 bpn 的定義為 varchar(20)，MySQL 的策略是將字串轉換為數字之後再比較。函式作用於表字段，索引失效。<br />
<br />
上述情況可能是應用程式框架自動填入的引數，而不是程式設計師的原意。現在應用框架很多很繁雜，使用方便的同時也小心它可能給自己挖坑。<br />
<br />
<h2 id='3.E3.80.81.E9.97.9C.E8.81.AF.E6.9B.B4.E6.96.B0.E3.80.81.E5.88.AA.E9.99.A4'>4. 3、關聯更新、刪除</h2><br />
<br />
雖然 MySQL5.6 引入了物化特性，但需要特別注意它目前僅僅針對查詢語句的最佳化。對於更新或刪除需要手工重寫成 JOIN。<br />
<br />
比如下面 UPDATE 語句，MySQL 實際執行的是迴圈/巢狀子查詢（DEPENDENT SUBQUERY)，其執行時間可想而知。<br />
<br />
UPDATE operation o <br />
SET    status = 'applying' <br />
WHERE  o.id IN (SELECT id <br />
                FROM   (SELECT o.id, <br />
                               o.status <br />
                        FROM   operation o <br />
                        WHERE  o.group = 123 <br />
                               AND o.status NOT IN ( 'done' ) <br />
                        ORDER  BY o.parent, <br />
                                  o.id <br />
                        LIMIT  1) t); <br />
<br />
<b>執行計劃：</b><br />
<br />
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+<br />
| id | select_type        | table | type  | possible_keys | key     | key_len | ref   | rows | Extra                                               |<br />
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+<br />
| 1  | PRIMARY            | o     | index |               | PRIMARY | 8       |       | 24   | Using where; Using temporary                        |<br />
| 2  | DEPENDENT SUBQUERY |       |       |               |         |         |       |      | Impossible WHERE noticed after reading const tables |<br />
| 3  | DERIVED            | o     | ref   | idx_2,idx_5   | idx_5   | 8       | const | 1    | Using where; Using filesort                         |<br />
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+<br />
<br />
重寫為 JOIN 之後，子查詢的選擇模式從 DEPENDENT SUBQUERY 變成 DERIVED，執行速度大大加快，從7秒降低到2毫秒。<br />
<br />
UPDATE operation o <br />
       JOIN  (SELECT o.id, <br />
                            o.status <br />
                     FROM   operation o <br />
                     WHERE  o.group = 123 <br />
                            AND o.status NOT IN ( 'done' ) <br />
                     ORDER  BY o.parent, <br />
                               o.id <br />
                     LIMIT  1) t<br />
         ON o.id = t.id <br />
SET    status = 'applying' <br />
<br />
<b>執行計劃簡化為：</b><br />
<br />
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+<br />
| id | select_type | table | type | possible_keys | key   | key_len | ref   | rows | Extra                                               |<br />
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+<br />
| 1  | PRIMARY     |       |      |               |       |         |       |      | Impossible WHERE noticed after reading const tables |<br />
| 2  | DERIVED     | o     | ref  | idx_2,idx_5   | idx_5 | 8       | const | 1    | Using where; Using filesort                         |<br />
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+<br />
<br />
<h2 id='4.E3.80.81.E6.B7.B7.E5.90.88.E6.8E.92.E5.BA.8F'>5. 4、混合排序</h2><br />
<br />
MySQL 不能利用索引進行混合排序。但在某些場景，還是有機會使用特殊方法提升效能的。<br />
<br />
SELECT * <br />
FROM   my_order o <br />
       INNER JOIN my_appraise a ON a.orderid = o.id <br />
ORDER  BY a.is_reply ASC, <br />
          a.appraise_time DESC <br />
LIMIT  0, 20 <br />
<br />
<b>執行計劃顯示為全表掃描：</b><br />
<br />
+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+<br />
| id | select_type | table | type   | possible_keys     | key     | key_len | ref      | rows    | Extra    <br />
+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+<br />
|  1 | SIMPLE      | a     | ALL    | idx_orderid | NULL    | NULL    | NULL    | 1967647 | Using filesort |<br />
|  1 | SIMPLE      | o     | eq_ref | PRIMARY     | PRIMARY | 122     | a.orderid |       1 | NULL           |<br />
+----+-------------+-------+--------+---------+---------+---------+-----------------+---------+-+<br />
<br />
由於 is_reply 只有0和1兩種狀態，我們按照下面的方法重寫後，執行時間從1.58秒降低到2毫秒。<br />
<br />
SELECT * <br />
FROM   ((SELECT *<br />
         FROM   my_order o <br />
                INNER JOIN my_appraise a <br />
                        ON a.orderid = o.id <br />
                           AND is_reply = 0 <br />
         ORDER  BY appraise_time DESC <br />
         LIMIT  0, 20) <br />
        UNION ALL <br />
        (SELECT *<br />
         FROM   my_order o <br />
                INNER JOIN my_appraise a <br />
                        ON a.orderid = o.id <br />
                           AND is_reply = 1 <br />
         ORDER  BY appraise_time DESC <br />
         LIMIT  0, 20)) t <br />
ORDER  BY  is_reply ASC, <br />
          appraisetime DESC <br />
LIMIT  20; <br />
<br />
<h2 id='5.E3.80.81EXISTS.E8.AA.9E.E5.8F.A5'>6. 5、EXISTS語句</h2><br />
<br />
MySQL 對待 EXISTS 子句時，仍然採用巢狀子查詢的執行方式。如下面的 SQL 語句：<br />
<br />
SELECT *<br />
FROM   my_neighbor n <br />
       LEFT JOIN my_neighbor_apply sra <br />
              ON n.id = sra.neighbor_id <br />
                 AND sra.user_id = 'xxx' <br />
WHERE  n.topic_status < 4 <br />
       AND EXISTS(SELECT 1 <br />
                  FROM   message_info m <br />
                  WHERE  n.id = m.neighbor_id <br />
                         AND m.inuser = 'xxx') <br />
       AND n.topic_type <> 5 <br />
<br />
<b>執行計劃為：</b><br />
<br />
+----+--------------------+-------+------+-----+------------------------------------------+---------+-------+---------+ -----+<br />
| id | select_type        | table | type | possible_keys     | key   | key_len | ref   | rows    | Extra   |<br />
+----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+<br />
|  1 | PRIMARY            | n     | ALL  |  | NULL     | NULL    | NULL  | 1086041 | Using where                   |<br />
|  1 | PRIMARY            | sra   | ref  |  | idx_user_id | 123     | const |       1 | Using where          |<br />
|  2 | DEPENDENT SUBQUERY | m     | ref  |  | idx_message_info   | 122     | const |       1 | Using index condition; Using where |<br />
+----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+<br />
<br />
去掉 exists 更改為 join，能夠避免巢狀子查詢，將執行時間從1.93秒降低為1毫秒。<br />
<br />
SELECT *<br />
FROM   my_neighbor n <br />
       INNER JOIN message_info m <br />
               ON n.id = m.neighbor_id <br />
                  AND m.inuser = 'xxx' <br />
       LEFT JOIN my_neighbor_apply sra <br />
              ON n.id = sra.neighbor_id <br />
                 AND sra.user_id = 'xxx' <br />
WHERE  n.topic_status < 4 <br />
       AND n.topic_type <> 5 <br />
<br />
<b>新的執行計劃：</b><br />
<br />
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+<br />
| id | select_type | table | type   | possible_keys     | key       | key_len | ref   | rows | Extra                 |<br />
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+<br />
|  1 | SIMPLE      | m     | ref    | | idx_message_info   | 122     | const    |    1 | Using index condition |<br />
|  1 | SIMPLE      | n     | eq_ref | | PRIMARY   | 122     | ighbor_id |    1 | Using where      |<br />
|  1 | SIMPLE      | sra   | ref    | | idx_user_id | 123     | const     |    1 | Using where           |<br />
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+<br />
<br />
<h2 id='6.E3.80.81.E6.A2.9D.E4.BB.B6.E4.B8.8B.E6.8E.A8'>7. 6、條件下推</h2><br />
<br />
外部查詢條件不能夠下推到複雜的檢視或子查詢的情況有：<br />
<br />
1、聚合子查詢；2、含有 LIMIT 的子查詢；3、UNION 或 UNION ALL 子查詢；4、輸出欄位中的子查詢；<br />
<br />
如下面的語句，從執行計劃可以看出其條件作用於聚合子查詢之後：<br />
<br />
SELECT * <br />
FROM   (SELECT target, <br />
               Count(*) <br />
        FROM   operation <br />
        GROUP  BY target) t <br />
WHERE  target = 'rm-xxxx' <br />
<br />
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+<br />
| id | select_type | table      | type  | possible_keys | key         | key_len | ref   | rows | Extra       |<br />
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+<br />
|  1 | PRIMARY     |  | ref   | <auto_key0>   |  | 514     | const |    2 | Using where |<br />
|  2 | DERIVED     | operation  | index | idx_4         | idx_4       | 519     | NULL  |   20 | Using index |<br />
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+<br />
<br />
確定從語義上查詢條件可以直接下推後，重寫如下：<br />
<br />
SELECT target, <br />
       Count(*) <br />
FROM   operation <br />
WHERE  target = 'rm-xxxx' <br />
GROUP  BY target<br />
<br />
<b>執行計劃變為：</b><br />
<br />
+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+<br />
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |<br />
+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+<br />
| 1 | SIMPLE | operation | ref | idx_4 | idx_4 | 514 | const | 1 | Using where; Using index |<br />
+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+<br />
<br />
<h2 id='7.E3.80.81.E6.8F.90.E5.89.8D.E7.B8.AE.E5.B0.8F.E7.AF.84.E5.9C.8D'>8. 7、提前縮小範圍</h2><br />
<br />
先上初始 SQL 語句：<br />
<br />
SELECT * <br />
FROM   my_order o <br />
       LEFT JOIN my_userinfo u <br />
              ON o.uid = u.uid<br />
       LEFT JOIN my_productinfo p <br />
              ON o.pid = p.pid <br />
WHERE  ( o.display = 0 ) <br />
       AND ( o.ostaus = 1 ) <br />
ORDER  BY o.selltime DESC <br />
LIMIT  0, 15 <br />
<br />
該SQL語句原意是：先做一系列的左連線，然後排序取前15條記錄。從執行計劃也可以看出，最後一步估算排序記錄數為90萬，時間消耗為12秒。<br />
<br />
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+<br />
| id | select_type | table | type   | possible_keys | key     | key_len | ref             | rows   | Extra                                              |<br />
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+<br />
|  1 | SIMPLE      | o     | ALL    | NULL          | NULL    | NULL    | NULL            | 909119 | Using where; Using temporary; Using filesort       |<br />
|  1 | SIMPLE      | u     | eq_ref | PRIMARY       | PRIMARY | 4       | o.uid |      1 | NULL                                               |<br />
|  1 | SIMPLE      | p     | ALL    | PRIMARY       | NULL    | NULL    | NULL            |      6 | Using where; Using join buffer (Block Nested Loop) |<br />
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+<br />
<br />
由於最後 WHERE 條件以及排序均針對最左主表，因此可以先對 my_order 排序提前縮小資料量再做左連線。SQL 重寫後如下，執行時間縮小為1毫秒左右。<br />
<br />
SELECT * <br />
FROM (<br />
SELECT * <br />
FROM   my_order o <br />
WHERE  ( o.display = 0 ) <br />
       AND ( o.ostaus = 1 ) <br />
ORDER  BY o.selltime DESC <br />
LIMIT  0, 15<br />
) o <br />
     LEFT JOIN my_userinfo u <br />
              ON o.uid = u.uid <br />
     LEFT JOIN my_productinfo p <br />
              ON o.pid = p.pid <br />
ORDER BY  o.selltime DESC<br />
limit 0, 15<br />
<br />
再檢查執行計劃：子查詢物化後（select_type=DERIVED)參與 JOIN。雖然估算行掃描仍然為90萬，但是利用了索引以及 LIMIT 子句後，實際執行時間變得很小。<br />
<br />
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+<br />
| id | select_type | table      | type   | possible_keys | key     | key_len | ref   | rows   | Extra                                              |<br />
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+<br />
|  1 | PRIMARY     |  | ALL    | NULL          | NULL    | NULL    | NULL  |     15 | Using temporary; Using filesort                    |<br />
|  1 | PRIMARY     | u          | eq_ref | PRIMARY       | PRIMARY | 4       | o.uid |      1 | NULL                                               |<br />
|  1 | PRIMARY     | p          | ALL    | PRIMARY       | NULL    | NULL    | NULL  |      6 | Using where; Using join buffer (Block Nested Loop) |<br />
|  2 | DERIVED     | o          | index  | NULL          | idx_1   | 5       | NULL  | 909112 | Using where                                        |<br />
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+<br />
<br />
<h2 id='8.E3.80.81.E4.B8.AD.E9.96.93.E7.B5.90.E6.9E.9C.E9.9B.86.E4.B8.8B.E6.8E.A8'>9. 8、中間結果集下推</h2><br />
<br />
再來看下面這個已經初步最佳化過的例子(左連線中的主表優先作用查詢條件)：<br />
<br />
SELECT    a.*, <br />
          c.allocated <br />
FROM      ( <br />
              SELECT   resourceid <br />
              FROM     my_distribute d <br />
                   WHERE    isdelete = 0 <br />
                   AND      cusmanagercode = '1234567' <br />
                   ORDER BY salecode limit 20) a <br />
LEFT JOIN <br />
          ( <br />
              SELECT   resourcesid， sum(ifnull(allocation, 0) * 12345) allocated <br />
              FROM     my_resources <br />
                   GROUP BY resourcesid) c <br />
ON        a.resourceid = c.resourcesid<br />
<br />
那麼該語句還存在其它問題嗎？不難看出子查詢 c 是全表聚合查詢，在表數量特別大的情況下會導致整個語句的效能下降。<br />
<br />
其實對於子查詢 c，左連線最後結果集只關心能和主表 resourceid 能匹配的資料。因此我們可以重寫語句如下，執行時間從原來的2秒下降到2毫秒。<br />
<br />
SELECT    a.*, <br />
          c.allocated <br />
FROM      ( <br />
                   SELECT   resourceid <br />
                   FROM     my_distribute d <br />
                   WHERE    isdelete = 0 <br />
                   AND      cusmanagercode = '1234567' <br />
                   ORDER BY salecode limit 20) a <br />
LEFT JOIN <br />
          ( <br />
                   SELECT   resourcesid， sum(ifnull(allocation, 0) * 12345) allocated <br />
                   FROM     my_resources r, <br />
                            ( <br />
                                     SELECT   resourceid <br />
                                     FROM     my_distribute d <br />
                                     WHERE    isdelete = 0 <br />
                                     AND      cusmanagercode = '1234567' <br />
                                     ORDER BY salecode limit 20) a <br />
                   WHERE    r.resourcesid = a.resourcesid <br />
                   GROUP BY resourcesid) c <br />
ON        a.resourceid = c.resourcesid<br />
<br />
但是子查詢 a 在我們的SQL語句中出現了多次。這種寫法不僅存在額外的開銷，還使得整個語句顯的繁雜。使用 WITH 語句再次重寫：<br />
<br />
WITH a AS <br />
( <br />
         SELECT   resourceid <br />
         FROM     my_distribute d <br />
         WHERE    isdelete = 0 <br />
         AND      cusmanagercode = '1234567' <br />
         ORDER BY salecode limit 20)<br />
SELECT    a.*, <br />
          c.allocated <br />
FROM      a <br />
LEFT JOIN <br />
          ( <br />
                   SELECT   resourcesid， sum(ifnull(allocation, 0) * 12345) allocated <br />
                   FROM     my_resources r, <br />
                            a <br />
                   WHERE    r.resourcesid = a.resourcesid <br />
                   GROUP BY resourcesid) c <br />
ON        a.resourceid = c.resourcesid<br />
<br />
<h2 id='.E7.B8.BD.E7.B5.90'>10. 總結</h2><br />
<br />
資料庫編譯器產生執行計劃，決定著SQL的實際執行方式。但是編譯器只是盡力服務，所有資料庫的編譯器都不是盡善盡美的。<br />
<br />
上述提到的多數場景，在其它資料庫中也存在效能問題。瞭解資料庫編譯器的特性，才能避規其短處，寫出高效能的SQL語句。]]></description>
            <dc:creator>frlily</dc:creator>
            <category>資訊</category>
            <pubDate>Tue, 12 Nov 2024 16:21:00 +0800</pubDate>
        </item>
    </channel>
</rss>
