MySQL優化案例---半連線(semi join)優化方式 導致的查詢效能低下
阿新 • • 發佈:2019-02-06
MySQL V5.6.x/5.7.x SQL查詢效能問題
一 簡單建立一表,並使用儲存過程插入一部分資料
CREATE TABLE users (
user_id int(11) unsigned NOT NULL,
user_name varchar(64) DEFAULT NULL,
PRIMARY KEY (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DELIMITER $$
DROP PROCEDURE IF EXISTS proc_auto_insertdata$$
CREATE PROCEDURE proc_auto_insertdata()
BEGIN
DECLARE init_data INTEGER DEFAULT 1;
WHILE init_data <= 20000 DO
INSERT INTO users VALUES(init_data, CONCAT('使用者-',init_data));
SET init_data = init_data + 1;
END WHILE;
END$$
DELIMITER ;
CALL proc_auto_insertdata();
二 執行如下查詢
Q1:
SELECT u.user_id, u.user_name FROM users u
WHERE u.user_name IN (SELECT t.user_name FROM users t WHERE t.user_id < 2000);
Q2: Q2比Q1只多了一個使用OR子句連線的條件,資料中沒有滿足此條件的資料)
SELECT u.user_id, u.user_name FROM users u
WHERE
(u.user_name IN (SELECT t.user_name FROM users t WHERE t.user_id < 2000) ));
三 實際執行結果
對Q1和Q2稍加改造,目的是避免有大量的查詢結果輸出. 目標列使用COUNT()函式替換.
mysql> SELECT COUNT(u.user_id) FROM users u
-> WHERE u.user_name IN (SELECT t.user_name FROM users t WHERE t.user_id < 2000);
+------------------+
| COUNT(u.user_id) |
+------------------+
| 1999 |
+------------------+
1 row in set ()
mysql> SELECT COUNT(u.user_id) FROM users u
-> WHERE (u.user_name IN (SELECT t.user_name FROM users t WHERE t.user_id < 2000) OR
-> u.user_name IN (SELECT t.user_name FROM users t WHERE t.user_id < -1));
+------------------+
| COUNT(u.user_id) |
+------------------+
| 1999 |
+------------------+
1 row in set ()
看紅色字型,所耗費的時間,. 為什麼?
四 探索原因
第一招: 察看執行計劃
對比執行計劃,發現Q1使用了"MATERIALIZED"物化方式儲存子查詢的臨時結果. 是不是物化導致了Q1慢呢?
mysql> EXPLAIN SELECT COUNT(u.user_id) FROM users u
-> WHERE (u.user_name IN (SELECT t.user_name FROM users t WHERE t.user_id < 2000) OR
-> u.user_name IN (SELECT t.user_name FROM users t WHERE t.user_id < -1));
+----+-------------+-------+-------+---------+---------+-------+----------+--------------------------------+
| id | select_type | table | type | key | key_len | rows | filtered | Extra |
+----+-------------+-------+-------+---------+---------+-------+----------+--------------------------------+
| 1 | PRIMARY | u | ALL | NULL | NULL | 19761 | 100.00 | Using where |
| 3 | | NULL | NULL | NULL | NULL | NULL | NULL | no matching row in const table |
| 2 | | t | range | PRIMARY | 4 | 1999 | 100.00 | Using where |
+----+-------------+-------+-------+---------+---------+-------+----------+--------------------------------+
3 rows in set, 1 warning (0.00 sec)
第二招: 察看IO
Handler_read_key
The number of requests to read a row based on a key. If this value is high, it is a good indication that your tables are properly indexed for your queries.
五 新的疑問,再次探索
之下如下操作,注意show warnings技巧的使用。查詢結果作了形式的調整,便於閱讀。
Q2表明,首先使用了臨時表,但是和Q1不同的是,子查詢沒有被上拉優化。
但是,MySQL對於臨時表的使用,會自動建立索引,所以我們能看到在“”上執行了“”。這就是Q2快於Q1的原因。也是為什麼Q2的索引讀計數器的值較大的原因。
問題:半連線優化
六 繼續探索
七 結論
1. Q1使用了物化+半連線優化, Q2是子查詢,但沒有使用半連線優化, 可見MySQL中半連線優化的效率未必高
2. 似乎物化的子查詢用半連線上拉,MySQL的判斷條件還是存在一點兒問題。