Man*_*ion 11 index sql-server-2008 sql-server materialized-view index-tuning
我正在努力在以下场景中设置索引视图,以便在没有两个聚集索引扫描的情况下执行以下查询。每当我为此查询创建索引视图然后使用它时,它似乎忽略了我放在它上面的任何索引:
-- +++ THE QUERY THAT I WANT TO IMPROVE PERFORMANCE-WISE +++
SELECT TOP 1 *
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
ORDER BY t1.somethingelse1
,t2.somethingelse2;
GO
Run Code Online (Sandbox Code Playgroud)
表设置如下:
(在下面的脚本中也有一些行来生成测试数据,以防万一它有助于重现问题)
-- +++ TABLE SETUP +++
CREATE TABLE [dbo].[TB_test1]
(
[PK_ID1] [INT] IDENTITY(1, 1) NOT NULL
,[something1] VARCHAR(40) NOT NULL
,[somethingelse1] BIGINT NOT NULL
CONSTRAINT [PK_TB_test1] PRIMARY KEY CLUSTERED ( [PK_ID1] ASC )
);
GO
create TABLE [dbo].[TB_test2]
(
[PK_ID2] [INT] IDENTITY(1, 1) NOT NULL
,[FK_ID1] [INT] NOT NULL
,[something2] VARCHAR(40) NOT NULL
,[somethingelse2] BIGINT NOT NULL
CONSTRAINT [PK_TB_test2] PRIMARY KEY CLUSTERED ( [PK_ID2] ASC )
);
GO
ALTER TABLE [dbo].[TB_test2] WITH CHECK ADD CONSTRAINT [FK_TB_Test1] FOREIGN KEY([FK_ID1])
REFERENCES [dbo].[TB_test1] ([PK_ID1])
GO
ALTER TABLE [dbo].[TB_test2] CHECK CONSTRAINT [FK_TB_Test1]
GO
-- +++ TABLE DATA GENERATION +++
-- this might not be the quickest way, but it's only to set up test data
INSERT INTO dbo.TB_test1
( something1, somethingelse1 )
VALUES ( CONVERT(VARCHAR(40), NEWID()) -- something1 - varchar(40)
,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1) -- somethingelse1 - bigint
)
GO 100000
RAISERROR( 'Finished setting up dbo.TB_test1', 0, 1) WITH NOWAIT
GO
INSERT INTO dbo.TB_test2
( FK_ID1, something2, somethingelse2 )
VALUES ( ISNULL(ABS(CHECKSUM(NewId())) % ((SELECT MAX(PK_ID1) FROM dbo.TB_test1) - 1), 0) + 1 -- FK_ID1 - int
,CONVERT(VARCHAR(40), NEWID()) -- something2 - varchar(40)
,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1) -- somethingelse2 - bigint
)
GO 100000
RAISERROR( 'Finished setting up dbo.TB_test2', 0, 1) WITH NOWAIT
GO
Run Code Online (Sandbox Code Playgroud)索引视图可能应该定义如下,结果 TOP 1 查询如下。但是我需要什么索引才能使这个查询比没有索引视图的查询性能更好?
CREATE VIEW VI_test
WITH SCHEMABINDING
AS
SELECT t1.PK_ID1
,t1.something1
,t1.somethingelse1
,t2.PK_ID2
,t2.FK_ID1
,t2.something2
,t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
GO
SELECT TOP 1 * FROM dbo.VI_test ORDER BY somethingelse1,somethingelse2
GO
Run Code Online (Sandbox Code Playgroud)
Pau*_*ite 12
它似乎忽略了我放在它上面的任何索引
除非您使用的是 SQL Server 企业版(或等效的试用版和开发版),否则您需要WITH (NOEXPAND)
在视图引用上使用才能使用它。事实上,即使您使用的是 Enterprise,也有充分的理由使用该提示。
如果没有提示,查询优化器(在企业版中)可能会在使用物化视图或访问基表之间做出基于成本的选择。在视图与基表一样大的情况下,此计算可能有利于基表。
另一个有趣的点是,在没有NOEXPAND
提示的情况下,视图引用总是在优化开始之前扩展到基本查询。随着优化的进行,优化器可能会也可能不会将扩展定义匹配回物化视图,这取决于之前的优化活动。您的简单查询几乎肯定不是这种情况,但我提到它是为了完整性。
因此,使用NOEXPAND
表提示是您的主要选择,但您也可能会考虑仅具体化基表键和视图中排序所需的列。在组合键列上创建一个唯一的聚集索引,然后在排序列上创建一个单独的非聚集索引。
这将减小物化视图的大小,并限制为保持视图与基表同步而必须进行的自动更新的数量。然后可以编写您的查询以从视图(理想情况下使用NOEXPAND
)获取所需顺序的前 1 个键,然后连接回基表以使用视图中的键获取任何剩余的列。
另一种变体是将视图聚集在排序列和表键上,然后编写查询以使用键从基表中手动获取非视图列。最适合您的选择取决于更广泛的背景。一个很好的决定方法是使用真实数据和工作负载对其进行测试。
CREATE VIEW VI_test
WITH SCHEMABINDING
AS
SELECT
t1.PK_ID1,
t1.something1,
t1.somethingelse1,
t2.PK_ID2,
t2.FK_ID1,
t2.something2,
t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2
ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Brute force unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(somethingelse1, somethingelse2, PK_ID1, PK_ID2);
GO
SELECT TOP (1) *
FROM dbo.VI_test WITH (NOEXPAND)
ORDER BY somethingelse1,somethingelse2;
Run Code Online (Sandbox Code Playgroud)
执行计划:
-- Minimal unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(PK_ID1, PK_ID2)
WITH (DROP_EXISTING = ON);
GO
-- Nonclustered index for ordering
CREATE NONCLUSTERED INDEX ix
ON dbo.VI_test (somethingelse1, somethingelse2);
Run Code Online (Sandbox Code Playgroud)
执行计划:
这个计划中有一个查找,但它只用于获取单行。
ALTER VIEW VI_test
WITH SCHEMABINDING
AS
SELECT
t1.PK_ID1,
t2.PK_ID2,
t1.somethingelse1,
t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2
ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(somethingelse1, somethingelse2, PK_ID1, PK_ID2);
Run Code Online (Sandbox Code Playgroud)
询问:
SELECT TOP (1)
V.PK_ID1,
TT1.something1,
V.somethingelse1,
V.PK_ID2,
TT2.FK_ID1,
TT2.something2,
V.somethingelse2
FROM dbo.VI_test AS V WITH (NOEXPAND)
JOIN dbo.TB_test1 AS TT1 ON TT1.PK_ID1 = V.PK_ID1
JOIN dbo.TB_test2 AS TT2 ON TT2.PK_ID2 = V.PK_ID2
ORDER BY somethingelse1,somethingelse2;
Run Code Online (Sandbox Code Playgroud)
执行计划:
这显示了正在检索的表键(按顺序从视图聚簇索引中获取单行),然后在基表上进行两次单行查找以获取剩余的列。