连载中 18/22

深分页与 count:limit 100000,10 和 count(*) 的优化套路

2026-05-11 · 7833 阅读 · 0 评论 · 0 赞

越翻越慢的订单列表

老王的运营后台有个怪现象:订单列表第一页秒开,翻到第 5000 页要 6 秒。同一张表、同一个索引,慢的不是查询本身,是深分页——这篇连同 count 统计一起,把两大查询慢性病的病理和药方讲全。

深分页为什么慢

LIMIT 100000, 10 的真实成本:没有「跳过」这个动作——数据库老老实实扫出前 100010 行,扔掉 10 万行,只留 10 行。走二级索引还要每行回表(第 3 篇),10 万次回表就是 10 万次 B+ 树查找。偏移量越大越慢,线性恶化。

药方一:游标分页(首选)

# 第一页
SELECT id, order_no, amount, create_time FROM orders
WHERE user_id = 9527
ORDER BY id DESC LIMIT 10;

# 下一页:带上上一页最后一行的 id
SELECT id, order_no, amount, create_time FROM orders
WHERE user_id = 9527 AND id < 987654
ORDER BY id DESC LIMIT 10;

把「跳过 10 万行」变成「从 id=987654 位置直接开始」——B+ 树一次定位,成本恒定,翻多深都一样快。代价是只支持连续翻页,不能直接跳第 5000 页。移动端无限下拉、管理端「上一页/下一页」,游标分页都是最优解。

药方二:延迟关联

SELECT o.id, o.order_no, o.amount, o.create_time
FROM orders o
JOIN (SELECT id FROM orders
      WHERE user_id = 9527
      ORDER BY id DESC LIMIT 100000, 10) tmp ON o.id = tmp.id;

产品坚持要页码跳转时的救命方案。核心思路:子查询只让索引干活——select id 恰好是二级索引自带的列,覆盖索引扫描完 100010 个 id 也不回表;外层只对 10 个最终 id 回表取整行。10 万次回表变 10 次,量级差异。

count(*) 的真实成本

带条件的 count 无法回避——要数就得扫(或扫索引),这是 MVCC 的代价:每个事务能看见的行都不一样,InnoDB 存不了全局统一的行数(MyISAM 那个免费的 count(*) 是没有并发版本概念的特权)。几个辨析:

  • count(*)、count(1)、count(id):性能基本等价,count(*) 是官方推荐的写法,别再纠结;
  • count(col):语义不同——不统计 col 为 NULL 的行,需要遍历取出值判断,多数场景反而更慢;
  • 给 count 配一个细窄的二级索引(EXPLAIN 里 key 选最小的那棵树)是免费的优化;
  • EXPLAIN rows 是估算值,误差可达几十倍,适合监控报警,不适合精确展示。

大表计数方案选型

方案做法适用
直接 count带窄索引扫百万级以内,低频统计
估算值EXPLAIN rows / information_schema 统计大盘展示「约 xx 条」
计数表业务写入时同事务增减计数行要求精确且高频读
Redis INCR计数放 Redis,定时对账校准超高频读、容忍秒级偏差(呼应 Redis 系列的 Write Behind)

老王的订单列表最终落地方案:移动端游标分页,管理端延迟关联 + 页码限制(超过 1000 页引导改用筛选条件),总数用计数表。三件套下去,第 5000 页和第一页一样快。

读的病治完,下一篇治「改」的病:千万级大表加个字段,为什么会把线上卡到报警?Online DDL 与 gh-ost 登场。

咖啡凉了,记得趁热喝。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#MySQL#深分页#游标分页#延迟关联#count

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10100 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9872 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9293 阅读 · 21 评论 · 287 赞