1000 万数据重刷遇坑:老程序员也常忽略的分页查询陷阱与优化(上)

最近在处理一个历史遗留问题的时候,踩到了一个坑。估计很多老程序员平时也很容易忽略。

背景

有一个业务需求,需要重刷数据,数据量大概有1000多万,存在MySQL中。

解决思路

无脑用分页查询,每页大概500条数据。结果当处理到百万条数据的时候,异常的卡顿。以下是我的代码逻辑。

public void process(){
	@Autowired
	CustomerMapper customerMapper;

	int pageNo = 0;
	int pageSize = 500;
	List<Customer> customerList = customerMapper.page(pageNo, pageSize);
	while(ObjectsUtils.isEmpty(customerList)){
		for(Customer customer : customerList){
			// 核心处理在doProcess()方法中,此处忽略。
			doProcess(customer);
		}
		pageNo += 1;
		customerList = customerMapper.page(pageNo, pageSize)
	}
}

以下是customerMapper.page执行的SQL。

SELECT * FROM customer LIMIT #{pageNo}, #{pageSize};

问题分析

我们首先来看下以上SQL的执行时间。

explain
SELECT * FROM customer LIMIT 20,500
[2025-02-26 21:18:50] 500 rows retrieved starting from 1 in 775 ms (execution: 98 ms, fetching: 677 ms)

查询第20页的时候,耗费了677ms。

接下来用explain命令看看SQL的执行情况。
SQL的执行情况

由此可以看出,该SQL进行了全表扫描,即会把所有数据查出来,然后集中选中选择第21行到第520行的数据(跳过前20条数据),并且该查询没有使用索引。同时,MySQL估计需要检查的行数为5262行,只是一个简单的查询,不涉及到多表联查。

解决思路

分析了SQL的执行情况,我们避免全表扫描,同时查询的时候,使用索引就可以减少查询的时间,提高查询效率。

我们每次查询的时候,记录拿到数据的最大ID, 记为maxId。

long maxId = customerList.stream().mapToLong(Customer::getId).max().orElse(0);

然后,执行customerMapper.page()方法时,传下maxId。

以下是最终的SQL。

SELECT * FROM customer where id > #{maxId} LIMIT #{pageNo}, #{pageSize};

再来看下该SQL的执行时间。

explain
SELECT * FROM customer where id >= 1000 LIMIT 20,500
[2025-02-26 21:36:25] 500 rows retrieved starting from 1 in 389 ms (execution: 117 ms, fetching: 272 ms)

减少了288ms。

最后用explain命令看下SQL的执行情况。
在这里插入图片描述
由此可以看出,该SQL避免了全表扫描,使用了范围查询,同时使用了主键索引 。MySQL估计需要检查的行数也从5262行减少到2631行,减少了将近50%。

至此,SQL优化完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coding侠客

一起充电,一起成长。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值