实战SQL优化(以MySQL深分页为例)

Posted 小冯同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战SQL优化(以MySQL深分页为例)相关的知识,希望对你有一定的参考价值。

1 准备表结构

CREATE TABLE `student`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `score` decimal(10, 2) NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

2 需求

按照成绩降序排列,并查询字段 user_no,user_name,score,做一个带排序的分页查询

3 自动执行数据

delimiter $$
CREATE PROCEDURE BatchInsert ( IN initId INT, IN loop_counts INT)BEGIN
	DECLARE Var INT;
	DECLARE ID INT;
	
	SET Var = 0;
	SET ID = initId;
	SET autocommit = 0;
	
	WHILE Var < loop_counts DO
		INSERT INTO `test`.`student` ( `user_no`, `user_name`, `score`, `create_time`, `update_time` )
		VALUES
			(
				CONCAT( \'学号\', ID ),
				CONCAT( \'姓名\', ID ),
				FLOOR( 1 + RAND()* 100 ),
				DATE_ADD( \'2023-3-30 16:08:00\', INTERVAL ROUND( RAND()* 1000+1 ) DAY ),
				DATE_ADD( \'2023-3-30 16:08:00\', INTERVAL ROUND( RAND()* 1000+1 ) DAY ) 
			);
			SET ID = ID + 1;
			SET Var = Var + 1;
		
	END WHILE;
	COMMIT;

END $$;
delimiter;
CALL BatchInsert(1,2000000)

4 需要分页的sql

SELECT user_no,user_name,score FROM student ORDER BY score DESC LIMIT 5,20 #浅分页
SELECT user_no,user_name,score FROM student ORDER BY score DESC LIMIT 80000,20 #深分页

5 分页执行计划

通过执行计划 expladin 看下执行效率:

  • 浅分页:

  • 深分页:

可以看出 type=all 都是走的全表扫描,并且都使用了额外的文件排序,现在记录一下执行时间:

浅分页:0.887s,深分页:1.427s

5.1 对排序字段添加索引

对 score 添加索引:alter table student add index idx_score(score)

浅分页:

耗时:0.021s

深分页:

耗时:1.475s

可以看出,虽然对排序字段加了索引,但是由于深分页偏移量太大,还是选择了走全表扫描 type=all。并额外使用了文件排序。

可以分析出,排序需要成本,回表也需要成本,浅分页由于偏移量小,回表成本低,所以执行效率有很大的提升,深分页偏移量大,回表成本太高了,所以需要降低深分页回表的成本。

5.2 建立联合索引

建立联合索引,就是为了消除回表带来的效率损耗。

alter table student add index idx_no_name_score(score,user_no,user_name)

浅分页:

耗时:0.024s

深分页:

耗时:0.047s

可以看到,使用联合索引已经可以解决了回表的问题,两者的执行效率也高了很多,但是这种做法有一个缺点,如果我们要查询出来的数据多了一个字段,就得重建联合索引,这样扩展性太差肯定不能接受的。所以还有一种办法,手动回表。

5.3 手动回表

手动回表的前提是对order by 字段添加了索引

浅分页:

SELECT
	user_no,
	user_name,
	score 
FROM
	student s1
	JOIN ( SELECT id FROM student ORDER BY score DESC LIMIT 5, 20 ) s2 ON s1.id = s2.id 

执行计划:

id 大的先执行

耗时:0.021s

深分页:

SELECT
	user_no,
	user_name,
	score 
FROM
	student s1
	JOIN ( SELECT id FROM student ORDER BY score DESC LIMIT 80000, 20 ) s2 ON s1.id = s2.id

执行计划:

耗时:0.042s

6 总结

优化方式 浅分页索引Type 深分页索引Type 浅分页耗时 深分页耗时
All All 0.887s 1.427s
order by 字段加索引 index All 0.021s 1.475s
联合索引 index index 0.024s 0.047s
手动回表(order by字段加索引) index index 0.021s 0.042s

以上是关于实战SQL优化(以MySQL深分页为例)的主要内容,如果未能解决你的问题,请参考以下文章

实战!聊聊如何解决MySQL深分页问题

实战!聊聊如何解决MySQL深分页问题

MySQL 千万数据量深分页优化, 拒绝线上故障!

SQL - MySQL深分页

MySQL深分页 + 多字段排序场景的优化方案三百万级数据量

MySQL深分页 + 多字段排序场景的优化方案三百万级数据量