说实话,索引视图这东西,我最早是不太信任的。当年在一个报表系统里,有个统计销售汇总的视图,每天几百万订单明细,查询一跑就是十几秒。给底层表加索引、改统计信息,什么招都试过,效果都有限。后来才真正花时间把索引视图(Indexed View)吃透,建完后那条查询稳定在一秒以内。从那时候起,我对SQL Server里“视图 + 索引”这套组合,算是彻底改观了。
这篇文章我就用实际踩坑的经验,把SQL Server中创建与管理视图索引这件事从头到尾拆一遍。包括普通视图和索引视图的本质区别、创建索引视图必须满足的条件、完整的建索引实操过程,以及日常维护、常见报错排查和性能监控。适合正在做报表查询优化、遇到视图性能瓶颈的开发人员和DBA参考,也适合想搞清楚视图和索引底层逻辑的学习者。
1. 先搞清楚:普通视图和索引视图到底差在哪
1.1 视图是查询的封装,不是数据的实体
在SQL Server里,标准视图(Standard View)本质上就是一个“预编译的查询定义”。你在SSMS里创建视图,数据库其实只保存了一条SELECT语句,以及它的元数据信息。当你执行SELECT * FROM v_OrderSummary的时候,SQL Server会把视图的定义展开,把真实的查询下推到底层表上面去执行。
这就会带来一个很好理解的后果:每次访问视图,底层表都会被重新扫描或重新计算。视图本身不保存任何数据,它也不占物理存储空间(严格说只占很小的元数据空间)。这也是为什么很多刚接触数据库的朋友会以为“视图把数据存了一份,所以查得快”,这个认知是错误的。视图只是一个逻辑层,它的价值在于封装、安全、简化查询,而不是性能。
但问题也在这。你封装得再漂亮,如果底层表的数据量很大、查询要聚合大量行,那么无论你如何封装,实际执行的开销一点都不会少。我在实际优化过程中遇到过太多类似场景:开发人员在SSMS里创建视图后,就理所当然地认为查询变快了,结果一上生产环境,数据量一大,查询照样慢得离谱。
1.2 索引视图的本质:把视图“物化”成一张真实存储的表
索引视图(Indexed View)才真正解决了“视图不存数据”的问题。它的核心机制是:当你满足一定条件给视图创建唯一聚集索引后,SQL Server就会把视图查询的结果集真正物理化,存储到数据库中,并且在你往底层表插入、更新、删除数据时,自动同步维护这份物理化的数据。
你可以把索引视图想象成一张“自动维护的汇总表”。它不是重新扫描底层表,而是直接读取已经算好的结果。这种机制在Oracle里叫物化视图(Materialized View),在SQL Server里就叫索引视图。两者的思路是相通的,但SQL Server对索引视图的限制条件会更苛刻一些。
我刚接触索引视图的时候最大的困惑是:既然它像一张表,为什么不直接创建一张汇总表,再用存储过程维护?原因其实有两方面。第一,索引视图的同步是数据库引擎自动完成的,不需要你写额外代码,不会出现漏刷数据、忘记刷数据的问题。第二,查询优化器在内核层面支持它,如果你用的是合适版本,它可以在你没有显式指定索引视图的情况下,自动把普通查询改写为对索引视图的访问。这种透明性是手工汇总表做不到的。
1.3 视图上到底能不能建索引?很多人的误区
网上有一种说法是“视图不能建索引”,这不准确。准确的说法是:普通视图不能直接创建非聚集索引,必须先给视图创建唯一聚集索引,之后才能创建非聚集索引。而且创建聚集索引的前提条件很多,包括视图必须绑定架构(SCHEMABINDING)、引用的表必须是两段式命名、语句里的SET选项必须符合要求等等。
也就是说,你平时在SSMS里创建的普通视图,确实没法学常规表那样直接右键新建索引。必须先改造视图定义,再按顺序创建索引。我把这个步骤总结成一句话:先绑定架构,再创建唯一聚集索引,然后再按需创建非聚集索引。这也是很多初学者最容易卡住的地方——不是索引创建的语法不会写,而是前面的条件没有满足。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 创建索引视图的前置条件和完整实操
2.1 前置条件:版本、权限、SET选项一个都不能少
很多人建索引视图失败,都不是索引语句本身写错,而是环境条件没满足。我整理了一下,主要分四类:
- 版本要求:索引视图在企业版、开发版、标准版(2016 SP1之后)等版本中可用。如果你用的是老旧的Express版或者早期标准版,可能没有这个功能,或者查询优化器不会自动匹配索引视图。
- 权限要求:创建索引视图需要在数据库中拥有CREATE VIEW权限,并且对视图引用的所有表拥有REFERENCES权限。
- SET选项要求:这个最坑。创建索引视图时,会话必须开启
ANSI_NULLS、ANSI_PADDING、ANSI_WARNINGS、ARITHABORT、CONCAT_NULL_YIELDS_NULL、QUOTED_IDENTIFIER,同时关闭NUMERIC_ROUNDABORT。尤其是ARITHABORT,在SSMS中默认是ON,但如果你用其他客户端连接,可能默认是OFF,这时候创建索引视图就会报错。 - 视图本身的要求:必须是
WITH SCHEMABINDING,引用的表必须写成dbo.TableName这样的两段式名称,不能使用SELECT *,不能包含DISTINCT、UNION、子查询、OUTER JOIN、非确定性函数等(在创建聚集索引之前)。
这里我要特别说一下NUMERIC_ROUNDABORT。这个选项在过去很多资料里被提到,但在实际操作中,我建议你在执行创建之前显式设置:
sql复制SET NUMERIC_ROUNDABORT OFF;
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
你别嫌麻烦,这些设置写在脚本开头,能避免很多莫名其妙的问题。特别是当你把脚本交给其他人执行时,不同客户端的默认设置不一样,很容易翻车。
2.2 第一步:把视图改成SCHEMABINDING
要建索引视图,首先得保证视图定义里有WITH SCHEMABINDING。它的作用是让视图绑定到底层表的架构上。如果某个表引用了这个视图,你就不能随意修改表结构、删除表或者表里的关键列,数据库会阻止这些操作,以此保证索引视图引用的对象一直存在并且结构有效。
我们来举个例子。假设有两个表:订单表SalesOrder和订单明细表SalesOrderDetail。
sql复制CREATE TABLE dbo.SalesOrder
(
OrderId INT IDENTITY(1,1) PRIMARY KEY,
OrderNo NVARCHAR(50),
OrderDate DATETIME,
CustomerId INT
);
CREATE TABLE dbo.SalesOrderDetail
(
DetailId INT IDENTITY(1,1) PRIMARY KEY,
OrderId INT,
ProductId INT,
Quantity INT,
UnitPrice DECIMAL(18,2)
);
如果直接创建一个普通视图:
sql复制CREATE VIEW dbo.v_OrderSummary
AS
SELECT
o.OrderDate,
d.ProductId,
SUM(d.Quantity) AS TotalQuantity,
SUM(d.UnitPrice * d.Quantity) AS TotalAmount
FROM dbo.SalesOrder AS o
INNER JOIN dbo.SalesOrderDetail AS d
ON d.OrderId = o.OrderId
GROUP BY o.OrderDate, d.ProductId;
这时候你右键试图建索引,SSMS会提示无法创建。正确的做法是给视图添加WITH SCHEMABINDING,同时确保引用的表都是两段式命名:
sql复制CREATE VIEW dbo.v_OrderSummary
WITH SCHEMABINDING
AS
SELECT
o.OrderDate,
d.ProductId,
SUM(d.Quantity) AS TotalQuantity,
SUM(d.UnitPrice * d.Quantity) AS TotalAmount
FROM dbo.SalesOrder AS o
INNER JOIN dbo.SalesOrderDetail AS d
ON d.OrderId = o.OrderId
GROUP BY o.OrderDate, d.ProductId;
注意,GROUP BY里的列必须包含一个COUNT_BIG(*)?不是必须,但SQL Server要求索引视图的聚合查询中,如果使用了GROUP BY,那么COUNT_BIG(*)可以辅助维护行数信息,但不是强制条件。真正强制的是:视图中的表达式必须是确定性的,并且SELECT列表中的所有列必须明确引用。我建议在写聚合视图时加上COUNT_BIG(*),一方面统计行数有用,另一方面在后续创建非聚集索引时更灵活。
2.3 第二步:先建聚集索引,再建非聚集索引
视图改造完成之后,你就可以在视图上创建唯一聚集索引了。这里有个很关键的概念:索引视图的第一个索引必须是唯一聚集索引(Unique Clustered Index)。这一步不仅仅是“建一个索引”,更关键的是通过唯一聚集索引来物理化视图数据。聚集索引决定了数据的物理存储顺序,唯一性则保证了视图结果集中每一行都是可识别的、唯一的。
具体到我们的例子,OrderDate和ProductId的组合可以唯一标识每个分组吗?如果同一订单日期、同一产品只出现一次分组,那么可以。但实际业务中,同一个日期同一个产品可能来自多个订单,但因为我们是对OrderDate, ProductId做分组聚合,所以分组结果本身就是唯一的。可以在这个字段组合上建唯一聚集索引:
sql复制CREATE UNIQUE CLUSTERED INDEX IX_v_OrderSummary_OrderDate_ProductId
ON dbo.v_OrderSummary(OrderDate, ProductId);
创建成功后,SQL Server就会把视图的聚合计数据实际存储下来。之后,如果你想提高特定查询的效率,还可以继续创建非聚集索引。比如,报表系统中经常按产品ID查询某个时间段的汇总数据,就可以创建:
sql复制CREATE NONCLUSTERED INDEX IX_v_OrderSummary_ProductId
ON dbo.v_OrderSummary(ProductId, OrderDate)
INCLUDE (TotalQuantity, TotalAmount);
有一个细节我必须提醒你:前面这套操作必须在同一个会话中、满足所有SET选项的前提下执行。如果你在SSMS中分步执行,每个新查询窗口的SET选项可能不一样,后面补建非聚集索引时也可能碰壁。我习惯的做法是把所有SET选项和创建语句放在同一个脚本文件里,一次性执行。
2.4 一个完整的实操案例:按月汇总销售报表
上面的例子还是偏简单,我再写一个比较贴近日常业务的场景:按月按产品汇总销售金额和销售量。这是报表系统里非常常见的需求。
首先,创建明细视图(绑定架构):
sql复制CREATE VIEW dbo.v_MonthlySales
WITH SCHEMABINDING
AS
SELECT
YEAR(o.OrderDate) AS SalesYear,
MONTH(o.OrderDate) AS SalesMonth,
d.ProductId,
SUM(d.Quantity) AS TotalQuantity,
SUM(d.UnitPrice * d.Quantity) AS TotalAmount,
COUNT_BIG(*) AS RowCount
FROM dbo.SalesOrder AS o
INNER JOIN dbo.SalesOrderDetail AS d
ON d.OrderId = o.OrderId
GROUP BY
YEAR(o.OrderDate),
MONTH(o.OrderDate),
d.ProductId;
然后创建唯一聚集索引:
sql复制CREATE UNIQUE CLUSTERED INDEX IX_v_MonthlySales
ON dbo.v_MonthlySales(SalesYear, SalesMonth, ProductId);
再创建非聚集索引,方便按产品维度快速检索:
sql复制CREATE NONCLUSTERED INDEX IX_v_MonthlySales_ProductId
ON dbo.v_MonthlySales(ProductId, SalesYear, SalesMonth);
到这里,一个可以自动维护的按月销售汇总数据集就建立好了。之后你查询某个月某种产品的销售额时,优化器如果选择了索引视图,那么性能跟直接聚合明细表完全不在一个量级。
3. 索引视图的工作原理与维护代价
3.1 索引视图的数据同步机制
索引视图物理化之后,最核心的问题就是:底层表数据变了,索引视图里的数据怎么同步?这一点SQL Server做得非常自动化。当SalesOrder或SalesOrderDetail表发生INSERT、UPDATE、DELETE操作时,SQL Server会在事务内部同步维护索引视图的索引结构,和同步维护普通表上的索引本质上是同一套机制。
这里有一个很重要的性能影响:对底层表的DML操作,不再只是更新表本身,还要更新所有相关的索引视图。这意味着,如果在一个OLTP系统里,对底层表写入非常频繁,那么索引视图会成为写路径上的额外负担。而且这个同步是实时的、同步的,不是异步延迟的。也就是说,事务提交的时候,索引视图的数据已经跟底层表一致了。
所以索引视图非常适合“读多写少”的场景,比如报表查询、数据仓库维度汇总、统计分析。反过来,如果底层表每秒有大量插入更新,你还建索引视图,那写性能可能会显著下降。
3.2 什么时候被自动使用,什么时候必须带NOEXPAND
索引视图建好之后,另一个常见困惑是:我查询的时候要不要强制让它走索引视图?答案分两种情况。
在企业版、开发版,以及SQL Server 2016 SP1之后的标准版中,查询优化器可以自动匹配索引视图。也就是说,你写一条普通SQL,比如:
sql复制SELECT
YEAR(o.OrderDate) AS SalesYear,
MONTH(o.OrderDate) AS SalesMonth,
d.ProductId,
SUM(d.Quantity)
FROM dbo.SalesOrder AS o
INNER JOIN dbo.SalesOrderDetail AS d
ON d.OrderId = o.OrderId
GROUP BY YEAR(o.OrderDate), MONTH(o.OrderDate), d.ProductId;
如果优化器认为索引视图v_MonthlySales能满足这个查询,它会自动改写执行计划,直接扫描索引视图而不是去扫明细表。这个过程对你来说是透明的,你不需要改SQL。
但在某些场景下,优化器出于成本考虑,可能不会自动选择索引视图。或者你使用的是不具备自动匹配功能的版本。这时候就需要显式提示NOEXPAND:
sql复制SELECT
SalesYear,
SalesMonth,
ProductId,
TotalQuantity,
TotalAmount
FROM dbo.v_MonthlySales WITH (NOEXPAND)
WHERE SalesYear = 2024
AND SalesMonth = 12;
NOEXPAND的意思是强制优化器不要展开视图、直接使用索引视图的物理存储结构。它和NOEXPAND搭配的常见提示还有WITH (NOEXPAND, INDEX(IX_v_MonthlySales_ProductId)),可以指定使用某个非聚集索引。
需要注意,NOEXPAND这个提示只在索引视图上有效。如果你对普通视图加NOEXPAND,SQL Server会直接报错。所以在用之前,先确认你访问的确实是索引视图。
3.3 不是免费的午餐:索引视图的三大代价
我见过不少团队一听说索引视图能提升性能,就一股脑给所有视图加索引,结果写性能恶化,甚至磁盘空间暴涨。这里必须把代价讲清楚。
第一个代价是存储空间。索引视图的聚集索引把结果集完整物理化了,数据量等于视图结果集的行数。如果你的视图是按月按产品汇总,那么行数可能是明细的几十分之一;但如果视图没有合理分组,结果集接近全表行数,那存储空间就会非常可观。再加上非聚集索引的开销,存储成本可能让你怀疑人生。
第二个代价是DML性能。前面说了,底层表每次INSERT、UPDATE、DELETE,索引视图都要同步维护。如果你的系统写入量大,索引视图上的每一个索引都会放大写操作的成本。尤其是聚集索引的键列如果经常被更新,同步维护成本更高。我在一个订单系统上做过测试,对一张每天插入量超过50万行的订单表建立月汇总索引视图后,插入性能下降了大约15%到20%。对于OLTP系统来说,这个代价不容忽视。
第三个代价是索引碎片和统计信息维护。索引视图的底层存储跟你普通表上的索引一样,也会产生碎片,也需要定期重建或重组。而且索引视图的统计信息也需要更新,否则优化器可能基于过期的统计信息做出错误判断。很多人建完索引视图就忘了维护,时间一长,性能照样退化。
3.4 索引视图在项目里的典型应用场景
从我的经验来看,索引视图最适合下面几类场景:
- 报表汇总查询。比如按天/周/月/季度统计订单、销量、金额,这种查询的结果集比明细小很多,非常适合索引视图。
- 数据仓库中的维度表和事实表关联后的聚合结果。ETL结束后,数据变化不频繁,索引视图能大幅提升即席查询速度。
- 大量重复的复杂聚合查询。如果很多报表页都在跑同样一堆GROUP BY,那不如物理化一份,让所有查询都复用。
- 权限隔离/安全视图的加速。如果经常通过视图过滤特定部门、特定客户的数据,且底层数据量很大,可以考虑索引视图。
不推荐的场景是:高并发OLTP系统中的基础业务表上建立复杂索引视图。那种场景下,表的写入性能远比“省掉几次复杂查询”重要。你要先评估读写比例,再决定要不要建。
4. 常见报错与排查测试实录
4.1 SET选项相关的经典报错和解决
我最早建第一个索引视图时遇到的报错,到现在都记忆犹新,大概是这个意思:“无法创建索引,因为CREATE INDEX语句的SET选项设置不正确”。那时候我一度以为是自己SQL语法写错了,后来才意识到是SET选项问题。
这类报错的排查有个非常实用的思路:查看当前会话的SET选项状态,然后跟要求做对比。比如执行:
sql复制SELECT
SESSIONPROPERTY('ANSI_NULLS') AS ANSI_NULLS,
SESSIONPROPERTY('ARITHABORT') AS ARITHABORT,
SESSIONPROPERTY('CONCAT_NULL_YIELDS_NULL') AS CONCAT_NULL_YIELDS_NULL,
SESSIONPROPERTY('QUOTED_IDENTIFIER') AS QUOTED_IDENTIFIER;
如果发现某些选项是0,那就要在创建语句前显式开启。注意,有一些SET选项是创建索引视图时“必须开启”的,但另一些是“必须关闭”的,比如NUMERIC_ROUNDABORT。我当时就是在某个工具里连数据库,它默认把NUMERIC_ROUNDABORT开成ON了,导致建索引一直失败。
另外还有个场景值得注意:如果你是通过存储过程或者作业去创建索引视图,存储过程的执行上下文可能会继承数据库默认SET选项,而不是SSMS里的设置。所以写自动化脚本时,一定要在脚本开头把SET选项固定下来,千万不要依赖客户端默认值。
还有一条要提醒:建好索引视图后,底层表的修改也会受到限制。比如你试图删除视图引用的列,SQL Server会报错“对象被SCHEMABINDING引用,无法删除”。这不是bug,是SCHEMABINDING的保护机制。遇到这种情况,先确认这个视图是否可以删掉或改造,再考虑修改表结构。
4.2 为什么建了索引视图,执行计划还是没用到
这是索引视图优化里最让人头疼的问题之一:索引视图建好了,查询写出来也匹配,但执行计划显示根本没走索引视图,还是在扫明细表。
从我的实践经验看,原因可能有这几个:
- 当前账号所在数据库版本或兼容级别不支持自动匹配。可以检查
SELECT compatibility_level FROM sys.databases,低版本兼容级别下优化器可能不会考虑索引视图。 - 查询语句里的列、分组、聚合函数与索引视图不完全匹配。比如视图用
SUM(UnitPrice * Quantity),而查询写成SUM(UnitPrice) * SUM(Quantity),语义不同,优化器无法匹配。 - 查询中缺少
COUNT_BIG(*)对应的行数统计需求。有些查询如果请求了视图未包含的列,优化器只能放弃索引视图。 - 成本估算问题。优化器计算后发现直接查明细表的成本更低(可能是因为索引视图的统计信息过期,或者查询的过滤条件本身能高效走底层表索引)。
- 使用了非企业版,且没加
NOEXPAND提示。标准版中即使建了索引视图,查询优化器也不会自动匹配,必须显式加提示。
排查方法也很直接:在SSMS中开启“包含实际执行计划”,然后分别执行普通查询和加了NOEXPAND的查询,对比两个计划的cost和IO统计。如果加了NOEXPAND后性能明显更好,说明优化器因为某种原因没有选索引视图;如果两者差不多,甚至普通查询更快,那就说明索引视图在当前场景下并不占优。
4.3 用动态管理视图监控索引视图的使用情况
索引视图本质上也是索引,所以你可以用查看普通索引的那套DMV来监控它的使用情况和碎片情况。
想看当前数据库里所有索引视图及其索引大小,可以这样:
sql复制SELECT
OBJECT_NAME(i.object_id) AS ViewName,
i.name AS IndexName,
i.type_desc AS IndexType,
ps.row_count,
ps.used_page_count * 8 / 1024.0 AS UsedMB
FROM sys.indexes AS i
JOIN sys.dm_db_partition_stats AS ps
ON i.object_id = ps.object_id
AND i.index_id = ps.index_id
WHERE OBJECTPROPERTY(i.object_id, 'IsIndexedView') = 1;
想看用户实际执行查询时,哪些索引视图被用到,可以通过sys.dm_db_index_usage_stats:
sql复制SELECT
OBJECT_NAME(ius.object_id) AS ViewName,
i.name AS IndexName,
ius.user_seeks,
ius.user_scans,
ius.user_lookups,
ius.user_updates
FROM sys.dm_db_index_usage_stats AS ius
JOIN sys.indexes AS i
ON ius.object_id = i.object_id
AND ius.index_id = i.index_id
WHERE ius.database_id = DB_ID();
如果user_updates非常高,而user_seeks和user_scans非常低,那基本可以断定这个索引视图的维护成本大于收益,你就要考虑是否值得保留。
索引碎片也是不能忽略的。我一般会每周跑一次索引碎片检查,对碎片率超过30%的索引视图执行ALTER INDEX ... REORGANIZE或REBUILD。不过要注意,REBUILD在索引视图上可能会重新触发索引视图的物理化过程,如果数据量很大,最好安排在业务低峰期执行。
4.4 维护索引视图:重建、碎片整理与更新策略
对于索引视图的日常维护,我的习惯是把它纳入常规索引维护流程中,和普通表索引一起处理。具体分三个层面:
一是定期重建和重组索引。通过ALTER INDEX语句操作,或者用现成的索引维护脚本统一跑。时间间隔可以根据数据更新频率来定,比如每天晚上跑一次重组,每周日跑一次重建。
二是更新统计信息。索引视图上的统计信息不会因为你重建索引就自动达到最佳状态。最好在重建聚集索引之后,手动执行:
sql复制UPDATE STATISTICS dbo.v_MonthlySales;
也可以带上WITH FULLSCAN,虽然会慢一些,但统计信息更准确。
三是关注底层表的结构变更风险。由于SCHEMABINDING的存在,底层表结构变更会被阻止。所以如果你需要改表结构,必须先处理掉关联的索引视图。我的建议是:项目开发阶段不要急着把所有视图都变成索引视图,等表结构稳定了再上。否则每改一次表结构,你都得先删视图索引、改表、再重建索引视图,流程极其痛苦。
我印象最深的一次是在一个项目上线前,业务方要求订单表增加一个“订单来源渠道”字段。结果那个表上有三个索引视图,我不得不先把三个视图的索引全部删掉,改完表结构,再逐个重建视图和索引。整个过程花了将近两个小时。从那以后,我给自己定了一条规矩:索引视图只建在表结构稳定、不太会动的核心汇总逻辑上。
5. 几条让你少走弯路的经验总结
说了这么多,最后我想分享几条自己在实际项目中积累的判断标准,基本可以当作索引视图的“使用守则”。
第一,别拿索引视图当万能优化工具。先看执行计划,确认性能瓶颈真的在视图对应的聚合查询上,再考虑建索引视图。很多时候,底层表加适当的非聚集索引,覆盖查询列,效果一样不差,而且维护成本低得多。索引视图应该是“大招”,不是“起手技能”。
第二,创建索引视图时,把SET选项固定在脚本开头。不要依赖SSMS或者任何客户端的默认配置。否则今天能建成功,明天换个环境就失败,排查起来非常浪费时间。
第三,监控写性能下降幅度。如果底层表的DML性能下降了超过你能接受的阈值,宁可放弃索引视图,也不要硬撑。有些系统报表查询跑三秒可以接受,但写入接口如果从50毫秒变成80毫秒,那就可能拖垮整个业务链路。我在实际项目里见过不少因为索引视图导致锁等待和阻塞的问题,最后都是忍痛删掉索引视图才恢复。
第四,把索引视图的维护纳入常规巡检。定期看碎片、更新统计信息、检查dm_db_index_usage_stats。如果发现某个索引视图长时间没有被查询使用,只有写入维护开销,那就果断删掉。留着一个不用的索引视图,等于每天都在为它交“存储和写入的税”。
我记得当时第一次把v_MonthlySales这个索引视图部署到生产后,那条折磨了团队好几周慢的报表查询,从12秒直接降到0.8秒。那种成就感确实让人上头,但后面的维护工作也让我清醒地认识到:索引视图不是一个建完就完事的对象,它是一个需要持续关注和维护的“实体存储”。理解了它的价值,也理解了它的代价,你才算真正掌握了SQL Server里的索引视图。
