在数据库开发和维护过程中,经常会遇到一些复杂的查询需求,这些查询可能包含多个连接(JOIN)操作、聚合函数(如SUM、COUNT、AVG等)。为了简化这些复杂查询的使用,提高查询性能,SQL Server提供了索引视图(Indexed Views)这一功能。索引视图不仅可以简化查询语句,还可以通过物理存储查询结果来提高查询效率。本文将详细介绍索引视图的概念、创建方法以及如何合理使用索引视图来提升数据库查询性能。
视图是SQL Server中的一种对象,它允许将复杂的SELECT查询语句封装成一个单一的查询。例如,假设有一个管理客户发票的数据库,需要查询居住在岘港市的所有客户的订单详情。一个典型的查询可能包含多个连接操作,如下所示:
SELECT a.Id, a.FirstName, a.LastName, d.ProductId, d.ProductName
FROM Customers a
JOIN Orders b ON a.CustomerId = b.CustomerId
JOIN OrderDetails c ON b.OrderId = c.OrderId
JOIN Products d ON c.ProductId = d.ProductId
WHERE a.City = 'Danang'
如果数据库工程师将这个复杂的查询直接交给后端开发人员,可能会让他们感到困惑。在这种情况下,数据库工程师可以创建一个名为vOrderDetail_DanangCity的视图,如下所示:
CREATE VIEW dbo.vOrderDetail_DanangCity AS
SELECT a.Id, a.FirstName, a.LastName, d.ProductId, d.ProductName
FROM Customers a
JOIN Orders b ON a.CustomerId = b.CustomerId
JOIN OrderDetails c ON b.OrderId = c.OrderId
JOIN Products d ON c.ProductId = d.ProductId
WHERE a.City = 'Danang'
现在,数据库工程师为后端开发人员提供了一个名为vOrderDetail_DanangCity的视图。开发人员只需要知道这个视图的作用,而不需要关心它是如何实现的。在需要使用视图的地方,他们只需要执行以下查询:
SELECT * FROM dbo.vOrderDetail_DanangCity
这样,开发人员只需要使用一行查询语句就可以获得结果。但是,视图内部发生了什么呢?在这种情况下,创建视图只是一个快捷方式,当调用视图时,数据库管理系统(DBMS)仍然需要重新运行上述查询以获取结果。这意味着许多连接操作、子查询和聚合函数将再次执行。
索引视图(或称为物化视图)是一种具有唯一聚集索引的视图。通过创建索引视图,可以更清楚地理解它。假设有一个简单的数据库,其中包含以下关系:一个学生可以参加多个课程,一个课程可以有多个学生参加。学生参加课程后会有考试并获得成绩。在数据库中,有2000行课程数据,5000行学生数据,以及10,000,000行学生课程数据。
假设需要列出至少有1名学生参加的所有课程及其平均成绩。一个典型的查询可能如下所示:
SELECT crs.CourseName, AVG(std_crs.Score) AS Average
FROM dbo.Courses crs
JOIN Student_Course std_crs ON crs.Id = std_crs.CourseId
GROUP BY crs.CourseName
这个查询需要4秒钟才能执行完成。将使用这个查询来创建一个视图:
CREATE VIEW dbo.AverageScores WITH SCHEMABINDING AS
SELECT crs.CourseName, SUM(std_crs.Score) AS TotalScore, COUNT_BIG(*) AS NumberOfRecords
FROM dbo.Courses crs
JOIN dbo.Student_Course std_crs ON crs.Id = std_crs.CourseId
GROUP BY crs.CourseName
注意,将使用SUM和COUNT_BIG(*)来处理这个案例。在SQL Server中创建索引视图有一些限制和要求,需要处理这些限制,例如将AVG转换为SUM和COUNT,以后将计算AVG = SUM / COUNT。不会在这里讨论这些要求。
WITH SCHEMABINDING是一个创建索引视图的强制性要求。它有助于保护视图中出现的列,不能从基础表中更改或删除列。如果想要这样做,首先需要删除视图,更改或删除列,然后重新创建另一个视图。
最后,在该视图上创建一个唯一聚集索引。可以在一个或多个列上创建索引。
CREATE UNIQUE CLUSTERED INDEX CIX_AverageScores ON dbo.AverageScores(CourseName);
现在,有一个索引视图了。当在视图上创建唯一索引时,正在“物化”它。如果视图没有唯一索引,它只是一个快捷方式。当调用视图时,DBMS将重新运行查询以获取结果。这意味着DBMS将再次运行许多连接操作、许多复杂的聚合函数等。
如果视图具有唯一索引,它就是“物化的”。创建了这个视图的副本。这个物化视图用于存储用于创建视图的查询结果。当调用视图时,而不是重新运行包含许多复杂子句的查询,DBMS将从物化视图中读取。
让运行以下查询:
SELECT CourseName, TotalScore / NumberOfRecords AS Average
FROM dbo.AverageScores WITH (NOEXPAND)
现在正在使用索引视图,指出查询应该使用WITH (NOEXPAND)。WITH (NOEXPAND)将让DBMS知道这是一个索引视图,希望使用之前存储的结果。如果忘记添加WITH (NOEXPAND),DBMS将重新运行查询。
结果在00:00:00秒后出现。立即。
运行这两个查询:第一个查询是用于创建视图的查询。第二个查询是索引视图。
SELECT crs.CourseName, SUM(std_crs.Score) / COUNT_BIG(*) AS Average
FROM dbo.Courses crs
JOIN dbo.Student_Course std_crs ON crs.Id = std_crs.CourseId
GROUP BY crs.CourseName
SELECT CourseName, TotalScore / NumberOfRecords AS Average
FROM dbo.AverageScores WITH (NOEXPAND)
可以看到结果是相同的,没有区别。但是执行时间完全不同。当使用索引视图时,它是00:00:00。当使用基础查询时,它是4秒。这种差异是因为索引视图WITH NOEXPAND不需要再次计算结果。它没有连接表和计算聚合函数。它只是读取存储在视图中的结果并显示给。
使用索引视图似乎是一个不错的选择。但是,这是免费的午餐吗?不,它不是。实际上,如果滥用它们,它就是一顿昂贵的午餐。每当基础表(在创建视图时连接的表)发生变化时,DBMS不仅要更新表,重新索引视图,还要重新计算存储的值。在这种情况下,当一些学生参加任何课程时,DBMS必须重新计算该课程的SUM和COUNT,这存储在物化视图中。这也意味着查询越复杂,视图维护就越复杂。
视图维护是一个大问题。维护存储的结果最有效的方法是什么,而不需要重新运行原始查询?由于视图维护问题,创建索引视图有许多限制和要求,例如,不允许使用OUTER JOIN的查询。