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的判断条件还是存在一点儿问题。
 

你可能感兴趣的:(数据库)