视图不存数据,聚合性能损失源于每次重执行底层查询;索引视图是唯一能持久化聚合结果的方案,但需满足WITH SCHEMABINDING、确定性函数、两段式表名等硬性条件。

视图本身不存数据,聚合操作的性能损失不是来自“视图语法”,而是数据库必须每次重跑整个底层查询、再对临时结果集做聚合——相当于把原始表扫描和分组逻辑重复执行一遍。
视图展开后聚合被迫在内存/临时表中完成
SQL Server、PostgreSQL、MySQL 8.0+ 都会把 SELECT * FROM v_sales_summary 内联展开成原始定义(比如 SELECT region, SUM(sales) FROM orders GROUP BY region),然后才生成执行计划。这意味着:
- 即使你只查
WHERE region = 'North',优化器仍可能先算出全部地区的聚合结果,再过滤——除非索引能支持谓词下推 -
GROUP BY需要哈希构建或排序,若中间结果集大(比如百万行分组),就会触发Hash Match Aggregate+ 临时磁盘写入(Spill to TempDB) - 视图里用了
SELECT *或未限定列,数据库可能加载多余字段,放大 I/O 和内存压力
普通索引对视图聚合几乎不起作用
给基础表建了 INDEX idx_orders_region_sales ON orders(region, sales),但执行计划里依然显示 Clustered Index Scan——这不是配置错,是机制限制:
- 普通非聚集索引不含行数据,而
SUM()必须累加真实值,无法仅靠索引键完成 - 即使索引覆盖所有
GROUP BY和聚合列,SQL Server 仍可能因统计信息不准、估算行数偏高而放弃使用 - 视图无物化层,优化器看不到“这个分组结果可以复用”,每次都是从头算
嵌套视图 + 聚合 = 扫描量指数级放大
当聚合视图又被另一个视图引用时,子查询逻辑会被多次展开。典型例子:
CREATE VIEW v_user_order_stats AS SELECT u.id, COUNT_BIG(*) AS order_cnt FROM dbo.users u LEFT JOIN dbo.orders o ON u.id = o.user_id GROUP BY u.id;
如果再建 v_active_users_stats AS SELECT * FROM v_user_order_stats WHERE u.last_login > '2026-01-01',执行时可能对每个活跃用户都重新 JOIN orders 表——外层 10 万用户 × 内层平均 50 行订单 = 500 万次逻辑读。
- 相关子查询(如
(SELECT COUNT(*) FROM orders WHERE user_id = u.id))在视图中尤其危险 - 嵌套超过 2 层时,优化器很难准确估算中间行数,容易选错连接算法(Nested Loops 而非 Hash Join)
- 没有
WITH SCHEMABINDING的视图,连索引视图都建不了,彻底失去物化可能
真正能提速的只有索引视图(且必须满足硬性条件)
想让聚合结果被持久化、后续查询直读索引,唯一路径是创建合规的索引视图:
- 必须用
WITH SCHEMABINDING,且所有表名带两段式前缀(dbo.orders) - 聚合函数只能用确定性版本:必须是
COUNT_BIG(*),不能是COUNT(*);禁用GETDATE()、NEWID()、标量子查询 - 基表对应
GROUP BY列要有主键或唯一非空约束 - 在 Standard/Express 版本中,查询必须显式加
WITH (NOEXPAND)提示才能命中索引
最易被忽略的一点:就算成功建了索引视图,只要外部查询的 WHERE 条件没落在索引键上(比如视图按 region 分组,你却查 WHERE sales > 10000),SQL Server 仍会退回到扫描基表——索引视图不是万能缓存,它只是另一张物理存储的“表”。


















