为什么这个查询 运行 这么慢?

Why this query is running so slow?

此查询运行速度非常快(<100 毫秒):

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 table 的 [Time] 列上添加索引,但优化器似乎没有使用该索引。尝试使用 With (index (Ix_Sms_Time));但令我惊讶的是,这需要更多时间(29 秒!)。

下面是实际的执行计划:

两个查询的执行计划相同。这里提到的表有 5M 到 8M 行(索引 < 1% 碎片化并且统计数据已更新)。我在 16 核 32GB 内存 Windows 2008 R2 机器上使用 MS SQL Server 2008R2)

仅在客户端过滤器具有 运行 后才强制启动时间过滤器是否有帮助?

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'

使用以下 Index Key ColumnsSms 上创建索引(按此顺序):

  1. 公司编号
  2. 时间

您可能需要也可能不需要添加 Id 作为 Included Column

您的时间列是什么数据类型? 如果是日期时间,请尝试将您的“2015-04-10”转换为等效数据类型,以便它可以使用索引。

Declare @test datetime
Set @test='2015-04-10'

然后修改你的条件:

[Extent2].Time > @test    

如果数据类型不匹配,sql 服务器会隐式转换为匹配的数据类型。并且任何函数或强制转换操作都会阻止使用索引。

我和@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 输出应该可以让您了解实际发生的情况。

想到两件事:

  1. 通过添加额外的限制,数据库将 'harder' 找到符合您的限制的前 10 个项目。从假设 10.000 项(总共 100 万)中查找前 10 行比从 100 项(总共 100 万)中查找前 10 行更容易。
  2. 索引未被使用可能是因为索引是在日期时间列上创建的,如果您也在其中存储时间,则效率不高。您可能想在 [time] 列上创建一个聚集索引(但是您必须删除现在在 [CompanyId] 列上的聚集索引,或者您可以创建一个计算列来存储 [time] 的日期部分time] 列,在此计算列上创建索引并在此列上进行筛选。

我发现 SplittedSms table 上的外键列 (SmsId) 没有索引。我做了一个,现在看来第二个查询几乎和第一个查询一样快。

现在的执行计划:

感谢大家的付出。