【问题标题】:SQL query executing slowly (for some parameter values)SQL 查询执行缓慢(对于某些参数值)
【发布时间】:2010-07-05 15:29:41
【问题描述】:

我有一个包含多个表的 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 行!

【问题讨论】:

  • 也许刷新DeviceID上的索引?
  • 能否在两个查询中添加执行计划?比我们知道的,它改变了什么。然后我们可以猜到,为什么会这样...... :-)
  • 好的,看起来统计信息不同步 - 我假设数据库设置为自动更新统计信息。听从 OMG Ponies 的建议

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


【解决方案1】:

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

另外,试试这个查询:

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

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

【讨论】:

  • 一开始创建索引似乎并没有让它更快,但后来发现我需要更改索引中列的顺序(我什至不知道顺序很重要,sql noob,我还能说什么)。将 DeviceId 设为复合索引中的第一列解决了这个问题。
【解决方案2】:

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

UPDATE STATISTICS dbo.Timestamps

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

【讨论】:

  • 谢谢,但这也无济于事。我的应用程序正在使用 LINQ to NHibernate,它会为两种设备生成显示的查询。现在我只是使用 SQL Server Management Studio 手动输入这两个查询。
【解决方案3】:

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

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

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 指出的那样)。

【讨论】:

  • 您好,非常感谢您的评论。你是对的 - 随着我的数据库的增长,与其他设备的 MessageCounter 相比相对较低的特定设备的最大 MessageCounter 检索速度越来越慢,即使使用组合的 DeviceId+MessageCounter 索引也是如此。你认为我还能做些什么吗?我最后的手段是将每个设备分成不同的表,但这是一些非常糟糕的非规范化。
  • 好吧,没关系,我很傻。我对复合索引中的列重新排序,将 DeviceId 列放在顶部。我的查询现在立即返回。
【解决方案4】:

我认为这一定会发生,因为如果您按 MessageCounter 降序排列记录,则它必须经过 6,500,000 条记录才能找到带有 DeviceId=4 的第一个记录,而另一个 DeviceId 则存在是一个更好的传播

我认为DeviceId=4 谓词直到执行计划中的过滤器运算符才会发挥作用。

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

以下更正

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      发送到 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 - 这会给您同样的结果吗??

      【讨论】:

      • 起初我开始检查 NHibernate 日志,但最后我只是使用 Management Studio 手动运行查询。我尝试以几种组合方式反向运行它们(实际上,对于 1 个查询以外的任何设备都会立即执行),但没有任何变化。
      猜你喜欢
      • 1970-01-01
      • 2019-06-19
      • 2015-01-06
      • 1970-01-01
      • 1970-01-01
      • 2011-09-10
      • 2020-04-23
      • 2018-07-19
      • 1970-01-01
      相关资源
      最近更新 更多