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