【问题标题】:Why is doing a top(1) on an indexed column in SQL Server slow?为什么在 SQL Server 的索引列上执行 top(1) 很慢?
【发布时间】:2011-01-28 01:36:49
【问题描述】:

我对以下内容感到困惑。我有一个包含大约 1000 万行的数据库,并且(在其他索引中)一列(campaignid_int)是一个索引。

现在我有 700k 行,其中的活动 ID 确实是 3835

对于所有这些行,connectionid 都是相同的。

我只是想找出这个connectionid。

 use messaging_db;
 SELECT     TOP (1) connectionid
 FROM         outgoing_messages WITH (NOLOCK)
 WHERE     (campaignid_int = 3835)

现在执行此查询大约需要 30 秒!

我(以我的小数据库知识)期望它会占用任何行,并返回我那个 connectionid

如果我为只有 1 个条目的广告系列测试相同的查询,它会非常快。所以索引有效。

我将如何解决这个问题,为什么这不起作用?

编辑:

estimated execution plan:

select (0%) - top (0%) - clustered index scan (100%)

【问题讨论】:

  • campaignid_int 列是否被索引?
  • 你能发布你当前的执行计划吗?
  • 是的,campaignid 已编入索引
  • 我编辑了问题以使其更清晰
  • 如何更新统计数据?

标签: sql-server tsql query-optimization performance


【解决方案1】:

我最近遇到了同样的问题,而且解决起来非常简单(至少在某些情况下)。

如果您在任何或某些已编入索引的列上添加ORDER BY-clause,则应该解决它。这至少为我解决了。

【讨论】:

  • 奇怪!就我而言,没有 ORDER BY 需要 50 个 cpu 和 2200 次读取。使用 ORDER BY 需要 0 cpu 和 5 次读取。我只想要一个具有一个复合主键的表中的一行。
【解决方案2】:

但是因为我指定了'top(1)' 意思是:给我任何一行。为什么会 首先爬过 700k 行 退回一个? – reinier 30 分钟前

抱歉,目前还不能发表评论,但这里的答案是,当 SQL Server 听到“Top 1”时,它不会理解“把你找到的第一个给我带来”的人类等价物。而不是预期的“给我任何行”,SQL Server 会获取所有找到的行中的第一行。 只有在首先获取所有行之后,它才知道,然后丢弃其余行。非常彻底,但在你的情况下不是很快。

其他人所说的主要问题是您的统计数据和索引的选择性。如果您的表中有另一个唯一字段(如标识列),则首先尝试在campaignid_int 上使用组合索引,然后再尝试唯一列。由于您只查询campaignid_int,它必须是键的第一部分。 听起来值得一试,因为该索引应该具有更高的选择性,因此优化器可以比进行索引爬取更好地使用它。

【讨论】:

  • TOP 在 SELECT 语句的 FROM、WHERE、GROUP BY 和 ORDER BY 阶段之后应用。因此,您会在数据库引擎处理 TOP 之前承担这些操作的成本。
【解决方案3】:

由于统计信息,您应该明确要求优化器使用您创建的索引而不是集群索引。

SELECT  TOP (1) connectionid
FROM    outgoing_messages WITH (NOLOCK, index(idx_connectionid))
WHERE  (campaignid_int = 3835)

希望能解决问题。

问候, 恩里克

【讨论】:

    【解决方案4】:

    您的查询没有按预期工作,因为 Sql Server 保留有关您的索引的统计信息,并且在这种特殊情况下知道有很多重复行的标识符为 3835,因此它认为只做一个完整的索引(或表)扫描。当您测试仅解析为一行的 ID 时,它会按预期使用索引,即执行索引查找(执行计划应验证此猜测)。

    可能的解决方案?使索引组合,如果你有任何东西可以组合它,也就是说,例如用发送消息的日期编写它(如果我正确理解你的情况),然后从列表中选择按日期排序的指定 ID 的前 1 个条目。虽然我不确定这是否会更好(例如,复合索引占用更多空间) - 只是一个猜测。

    编辑:我刚刚尝试了通过添加日期列来合成索引的建议。如果您这样做并在查询中指定order by date,则会按预期执行索引查找。

    【讨论】:

    • 由 guid + 日期组成的复合索引就像一个魅力。
    【解决方案5】:

    这不能回答您的问题,但请尝试使用:

    SET ROWCOUNT 1
    SELECT     connectionid
     FROM         outgoing_messages WITH (NOLOCK)
     WHERE     (campaignid_int = 3835)
    

    我看到 top(x) 在某些情况下也表现得很糟糕。我确定它正在执行全表扫描。也许您在该特定列上的索引需要重建?不过,上述方法值得一试。

    【讨论】:

    • 无赖。我不记得我们是如何解决查询问题的——如果我们调整了索引,或者只是在代码中而不是在数据库级别获取最高结果。
    【解决方案6】:

    您没有在查询中指定ORDER BY 子句,因此没有指示优化器应该选择前1 个的排序顺序。 SQL Server 不仅会随机取行,它还会按某事对行进行排序并取前 1 个,并且它可能会选择按次优排序。我建议您添加一个ORDER BY x 子句,其中x 作为该表上的聚集键可能是最快的。

    这可能无法解决您的问题 - 事实上,我不确定我是否希望从您提供的统计数据中得到解决 - 但 (a) 它不会造成伤害,并且 (b) 您将能够将其排除为促成因素。

    【讨论】:

    • 我实际上尝试了按 recid(主键)排序,它同样慢。 =^(
    • 我对MS-SQL-server一无所知,但是“ORDER BY campaignid_int”会满足优化器的排序要求吗?
    • @adrian:但是要订购它,数据库不需要扫描所有条目以知道哪个条目位于顶部吗?我已经知道所有值都是相同的,所以它可以在找到的任何行处停止。
    • @Toad:不,它不需要扫描所有行。由于 WHERE 子句规定campaignid_int = 3835,因此优化器可以知道将遵守 ORDER BY 而无需扫描行。
    【解决方案7】:

    索引可能无用有两个原因:

    • 1000 万中的 70 万可能选择性不够
    • 和/或
    • 需要包含连接 ID,因此整个查询只能使用一个索引

    否则,优化器决定它也可以使用 PK/聚集索引来过滤campaignid_int 并获取connectionid,以避免从当前索引中查找700k 行的书签。

    所以,我建议这个...

    CREATE NONCLUSTERED INDEX IX_Foo ON MyTable (campaignid_int) INCLUDE (connectionid)
    

    【讨论】:

    • 但由于我指定了 'top(1)' 这意味着:给我任何一行。为什么它会首先爬过 700k 行来返回一个?
    【解决方案8】:

    如果campaignid_int 列未编入索引,请为其添加索引。那应该加快查询速度。现在我假设您需要在返回 top(1) 行之前进行全表扫描以找到 campaignid_int = 3835 的匹配项(在返回结果之前进行过滤)。

    编辑: 索引已经到位,但由于 SQL Server 执行聚集索引扫描,优化器忽略了索引。这可能是由于(许多)重复行具有相同的campaignid_int 值。您应该考虑使用不同的索引或查询不同的列以获得所需的connectionid

    【讨论】:

    • 我稍微改变了问题。 Campaignid_int 已编入索引
    • 有没有办法欺骗它只返回任何行?我猜想即使这个索引有很多行,它仍然可以索引到这些行中的任何一个。我只是不希望它遍历整个数据库
    • 对不起,没有。索引的主要目的是快速查找唯一值(即键),但是当您有许多重复值时,索引查找(即查找)将不起作用,因此优化器将发出索引扫描。您将需要以不同的方式建立索引或更改您的查询。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-07
    • 1970-01-01
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多