MySQL子查詢?cè)淼纳钊敕治?/h1>
更新時(shí)間:2022年01月29日 11:07:54 作者:360云計(jì)算
子查詢指一個(gè)查詢語(yǔ)句嵌套在另一個(gè)查詢語(yǔ)句內(nèi)部的查詢,這個(gè)特性從 MySQL 4.1 開始引入,下面這篇文章主要介紹了MySQL子查詢?cè)淼南嚓P(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
01前言
子查詢,通俗解釋就是查詢語(yǔ)句中嵌套著另一個(gè)查詢語(yǔ)句。相信日常工作中接觸到 MySQL 的同學(xué)都了解或使用過(guò)子查詢,但是具體它是怎樣實(shí)現(xiàn)的呢? 查詢效率如何? 這些恐怕好多人就不太清楚了,下面咱們就圍繞這兩個(gè)問(wèn)題共同探索一下。
02準(zhǔn)備內(nèi)容
這里我們需要用到3個(gè)表,這3個(gè)表都有一個(gè)主鍵索引 id 和一個(gè)索引 a,字段 b 上無(wú)索引。存儲(chǔ)過(guò)程 idata() 往表 t1 里插入的是 100 行數(shù)據(jù),表 t2、t3 里插入了 1000 行數(shù)據(jù)。建表語(yǔ)句如下:
CREATE TABLE `t1` (
`id` INT ( 11 ) NOT NULL,
`t1_a` INT ( 11 ) DEFAULT NULL,
`t1_b` INT ( 11 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `idx_a` ( `t1_a` )) ENGINE = INNODB;
CREATE TABLE `t2` (
`id` INT ( 11 ) NOT NULL,
`t2_a` INT ( 11 ) DEFAULT NULL,
`t2_b` INT ( 11 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `idx_a` ( `t2_a` )) ENGINE = INNODB;
CREATE TABLE `t3` (
`id` INT ( 11 ) NOT NULL,
`t3_a` INT ( 11 ) DEFAULT NULL,
`t3_b` INT ( 11 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `idx_a` ( `t3_a` )) ENGINE = INNODB;
-- 向t1添加100條數(shù)據(jù)
-- drop procedure idata;
delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i<=100)do
insert into t1 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
-- 向t2添加1000條數(shù)據(jù)
drop procedure idata;
delimiter ;;
create procedure idata()
begin
declare i int;
set i=101;
while(i<=1100)do
insert into t2 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
-- 向t2添加1000條數(shù)據(jù),且t3_a列的值為倒敘
drop procedure idata;
delimiter ;;
create procedure idata()
begin
declare i int;
set i=101;
while(i<=1100)do
insert into t3 values(i, 1101-i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
03子查詢的語(yǔ)法形式和分類
3.1 語(yǔ)法形式
子查詢的語(yǔ)法規(guī)定,子查詢可以在一個(gè)外層查詢的各種位置出現(xiàn),這里我們只介紹常用的幾個(gè):
3.1.1 FROM子句中
如 SELECT m, n FROM (SELECT m2 + 1 AS m, n2 AS n FROM t2 WHERE m2 > 2) AS t;
這個(gè)例子中的子查詢是:(SELECT m2 + 1 AS m, n2 AS n FROM t2 WHERE m2 > 2
),這個(gè)放在FROM子句中的子查詢相當(dāng)于一個(gè)表,但又和我們平常使用的表有點(diǎn)兒不一樣,這種由子查詢結(jié)果集組成的表稱之為派生表。
3.1.2 WHERE或IN子句中
如:SELECT * FROM t1 WHERE m1 = (SELECT MIN(m2) FROM t2);
SELECT * FROM t1 WHERE m1 IN (SELECT m2 FROM t2);
其他的還有 SELECT 子句中,ORDER BY 子句中,GROUP BY 子句中,雖然語(yǔ)法支持,但沒(méi)啥意義,就不嘮叨這些情況了。
3.2 分類
3.2.1 按返回的結(jié)果集區(qū)分
標(biāo)量子查詢,只返回一個(gè)單一值的子查詢稱之為標(biāo)量子查詢,比如:
SELECT * FROM t1 WHERE m1 = (SELECT m1 FROM t1 LIMIT 1);
行子查詢,就是只返回一條記錄的子查詢,不過(guò)這條記錄需要包含多個(gè)列(只包含一個(gè)列就成了標(biāo)量子查詢了)。比如:SELECT * FROM t1 WHERE (m1, n1) = (SELECT m2, n2 FROM t2 LIMIT 1);
列子查詢,就是只返回一個(gè)列的數(shù)據(jù),不過(guò)這個(gè)列的數(shù)據(jù)需要包含多條記錄(只包含一條記錄就成了標(biāo)量子查詢了)。比如:SELECT * FROM t1 WHERE m1 IN (SELECT m2 FROM t2);
表子查詢,就是子查詢的結(jié)果既包含很多條記錄,又包含很多個(gè)列,比如:
SELECT * FROM t1 WHERE (m1, n1) IN (SELECT m2, n2 FROM t2);
其中的 (SELECT m2, n2 FROM t2) 就是一個(gè)表子查詢,這里需要和行子查詢對(duì)比一下,行子查詢中我們用了 LIMIT 1 來(lái)保證子查詢的結(jié)果只有一條記錄。
3.2.2 按與外層查詢關(guān)系來(lái)區(qū)分
不相關(guān)子查詢,就是子查詢可以單獨(dú)運(yùn)行出結(jié)果,而不依賴于外層查詢的值,我們就可以把這個(gè)子查詢稱之為不相關(guān)子查詢。
相關(guān)子查詢,就是需要依賴于外層查詢的值的子查詢稱之為相關(guān)子查詢。比如:SELECT * FROM t1 WHERE m1 IN (SELECT m2 FROM t2 WHERE n1 = n2);
04子查詢?cè)贛ySQL中是怎么執(zhí)行的
4.1 標(biāo)量子查詢、行子查詢的執(zhí)行方式
4.1.1 不相關(guān)子查詢
如下邊這個(gè)查詢語(yǔ)句:
mysql root@localhost:test> explain select * from t1 where t1_a = (select t2_a from t2 limit 1);
+----+-------------+-------+-------+---------------+-------+---------+--------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-------+---------+--------+------+-------------+
| 1 | PRIMARY | t1 | ref | idx_a | idx_a | 5 | const | 1 | Using where |
| 2 | SUBQUERY | t2 | index | <null> | idx_a | 5 | <null> | 1000 | Using index |
+----+-------------+-------+-------+---------------+-------+---------+--------+------+-------------+
它的執(zhí)行方式:
先單獨(dú)執(zhí)行 (select t2_a from t2 limit 1) 這個(gè)子查詢。
然后在將上一步子查詢得到的結(jié)果當(dāng)作外層查詢的參數(shù)再執(zhí)行外層查詢 select * from t1 where t1_a = ...。
也就是說(shuō),對(duì)于包含不相關(guān)的標(biāo)量子查詢或者行子查詢的查詢語(yǔ)句來(lái)說(shuō),MySQL 會(huì)分別獨(dú)立的執(zhí)行外層查詢和子查詢,就當(dāng)作兩個(gè)單表查詢就好了。
4.1.2 相關(guān)的子查詢
比如下邊這個(gè)查詢:
mysql root@localhost:test> explain select * from t1 where t1_a = (select t2_a from t2 where t1.t1_b=t2.t2_b limit 1);
+----+--------------------+-------+------+---------------+--------+---------+--------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------+------+---------------+--------+---------+--------+------+-------------+
| 1 | PRIMARY | t1 | ALL | <null> | <null> | <null> | <null> | 100 | Using where |
| 2 | DEPENDENT SUBQUERY | t2 | ALL | <null> | <null> | <null> | <null> | 1000 | Using where |
+----+--------------------+-------+------+---------------+--------+---------+--------+------+-------------+
它的執(zhí)行方式就是這樣的:
先從外層查詢中獲取一條記錄,本例中也就是先從 t1 表中獲取一條記錄。
然后從上一步驟中獲取的那條記錄中找出子查詢中涉及到的值,就是 t1 表中找出 t1.t1_b 列的值,然后執(zhí)行子查詢。
最后根據(jù)子查詢的查詢結(jié)果來(lái)檢測(cè)外層查詢 WHERE 子句的條件是否成立,如果成立,就把外層查詢的那條記錄加入到結(jié)果集,否則就丟棄。
然后重復(fù)以上步驟,直到 t1 中的記錄全部匹配完。
4.2 IN子查詢
4.2.1 物化
如果子查詢的結(jié)果集中的記錄條數(shù)很少,那么把子查詢和外層查詢分別看成兩個(gè)單獨(dú)的單表查詢效率還是蠻高的,但是如果單獨(dú)執(zhí)行子查詢后的結(jié)果集太多的話,就會(huì)導(dǎo)致這些問(wèn)題:
結(jié)果集太多,可能內(nèi)存中都放不下~
對(duì)于外層查詢來(lái)說(shuō),如果子查詢的結(jié)果集太多,那就意味著 IN 子句中的參數(shù)特別多,這就導(dǎo)致:
1)無(wú)法有效的使用索引,只能對(duì)外層查詢進(jìn)行全表掃描。
2)在對(duì)外層查詢執(zhí)行全表掃描時(shí),由于 IN 子句中的參數(shù)太多,這會(huì)導(dǎo)致檢測(cè)一條記錄是否符合和 IN 子句中的參數(shù)匹配花費(fèi)的時(shí)間太長(zhǎng)。
于是就有:不直接將不相關(guān)子查詢的結(jié)果集當(dāng)作外層查詢的參數(shù),而是將該結(jié)果集寫入一個(gè)臨時(shí)表里。寫入臨時(shí)表的過(guò)程是這樣的:
該臨時(shí)表的列就是子查詢結(jié)果集中的列。
寫入臨時(shí)表的記錄會(huì)被去重,讓臨時(shí)表變得更小,更省地方。
一般情況下子查詢結(jié)果集不大時(shí),就會(huì)為它建立基于內(nèi)存的使用 Memory 存儲(chǔ)引擎的臨時(shí)表,而且會(huì)為該表建立哈希索引。
如果子查詢的結(jié)果集非常大,超過(guò)了系統(tǒng)變量 tmp_table_size或者 max_heap_table_size,臨時(shí)表會(huì)轉(zhuǎn)而使用基于磁盤的存儲(chǔ)引擎來(lái)保存結(jié)果集中的記錄,索引類型也對(duì)應(yīng)轉(zhuǎn)變?yōu)?B+ 樹索引。
這個(gè)將子查詢結(jié)果集中的記錄保存到臨時(shí)表的過(guò)程稱之為物化(Materialize)。為了方便起見,我們就把那個(gè)存儲(chǔ)子查詢結(jié)果集的臨時(shí)表稱之為物化表。正因?yàn)槲锘碇械挠涗浂冀⒘怂饕ɑ趦?nèi)存的物化表有哈希索引,基于磁盤的有 B+ 樹索引),通過(guò)索引執(zhí)行IN語(yǔ)句判斷某個(gè)操作數(shù)在不在子查詢結(jié)果集中變得非常快,從而提升了子查詢語(yǔ)句的性能。
mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2);
+----+--------------+-------------+--------+---------------+------------+---------+--------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+-------------+--------+---------------+------------+---------+--------------+------+-------------+
| 1 | SIMPLE | t3 | ALL | idx_a | <null> | <null> | <null> | 1000 | Using where |
| 1 | SIMPLE | <subquery2> | eq_ref | <auto_key> | <auto_key> | 5 | test.t3.t3_a | 1 | <null> |
| 2 | MATERIALIZED | t2 | index | idx_a | idx_a | 5 | <null> | 1000 | Using index |
+----+--------------+-------------+--------+---------------+------------+---------+--------------+------+-------------+
其實(shí)上邊的查詢就相當(dāng)于表 t3 和子查詢物化表進(jìn)行內(nèi)連接:
mysql root@localhost:test> explain select * from t3 left join t2 on t3.t3_a=t2.t2_a;
+----+-------------+-------+------+---------------+--------+---------+--------------+------+--------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+--------------+------+--------+
| 1 | SIMPLE | t3 | ALL | <null> | <null> | <null> | <null> | 1000 | <null> |
| 1 | SIMPLE | t2 | ref | idx_a | idx_a | 5 | test.t3.t3_a | 1 | <null> |
+----+-------------+-------+------+---------------+--------+---------+--------------+------+--------+
此時(shí) MySQL 查詢優(yōu)化器會(huì)通過(guò)運(yùn)算來(lái)選擇成本更低的方案來(lái)執(zhí)行查詢。
雖然,上面通過(guò)物化表的方式,將IN子查詢轉(zhuǎn)換成了聯(lián)接查詢,但還是會(huì)有建立臨時(shí)表的成本,能不能不進(jìn)行物化操作直接把子查詢轉(zhuǎn)換為連接呢?直接轉(zhuǎn)換肯定不行。
-- 這里我們先構(gòu)造了3條記錄,其實(shí)也是構(gòu)造不唯一的普通索引
+------+------+------+
| id | t2_a | t2_b |
+------+------+------+
| 1100 | 1000 | 1000 |
| 1101 | 1000 | 1000 |
| 1102 | 1000 | 1000 |
+------+------+------+
-- 加限制條件where t2.id>=1100是為了減少要顯示的數(shù)據(jù)
mysql root@localhost:test> select * from t3 where t3_a in (select t2_a from t2 where t2.id>=1100);
+-----+------+------+
| id | t3_a | t3_b |
+-----+------+------+
| 101 | 1000 | 101 |
+-----+------+------+
1 row in set
Time: 0.016s
mysql root@localhost:test> select * from t3 left join t2 on t3.t3_a=t2.t2_a where t2.id>=1100;
+-----+------+------+------+------+------+
| id | t3_a | t3_b | id | t2_a | t2_b |
+-----+------+------+------+------+------+
| 101 | 1000 | 101 | 1100 | 1000 | 1000 |
| 101 | 1000 | 101 | 1101 | 1000 | 1000 |
| 101 | 1000 | 101 | 1102 | 1000 | 1000 |
+-----+------+------+------+------+------+
3 rows in set
Time: 0.018s
所以說(shuō) IN 子查詢和表聯(lián)接之間并不完全等價(jià)。而我們需要的是另一種叫做半聯(lián)接 (semi-join) 的聯(lián)接方式 :對(duì)于 t3 表的某條記錄來(lái)說(shuō),我們只關(guān)心在 t2 表中是否存在與之匹配的記錄,而不關(guān)心具體有多少條記錄與之匹配,最終的結(jié)果集中也只保留 t3 表的記錄。
注意:semi-join 只是在 MySQL 內(nèi)部采用的一種執(zhí)行子查詢的方式,MySQL 并沒(méi)有提供面向用戶的 semi-join 語(yǔ)法。
4.2.2 半聯(lián)接的實(shí)現(xiàn):
- Table pullout (子查詢中的表上拉)
當(dāng)子查詢的查詢列表處只有主鍵或者唯一索引列時(shí),可以直接把子查詢中的表上拉到外層查詢的 FROM 子句中,并把子查詢中的搜索條件合并到外層查詢的搜索條件中,比如這個(gè):
mysql root@localhost:test> select * from t3 where t3_a in (select t2_a from t2 where t2.id=999)
+-----+------+------+
| id | t3_a | t3_b |
+-----+------+------+
| 102 | 999 | 102 |
+-----+------+------+
1 row in set
Time: 0.024s
mysql root@localhost:test> select * from t3 join t2 on t3.t3_a=t2.t2_a where t2.id=999;
+-----+------+------+-----+------+------+
| id | t3_a | t3_b | id | t2_a | t2_b |
+-----+------+------+-----+------+------+
| 102 | 999 | 102 | 999 | 999 | 999 |
+-----+------+------+-----+------+------+
1 row in set
Time: 0.028s
mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2 where t2.id=999)
+----+-------------+-------+-------+---------------+---------+---------+-------+------+--------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+--------+
| 1 | SIMPLE | t2 | const | PRIMARY,idx_a | PRIMARY | 4 | const | 1 | <null> |
| 1 | SIMPLE | t3 | ref | idx_a | idx_a | 5 | const | 1 | <null> |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+--------+
- FirstMatch execution strategy (首次匹配)
FirstMatch 是一種最原始的半連接執(zhí)行方式,跟相關(guān)子查詢的執(zhí)行方式是一樣的,就是說(shuō)先取一條外層查詢的中的記錄,然后到子查詢的表中尋找符合匹配條件的記錄,如果能找到一條,則將該外層查詢的記錄放入最終的結(jié)果集并且停止查找更多匹配的記錄,如果找不到則把該外層查詢的記錄丟棄掉。然后再開始取下一條外層查詢中的記錄,重復(fù)上邊這個(gè)過(guò)程。
mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2 where t2.t2_a=1000)
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | t3 | ref | idx_a | idx_a | 5 | const | 1 | <null> |
| 1 | SIMPLE | t2 | ref | idx_a | idx_a | 5 | const | 4 | Using index; FirstMatch(t3) |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------+
- DuplicateWeedout execution strategy (重復(fù)值消除)
轉(zhuǎn)換為半連接查詢后,t3 表中的某條記錄可能在 t2 表中有多條匹配的記錄,所以該條記錄可能多次被添加到最后的結(jié)果集中,為了消除重復(fù),我們可以建立一個(gè)臨時(shí)表,并設(shè)置主鍵id,每當(dāng)某條 t3 表中的記錄要加入結(jié)果集時(shí),就首先把這條記錄的id值加入到這個(gè)臨時(shí)表里,如果添加成功,說(shuō)明之前這條 t2 表中的記錄并沒(méi)有加入最終的結(jié)果集,是一條需要的結(jié)果;如果添加失敗,說(shuō)明之前這條 s1 表中的記錄已經(jīng)加入過(guò)最終的結(jié)果集,直接把它丟棄。
- LooseScan execution strategy (松散掃描)
這種雖然是掃描索引,但只取值相同的記錄的第一條去做匹配操作的方式稱之為松散掃描。
4.2.3 半聯(lián)接的適用條件
當(dāng)然,并不是所有包含IN子查詢的查詢語(yǔ)句都可以轉(zhuǎn)換為 semi-join,只有形如這樣的查詢才可以被轉(zhuǎn)換為 semi-join:
SELECT ... FROM outer_tables?
? ? WHERE expr IN (SELECT ... FROM inner_tables ...) AND ...
-- 或者這樣的形式也可以:
SELECT ... FROM outer_tables?
? ? WHERE (oe1, oe2, ...) IN (SELECT ie1, ie2, ... FROM inner_tables ...) AND ...
用文字總結(jié)一下,只有符合下邊這些條件的子查詢才可以被轉(zhuǎn)換為 semi-join:
- 該子查詢必須是和IN語(yǔ)句組成的布爾表達(dá)式,并且在外層查詢的 WHERE 或者 ON 子句中出現(xiàn)
- 外層查詢也可以有其他的搜索條件,只不過(guò)和 IN 子查詢的搜索條件必須使用AND 連接起來(lái)
- 該子查詢必須是一個(gè)單一的查詢,不能是由若干查詢由 UNION 連接起來(lái)的形式
- 該子查詢不能包含 GROUP BY 或者 HAVING 語(yǔ)句或者聚集函數(shù)
4.2.4 轉(zhuǎn)為 EXISTS 子查詢
不管子查詢是相關(guān)的還是不相關(guān)的,都可以把 IN 子查詢嘗試轉(zhuǎn)為 EXISTS子查詢。其實(shí)對(duì)于任意一個(gè) IN 子查詢來(lái)說(shuō),都可以被轉(zhuǎn)為 EXISTS 子查詢,通用的例子如下:
outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)
-- 可以被轉(zhuǎn)換為:
EXISTS (SELECT inner_expr FROM ... WHERE subquery_where AND outer_expr=inner_expr)
當(dāng)然這個(gè)過(guò)程中有一些特殊情況,比如在 outer_expr 或者 inner_expr 值為 NULL 的情況下就比較特殊。因?yàn)橛?NULL 值作為操作數(shù)的表達(dá)式結(jié)果往往是 NULL,比方說(shuō):
mysql root@localhost:test> SELECT NULL IN (1, 2, 3);
+-------------------+
| NULL IN (1, 2, 3) |
+-------------------+
| <null> |
+-------------------+
1 row in set
而 EXISTS 子查詢的結(jié)果肯定是 TRUE 或者 FASLE 。但是現(xiàn)實(shí)中我們大部分使用 IN 子查詢的場(chǎng)景是把它放在 WHERE 或者 ON 子句中,而 WHERE 或者 ON 子句是不區(qū)分 NULL 和 FALSE 的,比方說(shuō):
mysql root@localhost:test> SELECT 1 FROM s1 WHERE NULL;
+---+
| 1 |
+---+
0 rows in set
Time: 0.016s
mysql root@localhost:test> SELECT 1 FROM s1 WHERE FALSE;
+---+
| 1 |
+---+
0 rows in set
Time: 0.033s
所以只要我們的IN子查詢是放在 WHERE 或者 ON 子句中的,那么 IN -> EXISTS 的轉(zhuǎn)換就是沒(méi)問(wèn)題的。說(shuō)了這么多,為啥要轉(zhuǎn)換呢?這是因?yàn)椴晦D(zhuǎn)換的話可能用不到索引,比方說(shuō)下邊這個(gè)查詢:
mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2 where t2.t2_a>=999) or t3_b > 1000;
+----+-------------+-------+-------+---------------+--------+---------+--------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+--------+------+--------------------------+
| 1 | PRIMARY | t3 | ALL | <null> | <null> | <null> | <null> | 1000 | Using where |
| 2 | SUBQUERY | t2 | range | idx_a | idx_a | 5 | <null> | 107 | Using where; Using index |
+----+-------------+-------+-------+---------------+--------+---------+--------+------+--------------------------+
但是將它轉(zhuǎn)為 EXISTS 子查詢后卻可以使用到索引:
mysql root@localhost:test> explain select * from t3 where exists (select 1 from t2 where t2.t2_a>=999 and t2.t2_a=t3.t3_a) or t3_b > 1000;
+----+--------------------+-------+------+---------------+--------+---------+--------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------+------+---------------+--------+---------+--------------+------+--------------------------+
| 1 | PRIMARY | t3 | ALL | <null> | <null> | <null> | <null> | 1000 | Using where |
| 2 | DEPENDENT SUBQUERY | t2 | ref | idx_a | idx_a | 5 | test.t3.t3_a | 1 | Using where; Using index |
+----+--------------------+-------+------+---------------+--------+---------+--------------+------+--------------------------+
需要注意的是,如果 IN 子查詢不滿足轉(zhuǎn)換為 semi-join 的條件,又不能轉(zhuǎn)換為物化表或者轉(zhuǎn)換為物化表的成本太大,那么它就會(huì)被轉(zhuǎn)換為 EXISTS 查詢?;蛘咿D(zhuǎn)換為物化表的成本太大,那么它就會(huì)被轉(zhuǎn)換為 EXISTS 查詢。
05總結(jié)
1. 如果IN子查詢符合轉(zhuǎn)換為 semi-join 的條件,查詢優(yōu)化器會(huì)優(yōu)先把該子查詢轉(zhuǎn)換為 semi-join,然后再考慮下邊執(zhí)行半連接的策略中哪個(gè)成本最低,
1)Table pullout
2)DuplicateWeedout
3)LooseScan
4)FirstMatch
選擇成本最低的那種執(zhí)行策略來(lái)執(zhí)行子查詢。
2. 如果IN子查詢不符合轉(zhuǎn)換為 semi-join 的條件,那么查詢優(yōu)化器會(huì)從下邊兩種策略中找出一種成本更低的方式執(zhí)行子查詢:
1)先將子查詢物化之后再執(zhí)行查詢
2)執(zhí)行 IN to EXISTS 轉(zhuǎn)換
到此這篇關(guān)于MySQL子查詢?cè)淼奈恼戮徒榻B到這了,更多相關(guān)MySQL子查詢?cè)韮?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
-
Ubuntu20下MySQL?8.0.28?安裝卸載方法圖文教程
這篇文章主要為大家詳細(xì)介紹了Ubuntu20下MySQL?8.0.28?安裝卸載方法圖文教程,文中安裝步驟介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下 2022-04-04
-
MySQL表LEFT JOIN左連接與RIGHT JOIN右連接的實(shí)例教程
這篇文章主要介紹了MySQL表LEFT JOIN左連接與RIGHT JOIN右連接的實(shí)例教程,表連接操作是MySQL入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下 2015-12-12
-
Navicat使用報(bào)2059錯(cuò)誤的兩種解決方案
Navicat是一款流行的數(shù)據(jù)庫(kù)管理工具,而MySQL則是其中的一種數(shù)據(jù)庫(kù)軟件,下面這篇文章主要給大家介紹了關(guān)于Navicat使用報(bào)2059錯(cuò)誤的兩種解決方案,需要的朋友可以參考下 2023-11-11
-
mysql如何實(shí)現(xiàn)最大連接數(shù)
云服務(wù)器同時(shí)最大連接數(shù),也就是說(shuō)可以有一千個(gè)用戶,那么mysql如何實(shí)現(xiàn)最大連接數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下 2021-12-12
-
MySQL優(yōu)化案例系列-mysql分頁(yè)優(yōu)化
這篇文章主要介紹了MySQL優(yōu)化案例系列-mysql分頁(yè)優(yōu)化,需要的朋友可以參考下 2016-08-08
-
mysql簡(jiǎn)單實(shí)現(xiàn)查詢結(jié)果添加序列號(hào)的方法
這篇文章主要介紹了mysql簡(jiǎn)單實(shí)現(xiàn)查詢結(jié)果添加序列號(hào)的方法,結(jié)合實(shí)例形式演示了2種查詢結(jié)果添加序列號(hào)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下 2016-06-06
-
分享CentOS下MySQL最新版本5.6.13源碼安裝過(guò)程
這篇文章主要介紹了CentOS下安裝MySQL最新版本5.6.13過(guò)程分享,需要的朋友可以參考下 2014-02-02
最新評(píng)論
01前言
子查詢,通俗解釋就是查詢語(yǔ)句中嵌套著另一個(gè)查詢語(yǔ)句。相信日常工作中接觸到 MySQL 的同學(xué)都了解或使用過(guò)子查詢,但是具體它是怎樣實(shí)現(xiàn)的呢? 查詢效率如何? 這些恐怕好多人就不太清楚了,下面咱們就圍繞這兩個(gè)問(wèn)題共同探索一下。
02準(zhǔn)備內(nèi)容
這里我們需要用到3個(gè)表,這3個(gè)表都有一個(gè)主鍵索引 id 和一個(gè)索引 a,字段 b 上無(wú)索引。存儲(chǔ)過(guò)程 idata() 往表 t1 里插入的是 100 行數(shù)據(jù),表 t2、t3 里插入了 1000 行數(shù)據(jù)。建表語(yǔ)句如下:
CREATE TABLE `t1` ( `id` INT ( 11 ) NOT NULL, `t1_a` INT ( 11 ) DEFAULT NULL, `t1_b` INT ( 11 ) DEFAULT NULL, PRIMARY KEY ( `id` ), KEY `idx_a` ( `t1_a` )) ENGINE = INNODB; CREATE TABLE `t2` ( `id` INT ( 11 ) NOT NULL, `t2_a` INT ( 11 ) DEFAULT NULL, `t2_b` INT ( 11 ) DEFAULT NULL, PRIMARY KEY ( `id` ), KEY `idx_a` ( `t2_a` )) ENGINE = INNODB; CREATE TABLE `t3` ( `id` INT ( 11 ) NOT NULL, `t3_a` INT ( 11 ) DEFAULT NULL, `t3_b` INT ( 11 ) DEFAULT NULL, PRIMARY KEY ( `id` ), KEY `idx_a` ( `t3_a` )) ENGINE = INNODB; -- 向t1添加100條數(shù)據(jù) -- drop procedure idata; delimiter ;; create procedure idata() begin declare i int; set i=1; while(i<=100)do insert into t1 values(i, i, i); set i=i+1; end while; end;; delimiter ; call idata(); -- 向t2添加1000條數(shù)據(jù) drop procedure idata; delimiter ;; create procedure idata() begin declare i int; set i=101; while(i<=1100)do insert into t2 values(i, i, i); set i=i+1; end while; end;; delimiter ; call idata(); -- 向t2添加1000條數(shù)據(jù),且t3_a列的值為倒敘 drop procedure idata; delimiter ;; create procedure idata() begin declare i int; set i=101; while(i<=1100)do insert into t3 values(i, 1101-i, i); set i=i+1; end while; end;; delimiter ; call idata();
03子查詢的語(yǔ)法形式和分類
3.1 語(yǔ)法形式
子查詢的語(yǔ)法規(guī)定,子查詢可以在一個(gè)外層查詢的各種位置出現(xiàn),這里我們只介紹常用的幾個(gè):
3.1.1 FROM子句中
如 SELECT m, n FROM (SELECT m2 + 1 AS m, n2 AS n FROM t2 WHERE m2 > 2) AS t;
這個(gè)例子中的子查詢是:(SELECT m2 + 1 AS m, n2 AS n FROM t2 WHERE m2 > 2
),這個(gè)放在FROM子句中的子查詢相當(dāng)于一個(gè)表,但又和我們平常使用的表有點(diǎn)兒不一樣,這種由子查詢結(jié)果集組成的表稱之為派生表。
3.1.2 WHERE或IN子句中
如:SELECT * FROM t1 WHERE m1 = (SELECT MIN(m2) FROM t2);
SELECT * FROM t1 WHERE m1 IN (SELECT m2 FROM t2);
其他的還有 SELECT 子句中,ORDER BY 子句中,GROUP BY 子句中,雖然語(yǔ)法支持,但沒(méi)啥意義,就不嘮叨這些情況了。
3.2 分類
3.2.1 按返回的結(jié)果集區(qū)分
標(biāo)量子查詢,只返回一個(gè)單一值的子查詢稱之為標(biāo)量子查詢,比如:
SELECT * FROM t1 WHERE m1 = (SELECT m1 FROM t1 LIMIT 1);
行子查詢,就是只返回一條記錄的子查詢,不過(guò)這條記錄需要包含多個(gè)列(只包含一個(gè)列就成了標(biāo)量子查詢了)。比如:SELECT * FROM t1 WHERE (m1, n1) = (SELECT m2, n2 FROM t2 LIMIT 1);
列子查詢,就是只返回一個(gè)列的數(shù)據(jù),不過(guò)這個(gè)列的數(shù)據(jù)需要包含多條記錄(只包含一條記錄就成了標(biāo)量子查詢了)。比如:SELECT * FROM t1 WHERE m1 IN (SELECT m2 FROM t2);
表子查詢,就是子查詢的結(jié)果既包含很多條記錄,又包含很多個(gè)列,比如:
SELECT * FROM t1 WHERE (m1, n1) IN (SELECT m2, n2 FROM t2);
其中的 (SELECT m2, n2 FROM t2) 就是一個(gè)表子查詢,這里需要和行子查詢對(duì)比一下,行子查詢中我們用了 LIMIT 1 來(lái)保證子查詢的結(jié)果只有一條記錄。
3.2.2 按與外層查詢關(guān)系來(lái)區(qū)分
不相關(guān)子查詢,就是子查詢可以單獨(dú)運(yùn)行出結(jié)果,而不依賴于外層查詢的值,我們就可以把這個(gè)子查詢稱之為不相關(guān)子查詢。
相關(guān)子查詢,就是需要依賴于外層查詢的值的子查詢稱之為相關(guān)子查詢。比如:SELECT * FROM t1 WHERE m1 IN (SELECT m2 FROM t2 WHERE n1 = n2);
04子查詢?cè)贛ySQL中是怎么執(zhí)行的
4.1 標(biāo)量子查詢、行子查詢的執(zhí)行方式
4.1.1 不相關(guān)子查詢
如下邊這個(gè)查詢語(yǔ)句:
mysql root@localhost:test> explain select * from t1 where t1_a = (select t2_a from t2 limit 1); +----+-------------+-------+-------+---------------+-------+---------+--------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+-------+---------+--------+------+-------------+ | 1 | PRIMARY | t1 | ref | idx_a | idx_a | 5 | const | 1 | Using where | | 2 | SUBQUERY | t2 | index | <null> | idx_a | 5 | <null> | 1000 | Using index | +----+-------------+-------+-------+---------------+-------+---------+--------+------+-------------+
它的執(zhí)行方式:
先單獨(dú)執(zhí)行 (select t2_a from t2 limit 1) 這個(gè)子查詢。
然后在將上一步子查詢得到的結(jié)果當(dāng)作外層查詢的參數(shù)再執(zhí)行外層查詢 select * from t1 where t1_a = ...。
也就是說(shuō),對(duì)于包含不相關(guān)的標(biāo)量子查詢或者行子查詢的查詢語(yǔ)句來(lái)說(shuō),MySQL 會(huì)分別獨(dú)立的執(zhí)行外層查詢和子查詢,就當(dāng)作兩個(gè)單表查詢就好了。
4.1.2 相關(guān)的子查詢
比如下邊這個(gè)查詢:
mysql root@localhost:test> explain select * from t1 where t1_a = (select t2_a from t2 where t1.t1_b=t2.t2_b limit 1); +----+--------------------+-------+------+---------------+--------+---------+--------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+------+---------------+--------+---------+--------+------+-------------+ | 1 | PRIMARY | t1 | ALL | <null> | <null> | <null> | <null> | 100 | Using where | | 2 | DEPENDENT SUBQUERY | t2 | ALL | <null> | <null> | <null> | <null> | 1000 | Using where | +----+--------------------+-------+------+---------------+--------+---------+--------+------+-------------+
它的執(zhí)行方式就是這樣的:
先從外層查詢中獲取一條記錄,本例中也就是先從 t1 表中獲取一條記錄。
然后從上一步驟中獲取的那條記錄中找出子查詢中涉及到的值,就是 t1 表中找出 t1.t1_b 列的值,然后執(zhí)行子查詢。
最后根據(jù)子查詢的查詢結(jié)果來(lái)檢測(cè)外層查詢 WHERE 子句的條件是否成立,如果成立,就把外層查詢的那條記錄加入到結(jié)果集,否則就丟棄。
然后重復(fù)以上步驟,直到 t1 中的記錄全部匹配完。
4.2 IN子查詢
4.2.1 物化
如果子查詢的結(jié)果集中的記錄條數(shù)很少,那么把子查詢和外層查詢分別看成兩個(gè)單獨(dú)的單表查詢效率還是蠻高的,但是如果單獨(dú)執(zhí)行子查詢后的結(jié)果集太多的話,就會(huì)導(dǎo)致這些問(wèn)題:
結(jié)果集太多,可能內(nèi)存中都放不下~
對(duì)于外層查詢來(lái)說(shuō),如果子查詢的結(jié)果集太多,那就意味著 IN 子句中的參數(shù)特別多,這就導(dǎo)致:
1)無(wú)法有效的使用索引,只能對(duì)外層查詢進(jìn)行全表掃描。
2)在對(duì)外層查詢執(zhí)行全表掃描時(shí),由于 IN 子句中的參數(shù)太多,這會(huì)導(dǎo)致檢測(cè)一條記錄是否符合和 IN 子句中的參數(shù)匹配花費(fèi)的時(shí)間太長(zhǎng)。
于是就有:不直接將不相關(guān)子查詢的結(jié)果集當(dāng)作外層查詢的參數(shù),而是將該結(jié)果集寫入一個(gè)臨時(shí)表里。寫入臨時(shí)表的過(guò)程是這樣的:
該臨時(shí)表的列就是子查詢結(jié)果集中的列。
寫入臨時(shí)表的記錄會(huì)被去重,讓臨時(shí)表變得更小,更省地方。
一般情況下子查詢結(jié)果集不大時(shí),就會(huì)為它建立基于內(nèi)存的使用 Memory 存儲(chǔ)引擎的臨時(shí)表,而且會(huì)為該表建立哈希索引。
如果子查詢的結(jié)果集非常大,超過(guò)了系統(tǒng)變量 tmp_table_size或者 max_heap_table_size,臨時(shí)表會(huì)轉(zhuǎn)而使用基于磁盤的存儲(chǔ)引擎來(lái)保存結(jié)果集中的記錄,索引類型也對(duì)應(yīng)轉(zhuǎn)變?yōu)?B+ 樹索引。
這個(gè)將子查詢結(jié)果集中的記錄保存到臨時(shí)表的過(guò)程稱之為物化(Materialize)。為了方便起見,我們就把那個(gè)存儲(chǔ)子查詢結(jié)果集的臨時(shí)表稱之為物化表。正因?yàn)槲锘碇械挠涗浂冀⒘怂饕ɑ趦?nèi)存的物化表有哈希索引,基于磁盤的有 B+ 樹索引),通過(guò)索引執(zhí)行IN語(yǔ)句判斷某個(gè)操作數(shù)在不在子查詢結(jié)果集中變得非常快,從而提升了子查詢語(yǔ)句的性能。
mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2); +----+--------------+-------------+--------+---------------+------------+---------+--------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------+-------------+--------+---------------+------------+---------+--------------+------+-------------+ | 1 | SIMPLE | t3 | ALL | idx_a | <null> | <null> | <null> | 1000 | Using where | | 1 | SIMPLE | <subquery2> | eq_ref | <auto_key> | <auto_key> | 5 | test.t3.t3_a | 1 | <null> | | 2 | MATERIALIZED | t2 | index | idx_a | idx_a | 5 | <null> | 1000 | Using index | +----+--------------+-------------+--------+---------------+------------+---------+--------------+------+-------------+
其實(shí)上邊的查詢就相當(dāng)于表 t3 和子查詢物化表進(jìn)行內(nèi)連接:
mysql root@localhost:test> explain select * from t3 left join t2 on t3.t3_a=t2.t2_a; +----+-------------+-------+------+---------------+--------+---------+--------------+------+--------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+--------+---------+--------------+------+--------+ | 1 | SIMPLE | t3 | ALL | <null> | <null> | <null> | <null> | 1000 | <null> | | 1 | SIMPLE | t2 | ref | idx_a | idx_a | 5 | test.t3.t3_a | 1 | <null> | +----+-------------+-------+------+---------------+--------+---------+--------------+------+--------+
此時(shí) MySQL 查詢優(yōu)化器會(huì)通過(guò)運(yùn)算來(lái)選擇成本更低的方案來(lái)執(zhí)行查詢。
雖然,上面通過(guò)物化表的方式,將IN子查詢轉(zhuǎn)換成了聯(lián)接查詢,但還是會(huì)有建立臨時(shí)表的成本,能不能不進(jìn)行物化操作直接把子查詢轉(zhuǎn)換為連接呢?直接轉(zhuǎn)換肯定不行。
-- 這里我們先構(gòu)造了3條記錄,其實(shí)也是構(gòu)造不唯一的普通索引
+------+------+------+ | id | t2_a | t2_b | +------+------+------+ | 1100 | 1000 | 1000 | | 1101 | 1000 | 1000 | | 1102 | 1000 | 1000 | +------+------+------+ -- 加限制條件where t2.id>=1100是為了減少要顯示的數(shù)據(jù) mysql root@localhost:test> select * from t3 where t3_a in (select t2_a from t2 where t2.id>=1100); +-----+------+------+ | id | t3_a | t3_b | +-----+------+------+ | 101 | 1000 | 101 | +-----+------+------+ 1 row in set Time: 0.016s mysql root@localhost:test> select * from t3 left join t2 on t3.t3_a=t2.t2_a where t2.id>=1100; +-----+------+------+------+------+------+ | id | t3_a | t3_b | id | t2_a | t2_b | +-----+------+------+------+------+------+ | 101 | 1000 | 101 | 1100 | 1000 | 1000 | | 101 | 1000 | 101 | 1101 | 1000 | 1000 | | 101 | 1000 | 101 | 1102 | 1000 | 1000 | +-----+------+------+------+------+------+ 3 rows in set Time: 0.018s
所以說(shuō) IN 子查詢和表聯(lián)接之間并不完全等價(jià)。而我們需要的是另一種叫做半聯(lián)接 (semi-join) 的聯(lián)接方式 :對(duì)于 t3 表的某條記錄來(lái)說(shuō),我們只關(guān)心在 t2 表中是否存在與之匹配的記錄,而不關(guān)心具體有多少條記錄與之匹配,最終的結(jié)果集中也只保留 t3 表的記錄。
注意:semi-join 只是在 MySQL 內(nèi)部采用的一種執(zhí)行子查詢的方式,MySQL 并沒(méi)有提供面向用戶的 semi-join 語(yǔ)法。
4.2.2 半聯(lián)接的實(shí)現(xiàn):
- Table pullout (子查詢中的表上拉)
當(dāng)子查詢的查詢列表處只有主鍵或者唯一索引列時(shí),可以直接把子查詢中的表上拉到外層查詢的 FROM 子句中,并把子查詢中的搜索條件合并到外層查詢的搜索條件中,比如這個(gè):
mysql root@localhost:test> select * from t3 where t3_a in (select t2_a from t2 where t2.id=999) +-----+------+------+ | id | t3_a | t3_b | +-----+------+------+ | 102 | 999 | 102 | +-----+------+------+ 1 row in set Time: 0.024s mysql root@localhost:test> select * from t3 join t2 on t3.t3_a=t2.t2_a where t2.id=999; +-----+------+------+-----+------+------+ | id | t3_a | t3_b | id | t2_a | t2_b | +-----+------+------+-----+------+------+ | 102 | 999 | 102 | 999 | 999 | 999 | +-----+------+------+-----+------+------+ 1 row in set Time: 0.028s mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2 where t2.id=999) +----+-------------+-------+-------+---------------+---------+---------+-------+------+--------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+-------+------+--------+ | 1 | SIMPLE | t2 | const | PRIMARY,idx_a | PRIMARY | 4 | const | 1 | <null> | | 1 | SIMPLE | t3 | ref | idx_a | idx_a | 5 | const | 1 | <null> | +----+-------------+-------+-------+---------------+---------+---------+-------+------+--------+
- FirstMatch execution strategy (首次匹配)
FirstMatch 是一種最原始的半連接執(zhí)行方式,跟相關(guān)子查詢的執(zhí)行方式是一樣的,就是說(shuō)先取一條外層查詢的中的記錄,然后到子查詢的表中尋找符合匹配條件的記錄,如果能找到一條,則將該外層查詢的記錄放入最終的結(jié)果集并且停止查找更多匹配的記錄,如果找不到則把該外層查詢的記錄丟棄掉。然后再開始取下一條外層查詢中的記錄,重復(fù)上邊這個(gè)過(guò)程。
mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2 where t2.t2_a=1000) +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------+ | 1 | SIMPLE | t3 | ref | idx_a | idx_a | 5 | const | 1 | <null> | | 1 | SIMPLE | t2 | ref | idx_a | idx_a | 5 | const | 4 | Using index; FirstMatch(t3) | +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------+
- DuplicateWeedout execution strategy (重復(fù)值消除)
轉(zhuǎn)換為半連接查詢后,t3 表中的某條記錄可能在 t2 表中有多條匹配的記錄,所以該條記錄可能多次被添加到最后的結(jié)果集中,為了消除重復(fù),我們可以建立一個(gè)臨時(shí)表,并設(shè)置主鍵id,每當(dāng)某條 t3 表中的記錄要加入結(jié)果集時(shí),就首先把這條記錄的id值加入到這個(gè)臨時(shí)表里,如果添加成功,說(shuō)明之前這條 t2 表中的記錄并沒(méi)有加入最終的結(jié)果集,是一條需要的結(jié)果;如果添加失敗,說(shuō)明之前這條 s1 表中的記錄已經(jīng)加入過(guò)最終的結(jié)果集,直接把它丟棄。
- LooseScan execution strategy (松散掃描)
這種雖然是掃描索引,但只取值相同的記錄的第一條去做匹配操作的方式稱之為松散掃描。
4.2.3 半聯(lián)接的適用條件
當(dāng)然,并不是所有包含IN子查詢的查詢語(yǔ)句都可以轉(zhuǎn)換為 semi-join,只有形如這樣的查詢才可以被轉(zhuǎn)換為 semi-join:
SELECT ... FROM outer_tables? ? ? WHERE expr IN (SELECT ... FROM inner_tables ...) AND ... -- 或者這樣的形式也可以: SELECT ... FROM outer_tables? ? ? WHERE (oe1, oe2, ...) IN (SELECT ie1, ie2, ... FROM inner_tables ...) AND ...
用文字總結(jié)一下,只有符合下邊這些條件的子查詢才可以被轉(zhuǎn)換為 semi-join:
- 該子查詢必須是和IN語(yǔ)句組成的布爾表達(dá)式,并且在外層查詢的 WHERE 或者 ON 子句中出現(xiàn)
- 外層查詢也可以有其他的搜索條件,只不過(guò)和 IN 子查詢的搜索條件必須使用AND 連接起來(lái)
- 該子查詢必須是一個(gè)單一的查詢,不能是由若干查詢由 UNION 連接起來(lái)的形式
- 該子查詢不能包含 GROUP BY 或者 HAVING 語(yǔ)句或者聚集函數(shù)
4.2.4 轉(zhuǎn)為 EXISTS 子查詢
不管子查詢是相關(guān)的還是不相關(guān)的,都可以把 IN 子查詢嘗試轉(zhuǎn)為 EXISTS子查詢。其實(shí)對(duì)于任意一個(gè) IN 子查詢來(lái)說(shuō),都可以被轉(zhuǎn)為 EXISTS 子查詢,通用的例子如下:
outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where) -- 可以被轉(zhuǎn)換為: EXISTS (SELECT inner_expr FROM ... WHERE subquery_where AND outer_expr=inner_expr)
當(dāng)然這個(gè)過(guò)程中有一些特殊情況,比如在 outer_expr 或者 inner_expr 值為 NULL 的情況下就比較特殊。因?yàn)橛?NULL 值作為操作數(shù)的表達(dá)式結(jié)果往往是 NULL,比方說(shuō):
mysql root@localhost:test> SELECT NULL IN (1, 2, 3); +-------------------+ | NULL IN (1, 2, 3) | +-------------------+ | <null> | +-------------------+ 1 row in set
而 EXISTS 子查詢的結(jié)果肯定是 TRUE 或者 FASLE 。但是現(xiàn)實(shí)中我們大部分使用 IN 子查詢的場(chǎng)景是把它放在 WHERE 或者 ON 子句中,而 WHERE 或者 ON 子句是不區(qū)分 NULL 和 FALSE 的,比方說(shuō):
mysql root@localhost:test> SELECT 1 FROM s1 WHERE NULL; +---+ | 1 | +---+ 0 rows in set Time: 0.016s mysql root@localhost:test> SELECT 1 FROM s1 WHERE FALSE; +---+ | 1 | +---+ 0 rows in set Time: 0.033s
所以只要我們的IN子查詢是放在 WHERE 或者 ON 子句中的,那么 IN -> EXISTS 的轉(zhuǎn)換就是沒(méi)問(wèn)題的。說(shuō)了這么多,為啥要轉(zhuǎn)換呢?這是因?yàn)椴晦D(zhuǎn)換的話可能用不到索引,比方說(shuō)下邊這個(gè)查詢:
mysql root@localhost:test> explain select * from t3 where t3_a in (select t2_a from t2 where t2.t2_a>=999) or t3_b > 1000; +----+-------------+-------+-------+---------------+--------+---------+--------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+--------+---------+--------+------+--------------------------+ | 1 | PRIMARY | t3 | ALL | <null> | <null> | <null> | <null> | 1000 | Using where | | 2 | SUBQUERY | t2 | range | idx_a | idx_a | 5 | <null> | 107 | Using where; Using index | +----+-------------+-------+-------+---------------+--------+---------+--------+------+--------------------------+
但是將它轉(zhuǎn)為 EXISTS 子查詢后卻可以使用到索引:
mysql root@localhost:test> explain select * from t3 where exists (select 1 from t2 where t2.t2_a>=999 and t2.t2_a=t3.t3_a) or t3_b > 1000; +----+--------------------+-------+------+---------------+--------+---------+--------------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+------+---------------+--------+---------+--------------+------+--------------------------+ | 1 | PRIMARY | t3 | ALL | <null> | <null> | <null> | <null> | 1000 | Using where | | 2 | DEPENDENT SUBQUERY | t2 | ref | idx_a | idx_a | 5 | test.t3.t3_a | 1 | Using where; Using index | +----+--------------------+-------+------+---------------+--------+---------+--------------+------+--------------------------+
需要注意的是,如果 IN 子查詢不滿足轉(zhuǎn)換為 semi-join 的條件,又不能轉(zhuǎn)換為物化表或者轉(zhuǎn)換為物化表的成本太大,那么它就會(huì)被轉(zhuǎn)換為 EXISTS 查詢?;蛘咿D(zhuǎn)換為物化表的成本太大,那么它就會(huì)被轉(zhuǎn)換為 EXISTS 查詢。
05總結(jié)
1. 如果IN子查詢符合轉(zhuǎn)換為 semi-join 的條件,查詢優(yōu)化器會(huì)優(yōu)先把該子查詢轉(zhuǎn)換為 semi-join,然后再考慮下邊執(zhí)行半連接的策略中哪個(gè)成本最低,
1)Table pullout
2)DuplicateWeedout
3)LooseScan
4)FirstMatch
選擇成本最低的那種執(zhí)行策略來(lái)執(zhí)行子查詢。
2. 如果IN子查詢不符合轉(zhuǎn)換為 semi-join 的條件,那么查詢優(yōu)化器會(huì)從下邊兩種策略中找出一種成本更低的方式執(zhí)行子查詢:
1)先將子查詢物化之后再執(zhí)行查詢
2)執(zhí)行 IN to EXISTS 轉(zhuǎn)換
到此這篇關(guān)于MySQL子查詢?cè)淼奈恼戮徒榻B到這了,更多相關(guān)MySQL子查詢?cè)韮?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Ubuntu20下MySQL?8.0.28?安裝卸載方法圖文教程
這篇文章主要為大家詳細(xì)介紹了Ubuntu20下MySQL?8.0.28?安裝卸載方法圖文教程,文中安裝步驟介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04MySQL表LEFT JOIN左連接與RIGHT JOIN右連接的實(shí)例教程
這篇文章主要介紹了MySQL表LEFT JOIN左連接與RIGHT JOIN右連接的實(shí)例教程,表連接操作是MySQL入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-12-12Navicat使用報(bào)2059錯(cuò)誤的兩種解決方案
Navicat是一款流行的數(shù)據(jù)庫(kù)管理工具,而MySQL則是其中的一種數(shù)據(jù)庫(kù)軟件,下面這篇文章主要給大家介紹了關(guān)于Navicat使用報(bào)2059錯(cuò)誤的兩種解決方案,需要的朋友可以參考下2023-11-11mysql如何實(shí)現(xiàn)最大連接數(shù)
云服務(wù)器同時(shí)最大連接數(shù),也就是說(shuō)可以有一千個(gè)用戶,那么mysql如何實(shí)現(xiàn)最大連接數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12MySQL優(yōu)化案例系列-mysql分頁(yè)優(yōu)化
這篇文章主要介紹了MySQL優(yōu)化案例系列-mysql分頁(yè)優(yōu)化,需要的朋友可以參考下2016-08-08mysql簡(jiǎn)單實(shí)現(xiàn)查詢結(jié)果添加序列號(hào)的方法
這篇文章主要介紹了mysql簡(jiǎn)單實(shí)現(xiàn)查詢結(jié)果添加序列號(hào)的方法,結(jié)合實(shí)例形式演示了2種查詢結(jié)果添加序列號(hào)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06分享CentOS下MySQL最新版本5.6.13源碼安裝過(guò)程
這篇文章主要介紹了CentOS下安裝MySQL最新版本5.6.13過(guò)程分享,需要的朋友可以參考下2014-02-02