2013-07-09 29 views
0

我有一个叫读数有> 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个不同的tunnel_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 
) 

这再一次只有几tunnel_id的慢。更让我更困惑的是内部选择快速运行缓慢的ID,如果我硬编码最大的ID而不是子查询它也快速运行。

我在这里错过了什么让这个查询执行得不好?

编辑意见:

Tunnel_id不是唯一的,每个通道都有数以百万计的行。这是在Sql Server 2012上运行的。

我包含来自快速和慢速运行的实际执行计划,它们是相同的。

快速: sql fast execution plan

慢:

sql fast execution plan

但你可以看到,在不到一秒钟的第一执行而第二个需要51秒。

+0

我猜它的很大一部分是你的'ORDER BY ID DESC' - 默认情况下,你可能在'id ASC'上有聚簇索引,所以有可能在后台发生排序。 – JNK

+0

为什么你首先有一个子查询? –

+0

建议尝试使用tunel_id的参数封装在存储过程中进行查询。 otimizer可能会发生一些奇怪的事情,以及如何缓存执行计划。一般来说,根据Micrsoft Press的Book 70-461,查询Microsoft SQL Server,与特定查询相比,查询计划对存储过程的缓存效率更高。只是一个想法。 – Karl

回答

0

刚刚发现,你可以提示使用tunnel_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秒的回报,其工作原理。

1

该计划基本上扫描整个聚集索引从开始到结束并查找与tunnel_id = @ tunnel_id的第一行。

我的教育猜测是'慢'隧道在聚集索引的开头没有任何行,因此它必须扫描更多的行。

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

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

这可能替换tunnel_id现有的索引。

+0

几乎在同一时间几乎相同的答案:P 有趣的是在关键字处添加id,但是如果它在desc处(如在查询中)会更好吗? – Alejandro

+0

@Ajjandro - 哈! :)是将ID放入索引而不是包含在内,可以直接查找单行。关于DESC的好处。我相信它可以使用它(只是做一个反向扫描),但是这当然应该被测试。 –

+0

我同意。匹配'tunnel_id = 13'的值可能都在索引的末尾。 SQL Server会假定它们是平均分布的。 [使用跟踪标志9130](http://sqlblogcasts.com/blogs/sqlandthelike/archive/2012/12/06/my-new-favourite-traceflag.aspx)将显示在“TOP 1”之前实际扫描了多少行'被找到了。顺便说一句,如果'ID'是聚簇索引键,那么它已经被自动包含在非唯一非聚簇索引中作为键列。 –

1

这里有趣的部分是SQL根本不使用tunnel_id中的索引,而只是在整个表中扫描表,如果它大到76百万行那么速度很慢。 我认为它没有使用它的真正原因是因为按ID排序,因为它必须执行查找,然后进行额外的排序。我首先怀疑参数嗅探是这里的主要问题。

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

CREATE NONCLUSTERED INDEX IX_tunnel_id ON dbo.readings (tunnel_id) INCLUDE (id, local_time, recorded_time) 

需要注意的是,虽然这样可以提高这个特定的查询,它会使插入和更新慢一点,并且需要额外的存储空间。