根据我的研究,我发现很多人提到覆盖索引将有助于减少“键查找”阻塞。我热衷于测试和理解它,但我的测试并没有显示出预期的结果。
首先我运行下面的代码,我尝试在第一个事务上更新表而不提交,然后在另一个会话上,我运行一个查询以从 tableA 中选择 col2 和 col3。根据这本书,第二个会话将被第一个会话阻止。
-- Create the table for testing
CREATE TABLE [TableA]
(
[col1] INT,
[col2] INT,
[col3] INT,
[col4] CHAR(100) DEFAULT('abc') NOT NULL
);
GO
DECLARE @int INT;
SET @int = 1;
-- Load data into the table
WHILE (@int <= 1000)
BEGIN
INSERT INTO [TableA]
([col1], [col2], [col3], [col4])
VALUES (@int*2, @int*2, @int*2, @int*2);
SET @int = @int + 1;
END
GO
CREATE CLUSTERED INDEX [cidx_TableA]
ON [TableA] ([col1]);
-- Create a non-clustered index
CREATE NONCLUSTERED INDEX [idx_TableA_col2]
ON [TableA] ([col2]);
GO
BEGIN TRANSACTION
UPDATE [TableA] SET col3=999 WHERE Col2=4
--Rollback
--ON another session, I query this.
SELECT [col2],col3 FROM [TableA]
WHERE [col2]=66
option (recompile)
第二个会话确实使用对集群索引 cidx_TableA 的键查找并返回结果,我希望它不会返回任何结果,因为假设它会通过第一个会话的更新锁定。为什么?
我还没有创建覆盖索引,因为它根本没有任何阻塞。那么,覆盖指数真的有效吗?
这里有很多东西要解压,但我会说最好的决定可能是开始查看已提交的读取快照隔离 ( RCSI ),这意味着您不再需要锁来读取数据(作为奖励,您可以获得有意义的结果)。
使用当前的索引(假设通过 访问
idx_TableA_col2
),您的更新语句将锁定idx_TableA_col2
索引中更新的行的条目您的 select 语句不需要读取任何锁定位,因为您可以
WHERE [col2]=66
在idx_TableA_col2
索引中查找(避免锁定的索引条目)并使用它来访问表中与此条件匹配的行(同样,这些行不是锁定)。覆盖索引试图避免的锁定问题是更新行的位置,但查询不需要担心此列。您的
update
语句正在更新col3
,它包含在您的 select 语句中,因此您的覆盖索引([col2],[col3])
也会为这些行锁定,因为更新语句将需要更新此索引中的条目。为了演示覆盖索引将解决的问题,您需要将
update
语句更改为不更新col3
,但让where
过滤器返回相同的行:但正如我在第一段中提到的,使用 RCSI 将完全避免这个问题。