2

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

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

IdDeviceId是唯一的主键 (Guid.Comb),我在和MessageCounter列上都有索引。

我想要做的是找到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(下图)在索引扫描中创建了更多的行:

设备 4(上)和设备 1(下)的执行计划 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 行!

4

6 回答 6

2

您确定统计数据是最新的吗?使用更新统计

UPDATE STATISTICS dbo.Timestamps

你是如何运行查询的?如果通过存储过程,也许您在参数嗅探方面遇到问题?

于 2010-07-05T15:34:46.903 回答
2

尝试在(DeviceId, MessageCounter DESC).

另外,试试这个查询:

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

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

于 2010-07-05T15:37:05.127 回答
1

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

最有用的信息来自以下查询

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

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

在这种情况下 SQL 服务器如何执行查询:

服务器从高数到低数读取 MessageCounter 索引。对于每一行,服务器都会对托管索引进行嵌套查找以比较设备 ID。

对于设备 2-4,这很快结束,因为服务器在 MessageCounter Index 中找到设备 2-4 的一行。对于设备 1,服务器需要超过 600 万次查找操作,才能找到设备 1 的第一行。

读取 deviceid 索引并查找托管索引会更快。这应该在 323k 寻道后停止。甚至很糟糕。

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

于 2010-07-06T12:43:02.793 回答
1

我认为这一定会发生,因为如果您通过MessageCounter降序对记录进行排序,则它必须经过 6,500,000 条记录才能找到第一个记录,DeviceId=4而对于另一个记录DeviceId,则分布要好得多

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

一个复合索引DeviceId, MessageCounter可以解决这个问题。但是设备是否具有DeviceId=4不再记录新数据的旧设备?如果是这样,您可能能够将 DeviceId=4 记录提取到他们自己的表中并使用分区视图,这样该设备上的查询就不会扫描大量不相关的记录。

以下更正

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

于 2010-07-06T09:16:07.870 回答
0

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

关于统计的建议很好,但我怀疑您需要查看这两个查询的查询计划。您可以在查询分析器中执行此操作 - 执行按钮右侧大约三个按钮。尝试查看两个查询的计划之间有什么不同...

于 2010-07-05T15:46:15.963 回答
0

Are those queries sent to SQL Server exactly like you posted them

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

or did NHibernate use parametrized queries? (where deviceid = @deviceid or something like that)??

That might explain it - SQL Server gets the parametrized query for DeviceId = 4, comes up with an execution plan that works for that parameter value, but then on the next execution, for DeviceId = 1, it stumbles and somehow the execution plan from the first query isn't optimal for that second case anymore.

Can you try to execute those two queries in the reversed order?? First with DeviceId=1 and then with DeviceId=4 - does that give you the same results??

于 2010-07-05T16:49:37.007 回答