做后台开发或者写管理后台时,经常要查数据列表——用户列表、订单列表、日志列表……一查就是几千上万条。直接 SELECT * FROM user 全拉出来?页面卡死,浏览器崩溃,老板路过都得皱眉。这时候就得用分页。
为什么不能用 LIMIT 0,10 这种写法?
其实可以,但得看数据库类型。MySQL 支持 LIMIT offset, size,比如:
SELECT id, name, email FROM user ORDER BY id DESC LIMIT 0, 20;
这是查第1页,每页20条。下一页就写 LIMIT 20, 20,再下一页 LIMIT 40, 20……看起来挺直白,但注意:当 offset 很大(比如 100000),MySQL 会先扫前10万行再扔掉,性能直线下降。线上查第5000页?小心DBA半夜敲你门。
不同数据库的分页写法差异
MySQL 8.0+ 推荐用 ROW_NUMBER()
如果你用的是 MySQL 8.0 或更新版本,可以用窗口函数优化深分页:
SELECT id, name, email FROM (
SELECT id, name, email, ROW_NUMBER() OVER (ORDER BY id DESC) AS rn
FROM user
) t WHERE rn BETWEEN 10001 AND 10020;
这样避免了大 offset 扫描,实际执行更稳。
SQL Server 用 OFFSET-FETCH
SQL Server 2012 起支持标准分页语法:
SELECT id, name, email
FROM user
ORDER BY id DESC
OFFSET 10000 ROWS FETCH NEXT 20 ROWS ONLY;
语义清晰,性能也比老式 TOP + NOT IN 方案靠谱得多。
PostgreSQL 就最省心
原生支持 OFFSET-FETCH,和 SQL Server 一样写法:
SELECT id, name, email
FROM "user"
ORDER BY id DESC
OFFSET 10000 LIMIT 20;
注意:PostgreSQL 里 LIMIT 和 OFFSET 是标准组合,顺序不能反,且 OFFSET 必须配合 ORDER BY 使用,否则结果不稳定。
一个小提醒:别忘了加索引
不管哪种写法,如果 ORDER BY 的字段没索引,分页照样慢。比如按 created_at 分页,就在这个字段建个索引:
CREATE INDEX idx_user_created ON user(created_at DESC);
实测过,没索引时查第1000页要3秒,加完索引压到80ms以内。
实战小技巧
真上线时,别硬算 offset。比如用户点“下一页”,后端最好记下当前页最后一条记录的 ID(比如 id=5678),下次查就写:
SELECT id, name, email FROM user
WHERE id < 5678
ORDER BY id DESC LIMIT 20;
这叫“游标分页”,跳过所有 offset 计算,响应快还稳定,适合无限滚动场景。