为啥这个查询运行这么慢?
Posted
技术标签:
【中文标题】为啥这个查询运行这么慢?【英文标题】:Why this query is running so slow?为什么这个查询运行这么慢? 【发布时间】:2015-04-15 03:57:28 【问题描述】:此查询运行速度非常快(
SELECT TOP (10)
[Extent2].[CompanyId] AS [CompanyId]
,[Extent1].[Id] AS [Id]
,[Extent1].[Status] AS [Status]
FROM [dbo].[SplittedSms] AS [Extent1]
INNER JOIN [dbo].[Sms] AS [Extent2]
ON [Extent1].[SmsId] = [Extent2].[Id]
WHERE [Extent2].[CompanyId] = 4563
AND ([Extent1].[NotifiedToClient] IS NULL)
如果我只添加一个时间过滤器,它会花费太长时间(22 秒!):
SELECT TOP (10)
[Extent2].[CompanyId] AS [CompanyId]
,[Extent1].[Id] AS [Id]
,[Extent1].[Status] AS [Status]
FROM [dbo].[SplittedSms] AS [Extent1]
INNER JOIN [dbo].[Sms] AS [Extent2]
ON [Extent1].[SmsId] = [Extent2].[Id]
WHERE [Extent2].Time > '2015-04-10'
AND [Extent2].[CompanyId] = 4563
AND ([Extent1].[NotifiedToClient] IS NULL)
我尝试在Sms
表的[Time]
列上添加索引,但优化器似乎没有使用该索引。尝试使用With (index (Ix_Sms_Time))
;但令我惊讶的是,这需要更多时间(29 秒!)。
这是实际的执行计划:
两个查询的执行计划相同。这里提到的表有 5M 到 8M 行(索引是
【问题讨论】:
尝试添加 ORDER BY ASC 或 DESC TIME 列并检查。它应该运行得更快。 首先,没有ORDER BY
,TOP
查询返回的行是不确定的。最佳实践是使用TOP
指定ORDER BY
。要提高第二个查询的性能,请尝试对 CompanyID 和 Time 使用复合非聚集索引。这将避免接触行
试过Order By [Time]
;更糟糕的是,29 秒!
你能发布创建表的脚本吗?连同索引定义
能否请您在某处发布实际执行计划,以便我们访问它并查看实际矩阵?
【参考方案1】:
仅在客户端过滤器运行后强制时间过滤器启动是否有帮助?
像这个例子中的FI:
;WITH ClientData AS (
SELECT
[E2].[CompanyId]
,[E2].[Time]
,[E1].[Id]
,[E1].[Status]
FROM [dbo].[SplittedSms] AS [E1]
INNER JOIN [dbo].[Sms] AS [E2]
ON [E1].[SmsId] = [E2].[Id]
WHERE [E2].[CompanyId] = 4563
AND ([E1].[NotifiedToClient] IS NULL)
)
SELECT TOP 10
[CompanyId]
,[Id]
,[Status]
FROM ClientData
WHERE [Time] > '2015-04-10'
【讨论】:
【参考方案2】:使用以下Index Key Columns
(按此顺序)在Sms
上创建索引:
-
公司ID
时间
您可能需要也可能不需要将Id
添加为Included Column
。
【讨论】:
【参考方案3】:您的时间列是什么数据类型? 如果是日期时间,请尝试将您的 '2015-04-10' 转换为等效的数据类型,以便它可以使用索引。
Declare @test datetime
Set @test='2015-04-10'
然后修改你的条件:
[Extent2].Time > @test
如果数据类型不匹配,sql server 会隐式转换为匹配的数据类型。并且任何函数或强制转换操作都会阻止使用索引。
【讨论】:
将日期更改为变量将导致优化器针对未知值优化子句(如果它不是存储过程),并且在日期时间字段和日期接近当天的情况下,它可能会使情况更糟。具有正确格式的日期时间,例如'20150410' 应该没问题。【参考方案4】:我与@JonTirjan 处于同一轨道,只有 Time 的索引会导致很多关键查找,因此您至少应该尝试以下操作:
create index xxx on Sms (Time, CompanyId) include (Id)
或
create index xxx on Sms (CompanyId, Time) include (Id)
如果 Id 是您的聚集索引,则在 include 子句中不需要它。如果您的大部分数据属于 CompanyID 4563,也可以将其作为包含列。
您在实际计划中看到的百分比只是基于行数假设的估计,因此有时完全错误。查看实际行数/执行数 + 统计 IO 输出应该会让您了解实际发生的情况。
【讨论】:
【参考方案5】:想到两件事:
-
通过添加额外的限制,数据库将“更难”找到符合您的限制的前 10 个项目。从假设 10.000 个项目(总共 100 万个)中查找前 10 行比从大约 100 个项目(总共 100 万个)中查找前 10 行更容易。
该索引未使用可能是因为该索引是在日期时间列上创建的,如果您还要在其中存储时间,则效率不高。您可能希望在 [time] 列上创建一个聚集索引(但是您必须删除现在位于 [CompanyId] 列上的聚集索引,或者您可以创建一个计算列来存储 [ time] 列,在此计算列上创建索引并在此列上过滤。
【讨论】:
【参考方案6】:我发现SplittedSms
表的外键列 (SmsId
) 上没有索引。我做了一个,现在第二个查询几乎和第一个查询一样快。
现在的执行计划:
感谢大家的努力。
【讨论】:
以上是关于为啥这个查询运行这么慢?的主要内容,如果未能解决你的问题,请参考以下文章
为啥这个查询这么慢? - PostgreSQL - 从 SERIAL、TIMESTAMP 和 NUMERIC(6,2) 中选择