通过删除执行计划中的排序运算符来优化 SQL 查询
Posted
技术标签:
【中文标题】通过删除执行计划中的排序运算符来优化 SQL 查询【英文标题】:Optimizing SQL queries by removing Sort operator in Execution plan 【发布时间】:2011-05-14 10:23:55 【问题描述】:我刚刚开始考虑通过索引优化我的查询,因为 SQL 数据正在快速增长。我查看了优化器如何通过 SSMS 中的执行计划处理我的查询,并注意到正在使用排序运算符。我听说排序运算符表示查询中的错误设计,因为可以通过索引过早地进行排序。所以这里有一个类似于我正在做的示例表和数据:
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
这是一个示例查询:
SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]
我创建了一个非聚集索引来帮助加快查询速度:
CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])
为了构建 IX_Store 索引,我从简单的谓词开始
[ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)
然后我为 ORDER BY 添加[Phone]
列并覆盖 SELECT 输出
所以即使建立了索引,优化器仍然使用排序运算符(而不是索引排序),因为[Phone]
在[ParentStoreId]
和[Type]
之后排序。如果我从索引中删除 [Type]
列并运行查询:
SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
--AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]
那么优化器当然不会使用排序运算符,因为[Phone]
是按[ParentStoreId]
排序的。
所以问题是如何创建一个涵盖查询的索引(包括[Type]
谓词)并且不让优化器使用排序?
编辑:
我正在使用的表有超过 2000 万行
【问题讨论】:
您确实应该将[StoreId]
设为主键(顺便说一下,它也默认为集群),而不仅仅是添加唯一索引。
您可以通过在 Phone 列上创建 second 索引来解决此问题。
@Lucero,我修改了我的帖子,将[StoreId]
标记为主键,虽然我认为这不会解决排序问题
@zespri,我刚刚注意到我正在使用的表格很大。创建新索引会占用大量硬盘空间
@jodev,我的评论不是对您问题的回答,而是更一般的设计建议。拥有一个集群的、小的、连续的主键可以帮助提高系统的整体性能,这就是为什么这是一个很好的开始。
【参考方案1】:
首先,您应该验证排序实际上是性能瓶颈。排序的持续时间将取决于要排序的元素的数量,并且特定父存储的存储数量可能很小。 (假设在应用 where 子句后应用了排序运算符)。
我听说排序运算符表明查询中的设计不好,因为可以通过索引过早地进行排序
这是一个过度概括。通常,排序运算符可以很容易地移到索引中,并且,如果只获取结果集的前几行,则可以大大降低查询成本,因为数据库不再需要获取所有匹配的行(并对它们进行排序) all) 查找第一个,但可以按结果集顺序读取记录,一旦找到足够的记录就停止。
在您的情况下,您似乎正在获取整个结果集,因此排序不太可能使事情变得更糟(除非结果集很大)。此外,在您的情况下,构建有用的排序索引可能并非易事,因为 where 子句包含一个或。
现在,如果您仍想摆脱该排序运算符,您可以尝试:
SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] in (0, 1)
ORDER BY [Phone]
或者,您可以尝试以下索引:
CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Phone], [Type])
尝试让查询优化器仅对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]
与
CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])
并在应用程序服务器上对两个列表进行排序,您可以在其中合并(如在合并排序中)预排序的列表,从而避免完全排序。但这确实是一个微优化,虽然将排序本身加快了一个数量级,但不太可能对查询的总执行时间产生太大影响,因为我预计瓶颈是网络和磁盘 I/O,尤其是考虑到磁盘将执行大量随机访问,因为索引不是集群的。
【讨论】:
我使用的表有超过 2000 万行。大约有 50 个不同的“[ParentStoreId]”值和 8 个不同的“[Type]”值。最后,我要对大约 200K 行进行排序,这似乎会减慢查询速度。你的信息很有用,我会试试看 @meriton 你能解释一下为什么使用“输入(0,1)”会有所不同吗?这与“或”有何不同?为什么电话号码会在没有排序运算符的情况下自动排序?据我了解,如果索引是在 ParentStoreId、Type、Phone 上创建的,那么电话号码是否会为每种类型单独排序?以上是关于通过删除执行计划中的排序运算符来优化 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章