SQL查询执行缓慢(对于某些参数值)

时间:2010-07-05 15:29:42

标签: sql sql-server database sql-server-2005 sqlprofiler

我有一个包含多个表的SQL Server 2005数据库。其中一个表用于存储多个设备的时间戳和消息计数器,并具有以下列:

CREATE TABLE [dbo].[Timestamps] (
[Id] [uniqueidentifier] NOT NULL,
[MessageCounter] [bigint] NULL,
[TimeReceived] [bigint] NULL,
[DeviceTime] [bigint] NULL,
[DeviceId] [int] NULL
)

Id是唯一的主键(Guid.Comb),我在DeviceIdMessageCounter列都有索引。

我想要做的是找到某个设备的最后插入的行(具有最大MessageCounter的行)。

奇怪的是查询设备号。 4 (以及除1号以外的所有其他设备)几乎立即返回:

select top 1 * 
   from "Timestamps"
   where DeviceId = 4
   order by MessageCounter desc

设备号的查询相同。 1 需要永远完成:

select top 1 * 
   from "Timestamps"
   where DeviceId = 1 /* this is the only line changed */
   order by MessageCounter desc

最奇怪的是设备1比设备4 少得多

select count(*) from "Timestamps" where DeviceId = 4
(returns 1,839,210)

select count(*) from "Timestamps" where DeviceId = 1
(returns 323,276).

有没有人知道我可能做错了什么?

[编辑]

从两个查询的执行计划中可以清楚地看到,设备1(下图)在索引扫描中创建了更多的行:

Execution plans for device 4 (upper) and device 1 (lower) http://img295.imageshack.us/img295/5784/execplans.png

不同之处在于我将索引扫描节点悬停在执行计划图上:

Device 4 Actual Number of Rows: 1

Device 1 Actual Number of Rows: approx. 6,500,000

6,500,000行是一个非常奇怪的数字,因为我的select count(*)查询返回设备1的大约300,000行!

6 个答案:

答案 0 :(得分:2)

您确定统计信息是最新的吗?使用UPDATE STATISTICS

UPDATE STATISTICS dbo.Timestamps

您是如何运行查询的?如果通过存储过程,您可能遇到parameter sniffing的问题?

答案 1 :(得分:2)

尝试在(DeviceId, MessageCounter DESC)上创建索引。

另外,请尝试以下查询:

select * 
   from "Timestamps"
   where DeviceId = 1
   and MessageCounter = (SELECT MAX(MessageCounter) FROM "Timestamps" WHERE DeviceID = 1)

猜测:性能差异可能是因为DeviceId = 1分布在比DeviceId = 4更多的网页上。通过排序,我怀疑你正在挖掘所有匹配的页面,即使你最终只选择了顶行。

答案 2 :(得分:1)

我认为必须发生这种情况,因为如果您按照MessageCounter降序排序,那么它有6,500,000个必须在找到第一个DeviceId=4而另一个DeviceId之前通过。 1}}有更好的传播

我认为DeviceId=4谓词在执行计划中的Filter运算符之前不会发挥作用。

DeviceId, MessageCounter上的综合索引可以解决此问题。但设备是否DeviceId=4是旧设备,不再记录新数据?如果是这样,您可以将DeviceId = 4记录解压缩到自己的表中并使用分区视图,以便该设备上的查询不会扫描大量不相关的记录。

已更正

另外,选择Guid.Comb作为聚集索引的原因是什么?我假设DeviceId, MessageCounter上的聚集索引在碎片方面具有相似的特征,避免热点,但更有用。

答案 3 :(得分:1)

执行计划图表不是很有帮助,因为它们不显示使用了哪个索引。

最有帮助的信息来自以下查询

select DeviceId, max(MessageCounter) from "Timestamps" group by DeviceId

我假设设备2到4的MessageCounter是相对较高的数字。 MessageCounter是一个相对较低的数字。

在这种情况下,SQL Server如何执行查询:

服务器从高到低的数字读取MessageCounter索引。对于每一行,服务器都会对嵌入式索引进行嵌套搜索,以比较设备ID。

对于设备2-4,这很快就会结束,因为服务器在设备2-4的MessageCounter索引中找到一行。对于设备1,在服务器找到设备1的第一行之前,服务器需要超过6百万次搜索操作。

读取deviceid索引并寻求聚合索引会更快。这应该在323k寻求之后停止。甚至不好。

你应该有一个包含设备ID和MessageCounter的索引(正如Marcelo Cantos指出的那样)。

答案 4 :(得分:0)

我的第一个想法是,这可能是由于参数嗅探 - 本质上SQL Server在第一次运行查询时提出了一个计划,但该查询不能代表典型的工作负载。见http://www.sqlshare.com/solve-parameter-sniffing-by-using-local-variables_531.aspx

有关统计数据的建议很好,但我怀疑您需要查看这两个查询的查询计划。您可以在查询分析器中执行此操作 - 它是“执行”按钮右侧的三个按钮。试着看看两个查询的计划有什么不同......

答案 5 :(得分:0)

这些查询是否发送到SQL Server 完全,就像您发布的那样

select top 1 * 
   from "Timestamps"
   where DeviceId = 4
   order by MessageCounter desc

还是NHibernate使用参数化查询? (where deviceid = @deviceid或类似的东西)??

这可以解释一下 - SQL Server获取DeviceId = 4的参数化查询,提出了一个适用于该参数值的执行计划,但是在下次执行时,对于DeviceId = 1,它会发生故障并以某种方式执行第一个查询的计划不再适用于第二个案例。

你能尝试以相反的顺序执行这两个查询吗?首先使用DeviceId = 1,然后使用DeviceId = 4 - 这会给你相同的结果??