聊聊面试必问的千万级数据库分页查询

写在文章开头

千万级别的MySQL单表查询算是近几年面试时碰到的一道比较棘手的问题,因为很多开发没有这方面的经验,所以最终回答都不是很好,所以笔者就以MySQL8作为实验数据库为读者演示一下笔者日常的处理思路和技巧。

Hi,我是sharkChili,是个不断在硬核技术上作死的java coder,是CSDN的博客专家,也是开源项目Java Guide的维护者之一,熟悉Java也会一点Go,偶尔也会在C源码边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号:写代码的SharkChili ,获取笔者的联系方式备注 "加群" 和笔者的交流群进行深入交流。

百万级别数据查询实践

前置准备

为了方便演示笔者,这里拿出一张曾经作为批量插入的数据表,该表差不多有1000w左右的数据:

scss 复制代码
CREATE TABLE `batch_insert_test` (
  `id` int NOT NULL AUTO_INCREMENT,
  `fileid_1` varchar(100) DEFAULT NULL,
  `fileid_2` varchar(100) DEFAULT NULL,
  `fileid_3` varchar(100) DEFAULT NULL,
  `fileid_4` varchar(100) DEFAULT NULL,
  `fileid_5` varchar(100) DEFAULT NULL,
  `fileid_6` varchar(100) DEFAULT NULL,
  `fileid_7` varchar(100) DEFAULT NULL,
  `fileid_8` varchar(100) DEFAULT NULL,
  `fileid_9` varchar(100) DEFAULT NULL,
  `fileid_10` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25414 DEFAULT CHARSET=utf8mb3 COMMENT='测试批量插入,一行数据1k左右';

如何limit

按照分页查询公式,查询第N页的sql就是limit (n-1)*page , size,所以笔者对如下几个分页查询进行实验,不难看出,随着分页深度的增加,查询也变得十分耗时:

csharp 复制代码
select * from batch_insert_test bit2 limit 10,10;
select * from batch_insert_test bit2 limit 100,10;
select * from batch_insert_test bit2 limit 1000,10;
select * from batch_insert_test bit2 limit 10000,10;
select * from batch_insert_test bit2 limit 100000,10;
select * from batch_insert_test bit2 limit 1000000,10;
select * from batch_insert_test bit2 limit 5000000,10;

查看第500w页的数据10条,花费了将近1s:

csharp 复制代码
select * from batch_insert_test limit 5000000,10;

查看其执行计划,可以发现本次查询走了全表扫描,性能表现非常差劲:

sql 复制代码
id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows   |filtered|Extra|
--+-----------+-----+----------+----+-------------+---+-------+---+-------+--------+-----+
 1|SIMPLE     |batch_insert_test |          |ALL |             |   |       |   |9004073|   100.0|     |

所以我们需要对这些SQL进行改造,因为笔者这张表是以id作为主键的,所以我们可以很好的利用这一点,通过定位当前页的第一个id,然后通过这个id筛选对应页的数据,对应SQL如下所示,经过笔者的实验耗时大约在500ms左右:

bash 复制代码
select
 *
from
 batch_insert_test 
where
 id >=(select id from batch_insert_test bit2 limit 5000000,1)
 limit 10;

查看这条sql的执行计划可以发现,这条sql是直接通过索引直接定位id,避免走向叶子节点直接返回,再通过走索引的方式进行范围查询性能提升了不少。

sql 复制代码
id|select_type|table|partitions|type |possible_keys|key    |key_len|ref|rows |filtered|Extra                         |
--+-----------+-----+----------+-----+-------------+-------+-------+---+-----+--------+------------------------------+
 1|PRIMARY    |     |          |     |             |       |       |   |     |        |no matching row in const table|
 2|SUBQUERY   |bit2 |          |index|             |PRIMARY|4      |   |38677|   100.0|Using index                   |

limit多少

接下来就是limit数据量的选择了,有些读者可能为了方便直接在业务上进行改造,一次性查询大几十万数据给用户。 可以看到随着数据量的增加,查询耗时主键增大,所以读者在进行这方面考虑的时候务必要结合压测,根据自己业务上所能容忍的延迟涉及最大的pageSize,以笔者为例大约10w条以内的数据查询性能差异是不大的:

csharp 复制代码
select * from batch_insert_test bit2 limit 1000000,10;
select * from batch_insert_test bit2 limit 1000000,100;
select * from batch_insert_test bit2 limit 1000000,1000;
select * from batch_insert_test bit2 limit 1000000,10000;
select * from batch_insert_test bit2 limit 1000000,100000;
select * from batch_insert_test bit2 limit 1000000,1000000;
select * from batch_insert_test bit2 limit 1000000,10000000;

其他注意事项

还有一点细节上的优化,MySQL的基本单位是页,所以每次查询都是以页为单位进行查询,所以高效的查询也要求我们用尽可能少的块查到存储尽可能多的数据,所以查询时我们建议没有用到的列就不要查询来了。

以笔者为例,只需用到3个字段,则直接将*改为了id,fileid_1 ,fileid_4

bash 复制代码
select
 id,fileid_1 ,fileid_4 
from
 batch_insert_test bit2
where
 id >(select id from batch_insert_test bit2 limit 5000000,1)
 limit 10;

小结

来简单小结一下,本文通过一张大表结合一个分页查询的场景为读者演示的大表分页查询的技巧,整体来说,针对大表查询时,我们的SQL优化要遵循以下几点:

  1. 尽可能利用索引,确保用最小的开销得到索引。
  2. 结合业务场景和服务器性能压测出最合适的limit数据量。
  3. 尽量不要查询没必要的列。

我是sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号:写代码的SharkChili ,取笔者的联系方式备注 "加群" 和笔者的交流群进行深入交流。

参考

面试官:一千万的数据,你是怎么查询的? :mp.weixin.qq.com/s/zhmVw1C5B...

本文使用 markdown.com.cn 排版

相关推荐
言之。1 小时前
Go 语言中接口类型转换为具体类型
开发语言·后端·golang
diving deep2 小时前
XML简要介绍
xml·java·后端
编程乐学(Arfan开发工程师)3 小时前
06、基础入门-SpringBoot-依赖管理特性
android·spring boot·后端
编程乐学(Arfan开发工程师)3 小时前
05、基础入门-SpringBoot-HelloWorld
java·spring boot·后端
橘子海全栈攻城狮4 小时前
【源码+文档+调试讲解】党员之家服务系统小程序1
java·开发语言·spring boot·后端·小程序·旅游
冼紫菜4 小时前
Java开发中使用 RabbitMQ 入门到进阶详解(含注解方式、JSON配置)
java·spring boot·后端·rabbitmq·springcloud
boring_1114 小时前
Apache Pulsar 消息、流、存储的融合
分布式·后端
源码方舟7 小时前
SpringBoot + Shiro + JWT 实现认证与授权完整方案实现
java·spring boot·后端
热河暖男10 小时前
【实战解决方案】Spring Boot+Redisson构建高并发Excel导出服务,彻底解决系统阻塞难题
spring boot·后端·excel
noravinsc14 小时前
redis是内存级缓存吗
后端·python·django