使用“OR”运算符时的 SQL Server 索引扫描

And*_*sch 2 sql-server optimization nonclustered-index

我们实现了一个 Google 风格的搜索,其中在前端触发去抖动后运行 SQL 查询。(我们知道 SQL 可能是错误的技术,但我在这里陷入了启动混乱。)查询:

SELECT 
    TOP(50) [Name], [Surname]
FROM 
    [dbo].[Clients]
WHERE 
    [Name] LIKE @SearchTerm + '%' OR
    [Surname] LIKE @SearchTerm + '%'
Run Code Online (Sandbox Code Playgroud)

这是一个相当大的表,所以我在两列上添加了两个非聚集索引以帮助加快速度:

CREATE NONCLUSTERED INDEX [IX_Patients_Name] ON [dbo].[Clients]
(
    [Name] ASC
)
INCLUDE([Surname]);

CREATE NONCLUSTERED INDEX [IX_Patients_Surname] ON [dbo].[Clients]
(
    [Surname] ASC
)
INCLUDE([Name]);
Run Code Online (Sandbox Code Playgroud)

我的想法是 SQL 会在两列上进行索引查找,但查询优化器似乎决定使用索引扫描

聚集索引查找

对于这个简单的用例,这可能不是一个真正的问题,但我们有更复杂的版本,具有多个连接等。

有什么方法可以优化此查询以使用搜索吗?

Jos*_*ell 6

正如您在问题中提到的,这种 Google 风格的查询并不是 SQL Server 真正“擅长”的。Erik Darling在他的文章The Only Thing Thing Worse Than Optional Parameters... 中谈到了这个确切的查询反模式。

抛开这一切。

可以通过这种类型的查询自然地进行搜索,但如您所见,获得扫描更为常见。下面是 StackOverflow2010 示例数据库中的一个示例。

首先,我将创建这两个有用的索引:

CREATE NONCLUSTERED INDEX IX_DisplayName ON dbo.Users (DisplayName) INCLUDE ([Location]);
CREATE NONCLUSTERED INDEX IX_Location ON dbo.Users ([Location]) INCLUDE (DisplayName);
GO
Run Code Online (Sandbox Code Playgroud)

然后我将创建一个类似于您所拥有的程序:

CREATE OR ALTER PROCEDURE dbo.sp_Test
    @SearchTerm nvarchar(100)
AS
BEGIN;
    SELECT TOP (50)
        DisplayName, 
        [Location]
    FROM 
        dbo.Users
    WHERE 
        DisplayName LIKE @SearchTerm + '%' OR
        [Location] LIKE @SearchTerm + '%'
END;
GO
Run Code Online (Sandbox Code Playgroud)

如果我使用一个相当有选择性的参数运行该过程,我将最终得到一个索引联合计划。如果该参数的选择性较低,则改为使用对覆盖索引之一的扫描。

DBCC FREEPROCCACHE;
GO
EXEC dbo.sp_Test @SearchTerm = N'Josh';
GO
DBCC FREEPROCCACHE;
GO
EXEC dbo.sp_Test @SearchTerm = N'S';
GO
Run Code Online (Sandbox Code Playgroud)

来自 pastetheplan.com 的索引联合和扫描计划的屏幕截图

执行计划在这里。

请注意,即使您UNION直接将其编写为单独的查询也是如此。

如链接的帖子中所述,可靠地获取索引联合计划的一种方法是向FORCESEEK要联合的表添加提示。

如果我将 proc 更改为此,我不会对任何一个计划进行扫描:

CREATE OR ALTER PROCEDURE dbo.sp_Test
    @SearchTerm nvarchar(100)
AS
BEGIN;
    SELECT TOP (50)
        DisplayName, 
        [Location]
    FROM 
        dbo.Users WITH (FORCESEEK)
    WHERE 
        DisplayName LIKE @SearchTerm + '%' OR
        [Location] LIKE @SearchTerm + '%'
END;
GO
Run Code Online (Sandbox Code Playgroud)

查询的更大问题(无论如何在问题中进行了简化)是您在使用时TOP没有使用ORDER BY,这可能会根据使用的索引产生截然不同的搜索结果。确保您的真实查询具有ORDER BY,或者以某种方式解决了此问题。