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.总结

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

相关推荐
写bug写bug8 分钟前
手把手教你使用JConsole
java·后端·程序员
苏三说技术12 分钟前
给你1亿的Redis key,如何高效统计?
后端
JohnYan35 分钟前
工作笔记- 记一次MySQL数据移植表空间错误排除
数据库·后端·mysql
程序员清风1 小时前
阿里二面:Kafka 消费者消费消息慢(10 多分钟),会对 Kafka 有什么影响?
java·后端·面试
CodeSheep1 小时前
宇树科技,改名了!
前端·后端·程序员
hstar95271 小时前
三十五、面向对象底层逻辑-Spring MVC中AbstractXlsxStreamingView的设计
java·后端·spring·设计模式·架构·mvc
楽码1 小时前
AI决策树:整理繁杂问题的简单方法
人工智能·后端·openai
星辰大海的精灵2 小时前
基于Dify+MCP实现通过微信发送天气信息给好友
人工智能·后端·python
import_random2 小时前
[深度学习]5大神经网络架构(介绍)
后端
pengyu2 小时前
【Java设计原则与模式之系统化精讲:壹】 | 编程世界的道与术(实战指导篇)
java·后端·设计模式