如何将聚集索引扫描改进为聚集索引查找?
How to improve Clustered Index scan to Clustered Index seek?
我有两张桌子
[dbo].[Employee](
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[Title] [varchar](50) NULL,
[Role] [int] NULL,
[Status] [varchar](1) NULL)
[dbo].[Roles](
[id] [int] IDENTITY(1,1) NOT NULL,
[Role] [varchar](25) NULL,
[Description] [varchar](25) NULL)
使用 id 主聚簇键。
我也有存储过程
SELECT
[EmployeeID]
,[Title]
,employee.[Role]
,roles.Role AS RoleName
FROM [dbo].Employee AS employee
INNER JOIN [dbo].Roles AS roles ON roles.id = employee.Role
WHERE [Status] <> 'D'
执行计划向我展示了一个我想避免的 'Clustered Index Scan'。有什么方法可以将它转换为 'Clustered Index Seek'?
Execution Plan screen
正如我在评论中提到的,dbo.Employees
上的 CLUSTERED INDEX
不会在这里帮助您。这样做的原因是因为您在 status
列上过滤,它只是 included 在 CLUSTERED INDEX
中,但没有对其进行排序。因此,RDBMS 别无选择,只能扫描整个 table 以过滤掉 Status
值为 D
.
的行
您可以在 Status
列上添加 INDEX
并在其他列上添加 INCLUDE
,这 可能 导致索引查找 (不是聚集索引查找),但是,由于您使用 <> D
,因此 RDBMS 可能仍会选择执行扫描;是否确实取决于您的数据分布:
CREATE INDEX IX_EmployeeStatus ON dbo.Employee (Status) INCLUDE (Title, Role);
同时添加一个 FOREIGN KEY
到您的 table:
ALTER TABLE dbo.Employee ADD CONSTRAINT FK_EmployeeRole FOREIGN KEY (Role) REFERENCES dbo.Roles (id);
如果您的 Status 列上有一个索引,那么该代码应该会导致两次索引查找。一份用于 < 'D',一份用于 > 'D'。您只需要一个关于 Status 的索引。
您可以使用临时 table 将其转换为一次查找,将“不是”转换为“是”,这在查询调优中始终是个好主意:
CREATE TABLE #StatusMatch (StatusCode varchar(1) NOT NULL PRIMARY KEY CLUSTERED)
INSERT INTO #StatusMatch WITH(TABLOCKX)
SELECT Status
FROM dbo.Employee WITH(NOLOCK)
WHERE Status <> 'D'
GROUP BY Status
ORDER BY Status;
SELECT a.EmployeeID, a.Title, a.Role, b.Description AS RoleName
FROM dbo.Employee a WITH(NOLOCK)
INNER JOIN dbo.Roles b WITH(NOLOCK) ON a.Role = b.id
INNER JOIN #StatusMatch c ON a.Status = c.StatusCode
我有两张桌子
[dbo].[Employee](
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[Title] [varchar](50) NULL,
[Role] [int] NULL,
[Status] [varchar](1) NULL)
[dbo].[Roles](
[id] [int] IDENTITY(1,1) NOT NULL,
[Role] [varchar](25) NULL,
[Description] [varchar](25) NULL)
使用 id 主聚簇键。 我也有存储过程
SELECT
[EmployeeID]
,[Title]
,employee.[Role]
,roles.Role AS RoleName
FROM [dbo].Employee AS employee
INNER JOIN [dbo].Roles AS roles ON roles.id = employee.Role
WHERE [Status] <> 'D'
执行计划向我展示了一个我想避免的 'Clustered Index Scan'。有什么方法可以将它转换为 'Clustered Index Seek'? Execution Plan screen
正如我在评论中提到的,dbo.Employees
上的 CLUSTERED INDEX
不会在这里帮助您。这样做的原因是因为您在 status
列上过滤,它只是 included 在 CLUSTERED INDEX
中,但没有对其进行排序。因此,RDBMS 别无选择,只能扫描整个 table 以过滤掉 Status
值为 D
.
您可以在 Status
列上添加 INDEX
并在其他列上添加 INCLUDE
,这 可能 导致索引查找 (不是聚集索引查找),但是,由于您使用 <> D
,因此 RDBMS 可能仍会选择执行扫描;是否确实取决于您的数据分布:
CREATE INDEX IX_EmployeeStatus ON dbo.Employee (Status) INCLUDE (Title, Role);
同时添加一个 FOREIGN KEY
到您的 table:
ALTER TABLE dbo.Employee ADD CONSTRAINT FK_EmployeeRole FOREIGN KEY (Role) REFERENCES dbo.Roles (id);
如果您的 Status 列上有一个索引,那么该代码应该会导致两次索引查找。一份用于 < 'D',一份用于 > 'D'。您只需要一个关于 Status 的索引。
您可以使用临时 table 将其转换为一次查找,将“不是”转换为“是”,这在查询调优中始终是个好主意:
CREATE TABLE #StatusMatch (StatusCode varchar(1) NOT NULL PRIMARY KEY CLUSTERED)
INSERT INTO #StatusMatch WITH(TABLOCKX)
SELECT Status
FROM dbo.Employee WITH(NOLOCK)
WHERE Status <> 'D'
GROUP BY Status
ORDER BY Status;
SELECT a.EmployeeID, a.Title, a.Role, b.Description AS RoleName
FROM dbo.Employee a WITH(NOLOCK)
INNER JOIN dbo.Roles b WITH(NOLOCK) ON a.Role = b.id
INNER JOIN #StatusMatch c ON a.Status = c.StatusCode