视图本身不解决分页性能问题,尤其含row_number()时每次查询都需全量排序编号、无法跳过前n行,索引失效且执行计划必现windowagg节点;应将核心查询逻辑放入视图,排序与分页交由调用方处理。

SQL视图本身不解决分页性能问题,反而容易掩盖底层执行代价——尤其当视图里硬塞了 ROW_NUMBER() 时,每次查询都得重算全量序号,根本没法跳过前 N 行。
视图里别写 ROW_NUMBER()
很多人建视图时直接套一层 ROW_NUMBER() OVER (ORDER BY id),以为能复用分页逻辑。实际调用时,哪怕只查第 100 万页的 20 条,数据库仍要先生成全部行号再过滤。执行计划里必带 WindowAgg 节点,索引完全失效。
- 视图定义中含
ROW_NUMBER(),外层加WHERE rn BETWEEN 1000001 AND 1000020不会触发剪枝,PostgreSQL/SQL Server 基本不优化,MySQL 8.0+ 有尝试但不可靠 - 真正该放进视图的,只有核心查询逻辑(如
SELECT id, created_at, status FROM orders WHERE status = 'paid'),把排序、分页留给调用方 - 如果业务强依赖“页码”参数,至少把视图设计成可被
WHERE下推的形式,比如不含窗口函数、不带GROUP BY
游标分页不能靠视图传参
视图不支持运行时参数,所以无法在定义里写 WHERE (created_at, id) > (?, ?)。游标值必须由外部 SQL 拼接或通过存储过程传入。
- 首次请求可用
SELECT * FROM my_view ORDER BY created_at DESC, id DESC LIMIT 20 - 后续请求必须手拼条件:
SELECT * FROM my_view WHERE (created_at, id) - 注意排序字段方向一致性:如果视图里默认是
ORDER BY created_at ASC, id ASC,那游标就得用>,且前端保存的上一页末尾值也得对应升序 - MySQL 对复合游标比较敏感,
(created_at, id)的索引必须是(created_at, id),不能反过来,否则范围扫描失效
偏移量分页还能抢救吗?看索引能不能覆盖整条路径
如果第三方组件强制要求 page=1000&size=20,唯一补救是让数据库用索引完成“定位 + 排序 + 取值”三件事,避免回表和文件排序。
- 建联合索引时,把
WHERE条件字段放最左,排序字段紧随其后,查询字段放最后:例如CREATE INDEX idx_cover ON orders (status, created_at, id) INCLUDE (user_id, amount)(SQL Server / PostgreSQL) - MySQL 没
INCLUDE,得写成INDEX(status, created_at, id, user_id, amount),但要注意总长度别超限制(InnoDB 单索引 3072 字节) -
EXPLAIN中看到Extra: Using index才算成功;若出现Using filesort或Using temporary,说明索引没覆盖排序或聚合 - 即使加了覆盖索引,
OFFSET 1000000仍要扫描 1000020 行——只是从磁盘读变成内存扫描,延迟从秒级降到百毫秒级,不是根本解法
真正卡住的地方往往不是 SQL 怎么写,而是接口契约里写了 page 参数——只要前端坚持跳页,后端就只能在“慢”和“更慢”之间选一个。游标分页需要前后端协同改协议,而这点最容易被忽略。










