查詢優化器無法做優化的情況,又可以分為合理的和不合理的。
整理自《數據庫查詢優化器的藝術》,每個例子後面都有頁碼
合理的情況
常量+in+子查詢,且子查詢與上層的表沒有關係,沒有做上拉優化
select t1.* from t1 where 10 in (select t2.id from t2)
這種情況沒法做上拉優化。查詢計劃是對t2做一個順序掃描,結果作為t1表的filter。
where true 則where條件相當於沒有
where false 則語句不返回任何值
(P24-25)
exists+子查詢,且子查詢與上層的表沒有關係,沒有做上拉優化
select t1.* from t1 where exists (select t2.id from t2 where t2.id=10)
子查詢只要執行一次即可推知EXISTS的結果是TRUE或FALSE(exists在這裏的含義是找到一個就返回true),所以不會執行多次,也沒有比要優化。
(P27-28)
主查詢中出現了易失函數,影響了子查詢的結果,沒有做上拉優化
select t1.* from t1 where t1.id+random() in (select t2.id from t2)
由於出現易失函數random(),子查詢結果不確定,查詢優化器就不能對子查詢做優化。
(P24-25)
聚集函數操作出現在子查詢的select中,沒有做子查詢消除
select * from t1 where t1.age>(select min(t2.age) from t2);
不支持子查詢消除。因為沒有必要,子查詢只需執行一次即可得出結果值。
另一個例子也是這種情況,聚集函數操作出現在in子查詢的select部分: select * from t1 where t1.age in (select min(t2.age) from t2);
(P377)
不合理的情況
OR沒法轉換ANY
sal>10 or sal>base+1 or sal>base*2
應重寫為sal > ANY(10,base+1,base*2)
PG和MySQL都不支持
(P31)
謂詞條件相似的子查詢沒有合併
select * from t1 where a1<4 and (exists(select a2 from t2 where t2.a2<5 and t2.b2=1) or exists(select a2 from t2 where t2.a2<5 and t2.b2=2));
執行計劃是兩個subquery,t2表做了兩次獨立的掃描(雖然都是用到了a2列索引的範圍掃描),沒有把條件合併在一起判斷。
應重寫為select * from t1 where a1<4 and exists(select a2 from t2 wheer t2.a2<5 and (t2.b2=1 or t2.b2=2));
這樣的好處是t2只執行了一次全表掃描,filter是(b2=1) or (b2=2)。這種改寫的前提是t2表上面的選擇度比較大,否則的話還是原語句效率更高。
(P374-P375)
子查詢作為主查詢的視圖時,展開支持不夠好
select from t1, (select from t2 where t2.age>10) v_t2 where t1.age <10 AND v_t2.age <20;
這種情況下,t2上的子查詢仍然時單獨執行:
select t1.id, t1.age, t2.id, t2.age from t1
join
(select t2.id, t2.age from t2 where t2.age >10) v_t2
where t1.age<10 and v_t2.age<20)
應重寫為:
select * from t1 join t2 where t1.age<10 and t2.age>10 and t2.age<20
(P375)
in相關子查詢上拉為內連接的策略,只在子查詢的select是針對主鍵列的才生效
select * from t1 where t1.age<100 and age in (select age from t2 where t2.age>10)
當t2.age不是主鍵時,上述執行計劃中,子查詢被物化,但是沒有上拉到頂層與t1進行連接。
當t2.age是主鍵時,執行計劃顯示上述子查詢被轉換為內連接。
(P376)
為什麼這裏強調in相關子查詢?因為對於in非相關子查詢來説,大多數又是支持上拉為內連接的。
(P380-P381)
不支持exists/not exists子查詢轉化為join
select * from t1 where exists(select 1 from t2 where t1.age=t2.age and t2.age>10)
select * from t1 where not exists(select 1 from t2 where t1.age=t2.age and t2.age>10)
上述兩個語句的執行計劃中,子查詢的select type為dependent subquery。
(P378-P379)
不支持not in子查詢轉化為join
select * from t1 wher t1.age not in (select age from t2 where t2.age>10)
查詢計劃中,只是對子查詢做了物化的優化方式,但沒有與t1做join
(P381-P382)
普通子查詢無法轉換為連接
普通子查詢指除了IN/NOT IN/EXISTS/NOT EXISTS/ALL/ANY/SOME以外的類型。
如select from t1 left join (select from t2 where t2.b2>10) tt2 on t1.b1=tt2.b2;
沒有做優化,子查詢仍然在。
(P438)
不支持把兩個相同子查詢合併
雖然可以把IN子查詢轉換為連接操作,如select * from t1 where t1.b1 in (select b2 from t2) and t1.a1 in (select b2 from t2);
對多次出現的相同子查詢做了優化,在t2表上合併了in的條件,執行了一次掃描後對t2表進行了物化,並用兩次semi join 完成連接:
select * from t1 semi join t2 semi join t2 where t1.a1=t2.b2 and t2.b2=t1.b1
但是兩個semi join其實是可以合併的。
(P441)
能識別子查詢,但不能進行子查詢的上拉優化
select from (select from t1)T;
MySQL把select from t1和select from T當作兩個不同的查詢,t1表以derived方式被處理。
MySQL上拉子查詢,在邏輯上有時會把子查詢和原查詢作為兩個查詢邏輯,所以沒有機會把邏輯上重複的表去掉(但MySQL可以把邏輯上重複的表的條件合併)。
(P442)
連接的消除
- 主外鍵關係的表進行內/外連接,可消除主鍵表
- 唯一鍵作為連接條件,三表連接可以去掉中間表(中間表的列只作為連接條件)
- 可消除的表除了作為連接對象外,不出現在任何子句中
(P40)
但是MySQL對上面的三種情況,都不支持連接消除(P398-P406)
情況一的一個示例:
create table a(id int, name varchar(2)), primary key(id);
create table b(id int, name varchar(2)), foreign key(id) references a(id);
create table c(id int,name varcher(2));
select b.,c. from a,b,c where b.id=a.id and a.id=c.id
被查詢優化器處理後的語句:select b.,c. from a join b join c where b.id=c.id and a.id=c.id
執行計劃顯示,在a關係上做了eq_ref掃描,a沒有被去除,c和a先進行連接,然後與b再連接。
(P398-P399)
語義優化
- 檢測空回答集(Detecting the empty answer set)
- 謂詞引入
- 排序優化(order optimizer)
- 唯一性使用(exxploiting uniqueness)
(P40)
MySQL支持部分語義優化的功能。(通過為SQL查詢語句添加DDL語句定義的表上的非空屬性,從而限定查詢語句的條件判斷來實現語義優化的,但其他的完整性約束MySQL尚不能利用)
MySQL支持:檢測空回答集,如check約束限定name是not null,而一個查詢條件是where name is null and age>18,則立刻推知條件不成立。
(P401)
MySQL不支持:謂詞引入,如一個表上有c1<c2的列約束,c2列上存在一個索引,查詢語句中的where條件有c1>200,則可以推知c2>200,where條件變更為c1>200 and c2>200 and c1<c2,由此可以利用c2列上的索引,對查詢語句進行優化,如果c2列上索引的選擇率很低,優化效果會更高。
(P40,P402)
MySQL支持:排序優化,但條件較為苛刻。order by操作如果可以利用索引,則排序操作可省略。
(案例見P403-P404)
MySQL支持:唯一性使用,利用唯一性、索引等特點,檢查是否存在不必要的distinct操作,如在主鍵上執行distinct,可以把distinct消除掉。
(P405)