MySQL語句,in子查詢語法錯誤,卻不影響整個查詢的正確性
阿新 • • 發佈:2020-09-17
問題重現
比如我有兩個表
mysql> select * from user; +----+------+-----+ | id | name | age | +----+------+-----+ | 1 | A | 9 | | 2 | B | 11 | | 3 | C | 15 | | 4 | D | 13 | +----+------+-----+ 4 rows in set (0.03 sec) mysql> select * from blacklist; +----+------+------+ | id | name | type |+----+------+------+ | 1 | B | 1 | | 2 | C | 0 | | 3 | D | 1 | +----+------+------+ 3 rows in set (0.03 sec)
我要從裡面選取黑名單並且年齡大於10的。可是我手誤,把age條件加在了in查詢裡,可是黑名單沒有age欄位。
select * from user where name in (select name from blacklist where type = 1 and age > 10);
單獨執行子查詢是有很明顯的問題的:
mysql>select name from blacklist where type = 1 and age > 10; 1054 - Unknown column 'age' in 'where clause'
可是我如果執行整個查詢:
mysql> select * from user where name in (select name from blacklist where type = 1 and age > 10); +----+------+-----+ | id | name | age | +----+------+-----+ | 2 | B | 11 || 4 | D | 13 | +----+------+-----+ 2 rows in set (0.02 sec)
沒問題!並且還是正確的結果!!
為什麼?
我們explain一下
mysql> explain select * from user where name in (select name from blacklist where type = 1 and age > 10); +----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------------------------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------------------------------------------------------------+ | 1 | SIMPLE | blacklist | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Start temporary | | 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where; End temporary; Using join buffer (Block Nested Loop) | +----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------------------------------------------------------------+ 2 rows in set (0.02 sec)
沒看出什麼端倪,繼續執行
mysql> show warnings; +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1276 | Field or reference 'mytest.user.age' of SELECT #2 was resolved in SELECT #1 | | Note | 1003 | /* select#1 */ select `mytest`.`user`.`id` AS `id`,`mytest`.`user`.`name` AS `name`,`mytest`.`user`.`age` AS `age` from `mytest`.`user` semi join (`mytest`.`blacklist`) where ((`mytest`.`user`.`name` = `mytest`.`blacklist`.`name`) and (`mytest`.`blacklist`.`type` = 1) and (`mytest`.`user`.`age` > 10)) | +-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 2 rows in set (0.03 sec)
這裡能看見資料庫優化之後的語句
/* select#1 */ SELECT `mytest`.`user`.`id` AS `id`, `mytest`.`user`.`name` AS `name`, `mytest`.`user`.`age` AS `age` FROM `mytest`.`user` semi JOIN ( `mytest`.`blacklist` ) WHERE ( (`mytest`.`user`.`name` = `mytest`.`blacklist`.`name` ) AND ( `mytest`.`blacklist`.`type` = 1 ) AND ( `mytest`.`user`.`age` > 10 ) )
可以看出,資料庫把原來的查詢改成了join查詢,每個欄位都指向了正確的表。
解決
當然是把語句改正唄,這屬於手誤造成的。
mysql> select * from user where name in (select name from blacklist where type = 1) and age > 10; +----+------+-----+ | id | name | age | +----+------+-----+ | 2 | B | 11 | | 4 | D | 13 | +----+------+-----+ 2 rows in set (0.03 sec)
但是其次,我們看到in裡面如果有語法錯誤,是沒有暴露的。我們把in改成exists試一下:
mysql> select * from user where EXISTS (select name from blacklist where type = 1 and age > 10); +----+------+-----+ | id | name | age | +----+------+-----+ | 2 | B | 11 | | 3 | C | 15 | | 4 | D | 13 | +----+------+-----+ 3 rows in set (0.05 sec)
結果不對,並且EXISTS返回的是一個布林值,只要其中的子查詢返回行數,where條件即成立。所以,selectname換成select 1也是成立的。
mysql> select * from user where EXISTS (select 1 from blacklist where type = 1 and age > 10); +----+------+-----+ | id | name | age | +----+------+-----+ | 2 | B | 11 | | 3 | C | 15 | | 4 | D | 13 | +----+------+-----+ 3 rows in set (0.04 sec)
如果使EXISTS不成立,則
mysql> select * from user where EXISTS (select name from blacklist where type = 11 and age > 10); Empty set
explain一下
mysql> explain select * from user where EXISTS (select name from blacklist where type = 1 and age > 10); +----+--------------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | PRIMARY | user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using where | | 2 | DEPENDENT SUBQUERY | blacklist | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where | +----+--------------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+ 2 rows in set (0.06 sec)
檢視warnings
mysql> show warnings; +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1276 | Field or reference 'mytest.user.age' of SELECT #2 was resolved in SELECT #1 | | Note | 1003 | /* select#1 */ select `mytest`.`user`.`id` AS `id`,`mytest`.`user`.`name` AS `name`,`mytest`.`user`.`age` AS `age` from `mytest`.`user` where exists(/* select#2 */ select `mytest`.`blacklist`.`name` from `mytest`.`blacklist` where ((`mytest`.`blacklist`.`type` = 1) and (`mytest`.`user`.`age` > 10))) | +-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 2 rows in set (0.06 sec)
優化之後的語句
/* select#1 */ SELECT `mytest`.`user`.`id` AS `id`, `mytest`.`user`.`name` AS `name`, `mytest`.`user`.`age` AS `age` FROM `mytest`.`user` WHERE EXISTS ( /* select#2 */ SELECT `mytest`.`blacklist`.`name` FROM `mytest`.`blacklist` WHERE (( `mytest`.`blacklist`.`type` = 1 ) AND ( `mytest`.`user`.`age` > 10 )))
可以看到,使用了exists,同樣優化掉了低階錯誤。但是exists不能返回我們需要的資料。
IN和Exists的區別
Exists:先執行外部查詢語句,然後在執行子查詢,子查詢中它每次都會去執行資料庫的查詢,執行次數等於外查詢的資料數量。
In:先查詢 in()子查詢的資料(1次),並且將資料放進記憶體裡(不需要多次查詢),然後外部查詢的表再根據查詢的結果進行查詢過濾,最後返回結果。
In 是把外表和內表作hash 連線,而exists是對外表作loop迴圈,每次loop迴圈再對內表進行查詢。
參考:https://blog.csdn.net/qq_27409289/article/details/85963089