在上一篇文章《1000 万数据重刷遇坑:老程序员也常忽略的分页查询陷阱与优化(上)》中,我们提到了使用游标分页的方式去优化千万数据的查询。游标分页的方式虽然可以有效的减少查询时间,但是其不支持跳页。
现在有这样一个场景:某公司拥有上千万的客户数据,其中有600多万的客户数据出现了异动,后端需要基于一定逻辑去刷新这些数据。
业务层的代码见《1000 万数据重刷遇坑:老程序员也常忽略的分页查询陷阱与优化(上)》。本文重点分析SQL。
以下是原始SQL。
SELECT * FROM customer LIMIT #{pageNo}, #{pageSize};
直接说结论,使用覆盖索引来优化该SQL。
以下是优化后的SQL。
SELECT * FROM customer
JOIN (
SELECT id FROM customer ORDER BY id LIMIT #{pageNo}, #{pageSize}
) AS c USING (id);
或者
SELECT * FROM customer c1
JOIN (
SELECT id FROM customer ORDER BY id LIMIT #{pageNo}, #{pageSize}
) AS c2 on c1.id = c2.id);
这两个SQL查询在逻辑是等价的,最终的执行结果也是一样,唯一的区别就是语法。
为什么覆盖索引可以优化?
按照惯例,我们使用explain命令来查看下它们的执行情况。
explain
SELECT * FROM customer LIMIT 20,500
[2025-03-03 22:35:28] 1 row retrieved starting from 1 in 468 ms (execution: 141 ms, fetching: 327 ms)
原SQL耗费468ms。
explain
SELECT * FROM customer join (select id from customer order by id LIMIT 20,500) as t using(id)
[2025-03-03 22:39:19] 3 rows retrieved starting from 1 in 170 ms (execution: 118 ms, fetching: 52 ms)
使用覆盖索引的SQL耗时170ms,相比原SQL减少了298ms。性能提升快2倍。
由此可知,本SQL涉及一个主查询和一个派生查询,主查询分为两部分,一部分是对派生查询的结果(derived2)进行了全表扫描,预计需要检查520行数据。另一部分访问了customer表,使用了唯一索引查找(eq_ref),这是非常高效的访问类型,并用派生查询结果中的id列来关联。
派生查询也访问了customer,使用了覆盖索引,即查询所需的数据都在索引中,不需要回表查询,提升了查询效率。