MySQL查询优化案例 - yiyixiaozhi/readingNotes GitHub Wiki
MySQL查询优化案例
环境:MySQL8
有一张用户出入记录表,当前有两个月的数据记录。
CREATE TABLE `t_student_entry_record` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned DEFAULT NULL,
`student_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户姓名',
`entry_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
PRIMARY KEY (`id`),
KEY `entry_time` (`entry_time`)
) ENGINE=InnoDB AUTO_INCREMENT=7092786 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC
当前建立了针对entry_time建立了索引,执行如下SQL进行查询,耗时20.357秒,查询到87万行结果。
SELECT id, entry_time, user_id, student_name
FROM t_student_entry_record
WHERE
entry_time BETWEEN '2020-12-01 00:00:00.0' AND '2020-12-31 23:59:59.999'
查看下执行计划:
EXPLAIN
SELECT id, entry_time, user_id, student_name
FROM t_student_entry_record
WHERE
entry_time BETWEEN '2020-12-01 00:00:00.0' AND '2020-12-31 23:59:59.999'
可以看到type是ALL,说明执行了全表扫描。
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
------ ----------- ---------------------- ---------- ------ ------------- ------ ------- ------ ------- -------- -------------
1 SIMPLE t_student_entry_record (NULL) ALL entry_time (NULL) (NULL) (NULL) 1573708 50.00 Using where
如果缩减where条件中entry_time的时间范围,可以看到type会变更为range,说明索引起了作用。
EXPLAIN
SELECT id, entry_time, user_id, student_name
FROM t_student_entry_record
WHERE
entry_time BETWEEN '2020-12-01 00:00:00.0' AND '2020-12-02 23:59:59.999'
执行结果如下:不但使用了索引,还使用了MRR(在使用索引做范围扫描的过程中减少磁盘随机IO和减少主键索引的访问次数)。
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
------ ----------- ---------------------- ---------- ------ ------------- ---------- -----
-- ------ ------ -------- ----------------------------------
1 SIMPLE t_student_entry_record (NULL) range entry_time entry_time 5 (NULL) 163358 100.00 Using index condition; Using MRR
优化后执行耗时0.128秒,查询到6万行结果。
由于user_id、student_name没有做索引,所以不查询与其有关的数据
SELECT id, entry_time
FROM t_student_entry_record
WHERE
entry_time BETWEEN '2020-12-01 00:00:00.0' AND '2020-12-31 23:59:59.999'
执行计划
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
------ ----------- ---------------------- ---------- ------ ------------- ---------- ------- ------ ------ -------- --------------------------
1 SIMPLE t_student_entry_record (NULL) range entry_time entry_time 5 (NULL) 786854 100.00 Using where; Using index
可以看到使用了索引来查询。耗时0.056秒,查询到879015行。
首先建立两个索引
ALTER TABLE `t_student_entry_record` ADD INDEX `user_id`(user_id);
看执行计划:
EXPLAIN
SELECT id, entry_time, user_id
FROM t_student_entry_record
WHERE
entry_time BETWEEN '2020-12-01 00:00:00.0' AND '2020-12-31 23:59:59.999'
AND user_id IN (342829,342809,342831,343581,352562,180016,350069,350063,164512,350060,350067,350062,350071)
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
------ ----------- ---------------------- ---------- ------ ------------------ ------- ------- ------ ------ -------- -----------------------------------------------
1 SIMPLE t_student_entry_record (NULL) range entry_time,user_id user_id 9 (NULL) 107 50.00 Using index condition; Using where; Using MRR
0.051秒,查询到106行数据。
索引使用多个字段
ALTER TABLE `t_student_entry_record` DROP INDEX `user_id`;
ALTER TABLE `t_student_entry_record` DROP INDEX `entry_time`;
ALTER TABLE `t_student_entry_record` ADD INDEX `entry_time_and_user_id`(entry_time,user_id);
EXPLAIN
SELECT id, entry_time, user_id
FROM t_student_entry_record
WHERE
entry_time BETWEEN '2020-12-01 00:00:00.0' AND '2020-12-31 23:59:59.999'
AND user_id IN (342829,342809,342831,343581,352562,180016,350069,350063,164512,350060,350067,350062,350071)
查看执行计划
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
------ ----------- ---------------------- ---------- ------ ---------------------- ---------------------- ------- ------ ------ -------- --------------------------
1 SIMPLE t_student_entry_record (NULL) range entry_time_and_user_id entry_time_and_user_id 14 (NULL) 786854 50.00 Using where; Using index
0.613秒查询到106行数据
效果不是太理想,因为每个人的记录数有限,重复情况较少,所以可以调整索引中字段的顺序:
ALTER TABLE `t_student_entry_record` DROP INDEX `entry_time_and_user_id`;
ALTER TABLE `t_student_entry_record` ADD INDEX `user_id_and_entry_time`(user_id,entry_time);
查看SQL优化结果:
EXPLAIN
SELECT id, user_id, entry_time
FROM t_student_entry_record
WHERE
user_id IN (342829,342809,342831,343581,352562,180016,350069,350063,164512,350060,350067,350062,350071)
AND entry_time BETWEEN '2020-12-01 00:00:00.0' AND '2020-12-31 23:59:59.999'
查看执行计划:
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
------ ----------- ---------------------- ---------- ------ ---------------------- ---------------------- ------- ------ ------ -------- --------------------------
1 SIMPLE t_student_entry_record (NULL) range user_id_and_entry_time user_id_and_entry_time 14 (NULL) 106 100.00 Using where; Using index
可以看到rows扫描行大幅度降低,最终在0.046秒查询到了106行数据。
各列解析说明:
id:表示执行的顺序,id的值相同时,执行顺序是从上到下,id的值不同时,id的值越大,优先级越高,越先执行
select_type: 1、SIMPLE表示不包含子查询和union 2、查询中若包含子查询,最外层查询则标记为:PRIMARY 3、在select或者where列表中包含了子查询,则标记为:SUBQUERY 4、在from的子查询会标记为:DERIVED 5、从union selcect出来的结果被标志为:UNION RESULT
type: 表示找到需要行的访问类型 ALL,index,range,ref,eq_ref,const,system,NULL 性能从最差到最好
key: 表示使用哪个索引
rows: 表示影响的行数
extra: 表示查询帅选的类型,比如使用了where条件,索引条件,或者排序,或者临时表空间