视图嵌套+分页必然导致全量扫描被放大N次,因LIMIT/OFFSET无法下推至内层,每层均需计算全部中间结果再截取;MySQL 5.7及以前不合并视图,PostgreSQL超3层触发MATERIALIZE,SQL Server依赖tempdb缓存,排序与索引均失效。

视图嵌套 + 分页 = 全量扫描被放大N次
不是分页本身慢,而是嵌套视图让数据库无法把 LIMIT 和 OFFSET 下推到最内层扫描节点。结果是:每层视图都得先算出全部中间结果,再截取——哪怕你只想要第10001~10010条。
- MySQL 5.7 及以前默认不合并视图,
SELECT * FROM v_paged_orders LIMIT 10 OFFSET 10000会先执行完整v_paged_orders定义(含所有 JOIN 和聚合),再排序、再截断 - PostgreSQL 中若嵌套超3层,
EXPLAIN (ANALYZE, BUFFERS)常显示MATERIALIZE节点下挂全表扫描,且Shared Read Blocks暴增数倍 - SQL Server 的
Table Spool (Eager Spool)出现频率高,说明它被迫把整层结果缓存进 tempdb,IO直接从内存落到磁盘
ORDER BY 在哪一层,就决定了排序开销在哪爆发
深分页的排序成本本就随 OFFSET 线性增长;嵌套视图会让这个排序发生在错误的位置——比如在已聚合、已 JOIN 过的宽表上排序,而不是在主键索引上快速定位。
- 错误写法:
SELECT * FROM (SELECT o.id, u.name, COUNT(*) c FROM orders o JOIN users u ON o.uid = u.id GROUP BY o.id, u.name ORDER BY o.id LIMIT 10000, 10) t→ 先 GROUP BY 所有行,再排序上万行字符串字段 - 正确方向:把
ORDER BY id和LIMIT放到最靠近基表的一层,例如SELECT * FROM orders o WHERE o.id > ? ORDER BY o.id LIMIT 10,再关联用户信息 - 验证方式:看执行计划里
Sort节点的Actual Rows是否等于全表行数,而不是目标分页大小
嵌套视图让“覆盖索引”彻底失效
你给 orders(id, status, created_at) 建了联合索引,本可避免回表;但一旦包进三层视图,外层 SELECT * 或未明确字段列表,优化器就无法确认是否能仅靠索引满足全部输出列,被迫回主键表捞数据。
- 视图定义里用
SELECT *是最大隐患:字段增减、顺序调整都会导致外层查询突然多出回表操作 - 即使视图只选3个字段,若其中一个是
TEXT或JSON类型,MySQL/PG 仍可能放弃使用覆盖索引 - 解决办法:视图定义必须显式列出字段,且外层查询只 SELECT 这些字段;配合
EXPLAIN确认key列非 NULL、Extra不含Using filesort或Using temporary
为什么加索引也救不了嵌套分页视图?
索引只加速单层扫描,但嵌套分页的问题是执行路径失控——优化器根本没机会用上索引,因为它连该在哪一层下推条件都不知道。
- 典型信号:
EXPLAIN显示某层type=ALL,但单独运行该层子查询却走ref或range - 视图嵌套超3层后,SQL Server 和 PostgreSQL 常放弃谓词下推(predicate pushdown),外层
WHERE status = 'shipped'无法压到orders表扫描节点 - MySQL 8.0 开启
optimizer_switch='derived_merge=on'有一定缓解,但对含GROUP BY或LIMIT的嵌套仍无效
真正卡住性能的,从来不是“有没有索引”,而是“数据库能不能把分页和过滤落到最底层扫描”。嵌套视图让这个能力失效,IO开销就从一次可控读,变成层层放大的重复读。










