Sql server 对聚集索引上的某些条件的查询速度较慢

Sql server 对聚集索引上的某些条件的查询速度较慢,sql-server,query-optimization,Sql Server,Query Optimization,我有一个名为readings的表,其中有超过7600万行,我正在运行此查询: declare @tunnel_id int = 13 SELECT TOP 1 local_time, recorded_time FROM readings WHERE tunnel_id = @tunnel_id ORDER BY id DESC id列是一个bigint,设置为主键,并有一个聚集索引,tunnel_id字段上也有一个索引 我正在尝试的20个不同的隧道id中有16个在不到一秒钟的时间内返回,效

我有一个名为readings的表,其中有超过7600万行,我正在运行此查询:

declare @tunnel_id int = 13
SELECT TOP 1 local_time, recorded_time
FROM readings
WHERE tunnel_id = @tunnel_id 
ORDER BY id DESC
id列是一个bigint,设置为主键,并有一个聚集索引,tunnel_id字段上也有一个索引

我正在尝试的20个不同的隧道id中有16个在不到一秒钟的时间内返回,效果非常好。然而,在最后4秒左右的时间里,查询需要40秒,并使用数十万次读取

我尝试将查询修改为:

SELECT TOP (1) local_time, recorded_time
FROM readings
where id = (
    SELECT TOP 1 id
    FROM readings
    WHERE tunnel_id = 13
    ORDER BY id DESC
)
这又是一个只有几个隧道id的慢。更让我困惑的是,对于较慢的id,内部select运行得很快,如果我硬编码最大id而不是子查询,它也会运行得很快

我在这里遗漏了什么使这个查询执行得很差

编辑评论:

隧道id不是唯一的,每个隧道有数百万行。这是在Sql Server 2012上运行的

我包括了快速运行和慢速运行的实际执行计划,它们是相同的

快速:

慢:


但正如您所看到的,第一个执行不到一秒钟,而第二个执行需要51秒。

该计划基本上从头到尾扫描整个聚集索引,并查找具有tunnel\u id=@tunnel\u id的第一行

我有根据地猜测,“慢”通道在聚集索引的开头没有任何行,因此它必须扫描更多的行

此非聚集索引应该可以加快速度:

CREATE NONCLUSTERED INDEX [IX_FOO] ON [readings]
(
    tunnel_id, 
    ID 
)
INCLUDE 
(
    local_time, 
    recorded_time
)

这可能会取代tunnel\u id上现有的索引。

这里有趣的部分是,SQL根本不使用tunnel\u id中的索引,只是扫描整个表,如果它像7600万行那样大,那么速度会很慢。 我认为它不使用它的真正原因是因为按id排序,因为它必须执行查找,然后再执行额外的排序。一开始我怀疑参数嗅探是这里的主要问题

我会尝试更改索引,使其覆盖。如果可能,在索引中包括本地时间、记录的时间和id(不能100%确定是否需要它,因为它是集群密钥)


请注意,虽然这可以改进此特定查询,但它会使插入和更新速度稍慢,并需要额外的存储空间。

刚刚发现,您可以提示使用隧道id索引:

declare @tunnel_id int = 13
SELECT TOP 1 local_time, recorded_time
FROM readings
WITH (INDEX(idx_tunnel_id))
WHERE tunnel_id = @tunnel_id 
ORDER BY id DESC

它按预期工作,并在不到1秒的时间内返回。

我猜其中很大一部分是您的
按id描述的订单
-默认情况下,您可能在
id ASC
上有聚集索引,因此,在后台可能会发生排序。为什么首先会有子查询?建议尝试将查询包装在存储过程中,并使用tunel_id参数。otimizer可能会出现一些奇怪的情况,以及执行计划是如何缓存的。一般来说,根据Micrsoft Press的书70-461《查询Microsoft SQL Server》,与即席查询相比,存储过程的查询计划缓存效率更高。只是想一想。tunnel_id的索引是唯一的吗?您使用的是什么版本的SQL Server?你能把“慢”和“快”案件的执行计划也张贴出来吗?我已经包括了你的问题的答案@Alejandro。至于优化器Karl,我曾尝试在查询中使用option(recompile),但没有任何效果,这在过去为我解决了其他类似的问题。几乎在同一时间得到了几乎相同的答案:P对在键处添加id很感兴趣,但如果在查询中添加desc会更好吗?@Alejandro--ha:)是的,将ID放在索引中而不是包含中将允许直接搜索单行。关于DESC的好观点。我相信它无论如何都可以使用它(只是做一个反向扫描),但这当然应该经过测试。我同意。与
tunnel_id=13
匹配的值很可能都位于索引的末尾。SQL Server将假定它们是均匀分布的。将显示在找到
TOP 1
之前实际扫描了多少行。顺便说一句,如果
ID
是聚集索引键,那么它将自动作为键列包含在非唯一非聚集索引中。我使用了跟踪标志9103,实际扫描的行数约为5300万。我确实认为这个索引将有助于这个特定的查询,但我不太愿意这样做,因为表已经是3.5gb了,索引已经是5.8gb了。我认为更改索引会对许多其他查询产生不利影响。
declare @tunnel_id int = 13
SELECT TOP 1 local_time, recorded_time
FROM readings
WITH (INDEX(idx_tunnel_id))
WHERE tunnel_id = @tunnel_id 
ORDER BY id DESC