读书笔记:一个非聚集索引查询引起的“表扫描” + “阻塞”问题
以下是使用AdventureWorks2008R2数据库测试一个因全表扫描而引起的阻塞问题。
步骤:
一、建表
CREATE TABLE Employee_Demo_Heap
(
[BusinessEntityID] [int] NOT NULL,
[NationalIDNumber] [nvarchar](15) NOT NULL,
[LoginID] [nvarchar](256) NOT NULL,
[OrganizationNode] [hierarchyid] NULL,
[OrganizationLevel] AS ([OrganizationNode].[GetLevel]()),
[JobTitle] [nvarchar](50) NOT NULL,
[BirthDate] [date] NOT NULL,
[MaritalStatus] [nchar](1) NOT NULL,
[Gender] [nchar](1) NOT NULL,
[HireDate] [date] NOT NULL,
[SalariedFlag] [dbo].[Flag] NOT NULL,
[VacationHours] [smallint] NOT NULL,
[SickLeaveHours] [smallint] NOT NULL,
[CurrentFlag] [dbo].[Flag] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT PK_Employee_BusinessEntityID_Demo_Heap PRIMARY KEY NONCLUSTERED
(
BusinessEntityID ASC
)
)
GO
CREATE NONCLUSTERED INDEX IX_Employee_NationalIDNumber_Demo_Heap ON Employee_Demo_Heap
(
NationalIDNumber ASC
)
go
CREATE NONCLUSTERED INDEX IX_Employee_ModifiedDate_Demo_Heap ON Employee_Demo_Heap
(
ModifiedDate ASC
)
go
insert into Employee_Demo_Heap
(
BusinessEntityID,
NationalIDNumber,
LoginID,
OrganizationNode,
JobTitle,
BirthDate,
MaritalStatus,
Gender,
HireDate,
SalariedFlag,
VacationHours,
SickLeaveHours,
CurrentFlag,
ModifiedDate
)
select BusinessEntityID,
NationalIDNumber,
LoginID,
OrganizationNode,
JobTitle,
BirthDate,
MaritalStatus,
Gender,
HireDate,
SalariedFlag,
VacationHours,
SickLeaveHours,
CurrentFlag,
ModifiedDate
from HumanResources.Employee
go
CREATE NONCLUSTERED INDEX IX_Employee_JobTitle_Demo_Heap ON Employee_Demo_Heap
(
[JobTitle] ASC
)
go
二、新建连接A中执行以下update语句
begin tran a
update Employee_Demo_Heap set jobtitle = 'Changed1' where BusinessEntityID = 70
三、新建连接B中执行以下select语句
select BusinessEntityID, loginID, jobtitle from Employee_Demo_Heap where BusinessEntityID in ( 3, 4, 100)
发现连接B中的语句已被阻塞住了。查询 select * from sys.dm_tran_locks 发现
对于 RID 1:23128:19 一个连接申请了 X 锁,一个连接申请了 S 锁,S 锁的等待状态是WAIT。
也就是说连接B也对BusinessEntityID = 70这条记录去申请S 锁,但是连接A事务未提交,X 锁未释放。导致被阻塞住了。
但问题是连接B中的select语句并未查询 BusinessEntityID = 70 的记录,为什么还会对 BusinessEntityID = 70 的记录申请 S 锁 呢?
通过对执行计划的分析,我们发现,select BusinessEntityID, loginID, jobtitle from Employee_Demo_Heap where BusinessEntityID in ( 3, 4, 100) 语句采用了 Table Scan ,这就是为什么也会查询 BusinessEntityID = 70 记录的原因了,这也导致了连接B也对BusinessEntityID = 70这条记录去申请S 锁。从而导致了连接B中的语句被阻塞住了。
如果我们把Employee_Demo_Heap的主键 PK_Employee_BusinessEntityID_Demo_Heap 换成聚集索引,再执行连接B中的语句,我们会发现并没有被阻塞。
由此,我们可以得到:
- 聚集索引容易使用 Clustered Index Seek 从而减少阻塞的概率。
- 非聚集索引容易使SQL Server认为非聚集索引+Bookmark Lookup并不比全表扫描快,而采用全表扫描,从而增加阻塞发生的概率。
可见用聚集索引的查询还是比用非聚集索引要可靠的多。能用到聚集索引的地方,我们还是尽量使用聚集索引吧。