通过删除执行计划中的排序运算符来优化SQL查询

jod*_*dev 22 sql database sql-server sql-server-2005 sql-server-2008

我刚刚开始考虑通过索引优化我的查询,因为SQL数据正在快速增长.我查看了优化器如何通过SSMS中的执行计划处理我的查询,并注意到正在使用Sort运算符.我听说Sort操作符表示查询中的设计不好,因为可以通过索引提前进行排序.所以这里是一个示例表和数据类似于我正在做的事情:

IF OBJECT_ID('dbo.Store') IS NOT NULL DROP TABLE dbo.[Store]
GO

CREATE TABLE dbo.[Store]
(
    [StoreId] int NOT NULL IDENTITY (1, 1),
    [ParentStoreId] int NULL,
    [Type] int NULL,
    [Phone] char(10) NULL,
    PRIMARY KEY ([StoreId])
)

INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 0, '2223334444')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 0, '3334445555')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 1, '0001112222')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 1, '1112223333')
GO
Run Code Online (Sandbox Code Playgroud)

这是一个示例查询:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]
Run Code Online (Sandbox Code Playgroud)

我创建一个非聚集索引来帮助加快查询速度:

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])
Run Code Online (Sandbox Code Playgroud)

要构建IX_Store索引,我从简单谓词开始

[ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)
Run Code Online (Sandbox Code Playgroud)

然后我添加[Phone]ORDER BY 的列并覆盖SELECT输出

因此,即使构建了索引,优化器仍然使用Sort运算符(而不是索引排序),因为它[Phone]是在AFTER [ParentStoreId]AND 之后排序的[Type].如果我[Type]从索引中删除列并运行查询:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
--AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]
Run Code Online (Sandbox Code Playgroud)

然后,优化器不使用Sort运算符,因为它[Phone]是按排序的[ParentStoreId].

所以问题是如何创建一个覆盖查询(包括[Type]谓词)的索引,而不是让优化器使用Sort?

编辑:

我正在使用的表有超过2000万行

mer*_*ike 18

首先,您应该验证排序实际上是性能瓶颈.排序的持续时间取决于要排序的元素的数量,并且特定父级存储的存储数量可能很小.(假设在应用where子句后应用了sort运算符).

我听说Sort操作符表示查询中的设计不好,因为可以通过索引提前进行排序

这是一种过度概括.通常,排序运算符可以简单地移动到索引中,并且,如果只获取结果集的前几行,则可以大大降低查询成本,因为数据库不再需要获取所有匹配的行(并对它们进行排序)所有)找到第一个,但可以按结果集顺序读取记录,并在找到足够的记录后停止.

在您的情况下,您似乎正在获取整个结果集,因此排序不太可能使事情变得更糟(除非结果集很大).此外,在您的情况下,构建有用的排序索引可能并不简单,因为where子句包含一个或.

现在,如果您仍想摆脱该排序运算符,您可以尝试:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] in (0, 1)
ORDER BY [Phone]    
Run Code Online (Sandbox Code Playgroud)

或者,您可以尝试以下索引:

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Phone], [Type])
Run Code Online (Sandbox Code Playgroud)

尝试让查询优化器ParentStoreId仅对索引范围进行扫描,然后扫描索引中的所有匹配行,如果Type匹配则输出它们.但是,这可能会导致更多磁盘I/O,从而减慢查询速度而不是加快速度.

编辑:作为最后的手段,你可以使用

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] = 0
ORDER BY [Phone]

UNION ALL

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] = 1
ORDER BY [Phone]
Run Code Online (Sandbox Code Playgroud)

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])
Run Code Online (Sandbox Code Playgroud)

并在应用程序服务器上对两个列表进行排序,您可以在其中合并(如合并排序)预先排序的列表,从而避免完整的排序.但这实际上是一种微优化,虽然将排序本身加速一个数量级,但不太可能影响查询的总执行时间,因为我预计瓶颈是网络和磁盘I/O,特别是考虑到磁盘将进行大量随机访问这一事实,因为索引没有聚集.