1. 程式人生 > >MySQL優化案例---半連線(semi join)優化方式 導致的查詢效能低下

MySQL優化案例---半連線(semi join)優化方式 導致的查詢效能低下

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 ()
看紅色字型,所耗費的時間,. 為什麼?
四 探索原因
第一招: 察看執行計劃

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)
對比執行計劃,發現Q1使用了"MATERIALIZED"物化方式儲存子查詢的臨時結果. 是不是物化導致了Q1慢呢?
第二招: 察看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的判斷條件還是存在一點兒問題。