衍生表的優化:合並 | 具化
一、mysql優化器對於衍生表的優化處理可以從兩方面進行:
-
將衍生表合並到外部查詢
-
將衍生表具化為內部臨時表
1、示例 1:
SELECT * FROM (SELECT * FROM t1) AS derived_t1;
衍生表 derived_t1 合並處理后,實際執行的查詢類似如下:
SELECT * FROM t1;
2、示例 2:
SELECT * FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2 ON t1.f2=derived_t2.f1 WHERE t1.f1 > 0;
衍生表 derived_t2 合並處理后,實際執行的查詢類似如下:
SELECT t1.*, t2.f1 FROM t1 JOIN t2 ON t1.f2=t2.f1 WHERE t1.f1 > 0;
如果是具化操作的話, derived_t1
和 derived_t2
會被作為獨立的表來進行查詢。
mysql 優化器會盡量避免去具化衍生表。
如果合並操作是的外部表超過61個,則優化器會選擇具化表。
二、優化器關於衍生表中 order by 的處理:
1、在 sql 滿足如下全部條件時,衍生表的 order by 會被放到外部查詢延遲執行,反之,則會被忽略:
-
外部查詢無分組、聚合操作。
-
外部查詢沒有使用
DISTINCT
,HAVING 或
ORDER BY等操作。
-
外部查詢只有衍生表這個唯一的查詢源。
2、可以通過以下幾種方式進行優化器的衍生表合並:
-
關閉 derived_merge:mysql5.7默認是開啟的。
-
子查詢使用一些特定操作來阻止優化器合並操作:
三、實際應用
筆者曾經遇到需要查詢關聯同一身份證信息的所有用戶中最新關聯的用戶記錄:
SELECT id, name, created_at FROM( SELECT table1.*, max(table1.created_at) FROM( SELECT * FROM users ORDER BY created_at desc ) table1 GROUP BY id_no ) table2 ORDER BY id
但是,並沒有得到想要的結果,查看執行計划如下:
只有一個衍生表,但是,看我們的sql,明明有三層查詢。
想到之前,mysql版本做過升級,當前為5.7版本,考慮到mysql5.7版本對於衍生表的優化處理,首先能夠確定的一點是優化器對衍生表做了合並處理,但是僅僅是合並,也不應該影響預期的查詢結果。
參考第二節中介紹的,進一步觀察可知,最內部的 SELECT * FROM users ORDER BY created_at desc 不滿足第二.2中的條件,因此 order by 丟失導致查詢結果不符合預期。
sql調整:確定記錄不超過10000,所以添加 limit 1000 來阻止優化器對衍生表進行合並操作
SELECT id, name, created_at FROM( SELECT table1.*, max(table1.created_at) FROM( SELECT * FROM users ORDER BY created_at desc LIMIT 10000 ) table1 GROUP BY id_no ) table2 ORDER BY id
查看執行計划如下:
兩層衍生表,符合sql預期,執行結果也符合預期。
或者,也可以執行如下調整:使用 HAVING 1=1 等true條件
SELECT id, name, created_at FROM( SELECT table1.*, max(table1.created_at) FROM( SELECT * FROM users HAVING 1=1 ORDER BY created_at desc ) table1 GROUP BY id_no ) table2 ORDER BY id
查看執行計划如下:
同樣阻止了優化器的衍生表合並操作。