MySQL查询百万级数据分页查询优化实验记录

1.业务场景

在项目开发中,经常需要进行统计数据的上报和分析。通常,经过分析后,这些数据将在后台呈现给运营和产品人员,以便他们进行分页查看和分析。其中,一种常见的需求是按日期进行筛选和汇总数据。随着时间的推移,统计数据的数量将逐渐增加,最终可能达到数百万甚至数千万条数据。这种情况下,数据的处理和展示需要一些优化和考虑,以确保系统性能和用户体验。

2.实验数据插入

1.创建 user

sql 复制代码
CREATE TABLE `user`  (
  `id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `age` int(0) UNSIGNED NULL DEFAULT NULL,
  `phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `create_time` datetime(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

2.插入一百万条数据

开放了一个插入接口,通过 Apache Bench 发送 100 万次请求来向数据库插入 100 万条数据。命令如下:

shell 复制代码
# -c 表示并发请求数;-n 表示总请求数
ab -c 20 -n 1000000 http://127.0.0.1:8080/api/mysql/insert

大约需要 362 秒。

代码及测试 SQL 链接

3.无索引分页查询耗时

sql 复制代码
-- SQL_NO_CACHE 防止 SQL 查询走缓存
SELECT SQL_NO_CACHE * from `user` WHERE create_time BETWEEN '2000-01-01' AND '2023-12-31' LIMIT 1,10;

不同分页条件下的耗时情况:

LIMIT Time (ms)
1,10 0
100,10 1
1000,10 2
10000,10 9
100000,10 62
200000,10 122
400000,10 418
600000,10 466
800000,10 520
1000000,10 757

上述实验结果是单次实验所得。从表中分析可知,随着数据量的增大,往后页查询所耗时间按理来说会越来越大。

通过 Explain 命令对查询语句进行分析:

sql 复制代码
EXPLAIN
SELECT SQL_NO_CACHE * from `user` WHERE create_time BETWEEN '2000-01-01' AND '2023-12-31' LIMIT 1000000,10;

结果:

从结果中可知走的是全表扫描(ALL),扫描的数据行有 1022908 条,选取的行和读取的行的百分比为 11.11,使用到了 where 过滤。

4.添加索引后分页查询耗时

1.添加索引

sql 复制代码
ALTER TABLE user
ADD INDEX idx_createtime (create_time);

2.查看索引

sql 复制代码
SHOW INDEX FROM user;

3.查询耗时情况

LIMIT Time (ms)
1,10 42
100,10 43
1000,10 57
10000,10 209
100000,10 1502
200000,10 2860
400000,10 5975
600000,10 8492
800000,10 11155
1000000,10 14192

如果需要频繁通过 creae_time 字段进行条件查询,那么为该字段添加索引是必要的。 从上述实验结果可知,添加索引后,其执行时间反而增加。

通过 Explain 命令对查询语句进行分析:

sql 复制代码
EXPLAIN
SELECT SQL_NO_CACHE * from `user` WHERE create_time BETWEEN '2000-01-01' AND '2023-12-31' LIMIT 1000000,10;

从结果中可知走的是索引范围扫描(range),实际使用的索引是 idx_createtime,扫描的数据行有 511454 条;Using index condition 表示查询的列有非索引的列,先判断索引的条件,以减少磁盘的 I/O;Using MRR 指通过一次扫描来匹配多个范围。

究其原因,主要还是因为存在回表的情况。其执行过程如下:

  • 先从 「idx_createtime」B+ 树找到对应的叶子节点,获取主键值
  • 然后从上一步获取的主键值,在 「PRIMARY」B+ 树检索到对应的叶子节点,然后获取要查询的数据

5.优化方式

通过分析,可知是由于回表 导致的性能变差。那优化的方向就是减少回表或者避免回表。我们可以通过覆盖索引来避免回表的发生。

覆盖索引指在使用「二级索引」(非 PRIMARY 聚簇索引的索引)作为查询条件的时候,如果要查询的字段都包含在「二级索引」的叶子节点中,则不需要再从 「PRIMARY」B+ 树中查询,直接返回即可。

1.通过覆盖索引获取符合条件的 id

sql 复制代码
SELECT SQL_NO_CACHE id FROM `user` WHERE create_time BETWEEN '2000-01-01' AND '2023-12-31' LIMIT 1000000,10;

2.将 1 作为子查询

sql 复制代码
SELECT SQL_NO_CACHE * from (SELECT SQL_NO_CACHE id FROM `user` WHERE create_time BETWEEN '2000-01-01' AND '2023-12-31' LIMIT 1000000,10) AS temp INNER JOIN `user` AS u on temp.id=u.id;

3.查询耗时情况

LIMIT Time (ms)
1,10 0
100,10 2
1000,10 3
10000,10 7
100000,10 40
200000,10 80
400000,10 153
600000,10 307
800000,10 397
1000000,10 432

6.总结

对于超大分页的情况,可以通过 覆盖索引+子查询 的方式来减少回表情况,从而提高效率。

相关推荐
计算机毕设VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
没差c2 小时前
springboot集成flyway
java·spring boot·后端
三水不滴2 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
笨蛋不要掉眼泪3 小时前
Spring Boot集成LangChain4j:与大模型对话的极速入门
java·人工智能·后端·spring·langchain
sheji34165 小时前
【开题答辩全过程】以 基于SpringBoot的疗养院管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
短剑重铸之日6 小时前
《设计模式》第六篇:装饰器模式
java·后端·设计模式·装饰器模式
码界奇点7 小时前
基于Flask与OpenSSL的自签证书管理系统设计与实现
后端·python·flask·毕业设计·飞书·源代码管理
代码匠心8 小时前
从零开始学Flink:状态管理与容错机制
java·大数据·后端·flink·大数据处理
分享牛8 小时前
LangChain4j从入门到精通-11-结构化输出
后端·python·flask
知识即是力量ol9 小时前
在客户端直接上传文件到OSS
java·后端·客户端·阿里云oss·客户端直传