【问题标题】:Optimize self join on millions of rows优化数百万行的自联接
【发布时间】:2012-12-14 17:21:00
【问题描述】:

我有一个表,它是我的 SQL Server 2012 数据库 (annonsid, annonsid2) 中对象的链接表。该表用于创建三角形甚至矩形链,以查看谁可以与谁交换。

这是我在表 Matching_IDs 上使用的查询,其中有 150 万行,使用此查询产生 1400 万条可能的链:

SELECT COUNT(*)
FROM Matching_IDs AS m
  INNER JOIN Matching_IDs AS m2
     ON m.annonsid2 = m2.annonsid
  INNER JOIN Matching_IDs AS m3
     ON m2.annonsid2 = m3.annonsid
       AND m.annonsid = m3.annonsid2

我必须将性能提高到可能需要 1 秒或更短的时间,有没有更快的方法来做到这一点?在我的电脑上查询大约需要 1 分钟。我通常使用WHERE m.annonsid=x,但它需要的时间相同,因为它必须通过所有可能的组合。

更新:最新的查询计划

|--Compute Scalar(DEFINE:([Expr1006]=CONVERT_IMPLICIT(int,[globalagg1011],0)))
   |--Stream Aggregate(DEFINE:([globalagg1011]=SUM([partialagg1010])))
        |--Parallelism(Gather Streams)
             |--Stream Aggregate(DEFINE:([partialagg1010]=Count(*)))
                  |--Hash Match(Inner Join, HASH:([m2].[annonsid2], [m2].[annonsid])=([m3].[annonsid], [m].[annonsid2]), RESIDUAL:([MyDatabase].[dbo].[Matching_IDs].[annonsid2] as [m2].[annonsid2]=[MyDatabase].[dbo].[Matching_IDs].[annonsid] as [m3].[annonsid] AND [MyDatabase].[dbo].[Matching_IDs].[annonsid2] as [m].[annonsid2]=[MyDatabase].[dbo].[Matching_IDs].[annonsid] as [m2].[annonsid]))
                       |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m2].[annonsid2], [m2].[annonsid]))
                       |    |--Index Scan(OBJECT:([MyDatabase].[dbo].[Matching_IDs].[NonClusteredIndex-20121229-133207] AS [m2]))
                       |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m3].[annonsid], [m].[annonsid2]))
                            |--Merge Join(Inner Join, MANY-TO-MANY MERGE:([m].[annonsid])=([m3].[annonsid2]), RESIDUAL:([MyDatabase].[dbo].[Matching_IDs].[annonsid] as [m].[annonsid]=[MyDatabase].[dbo].[Matching_IDs].[annonsid2] as [m3].[annonsid2]))
                                 |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m].[annonsid]), ORDER BY:([m].[annonsid] ASC))
                                 |    |--Index Scan(OBJECT:([MyDatabase].[dbo].[Matching_IDs].[NonClusteredIndex-20121229-133152] AS [m]), ORDERED FORWARD)
                                 |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([m3].[annonsid2]), ORDER BY:([m3].[annonsid2] ASC))
                                      |--Index Scan(OBJECT:([MyDatabase].[dbo].[Matching_IDs].[NonClusteredIndex-20121229-133207] AS [m3]), ORDERED FORWARD)

【问题讨论】:

  • 你在表上定义了哪些索引?索引(annonsid2,annonsid) 可能是一个不错的选择。
  • 我有两列的聚集索引,每列都有一个非聚集索引 (2)
  • 查询计划说时间在哪里?
  • 嗯,从我按下运行到它开始显示结果大约需要 2 秒,但这并不意味着查询有那么快,对吧?因为当我只是做一个 Count(m .annonsid) 而不是获取所有记录,大约需要 1 分钟。
  • 您能否将查询计划附加到问题中?

标签: sql sql-server optimization sql-server-2012


【解决方案1】:

一些想法:

尝试两个索引 (annonsid,annonsid2) 和 (annonsid2,annonsid)

您是否尝试过列存储索引?它使表只读,但它可能会提高性能。

此外,查询的一些变体可能会有所帮助。例子:

SELECT COUNT(*)
FROM Matching_IDs AS m
  INNER JOIN Matching_IDs AS m2
     ON m.annonsid2 = m2.annonsid
  INNER JOIN Matching_IDs AS m3
     ON m2.annonsid2 = m3.annonsid
where m.annonsid = m3.annonsid2

或

SELECT COUNT(*)
FROM Matching_IDs AS m, Matching_IDs AS m2, Matching_IDs AS m3
where m2.annonsid2 = m3.annonsid
  and m.annonsid2 = m2.annonsid
  and m.annonsid = m3.annonsid2

您检查 CPU/IO-Load 了吗?如果 IO-Load 很高,那么服务器不是在处理数字而是交换 => 更多 RAM 可以解决问题。

这个查询有多快?

SELECT COUNT(*)
FROM Matching_IDs AS m
  INNER JOIN Matching_IDs AS m2
     ON m.annonsid2 = m2.annonsid

如果这非常快,但添加下一个连接会减慢速度,那么您可能需要更多 RAM。

【讨论】:

  • #1 和 #2 与我的正常查询类似(22 秒),我设法使用了一个从 1 分钟下降到 22 秒的不同表。第三次查询非常快,2 秒。我有 8GB 内存。
  • 实际上我真正想要的是删除 1 对 1 id 表,但我不知道如何以简单的方式知道哪个广告与哪个匹配。我想过有很多 bigint 并在广告之间使用按位和使用。这将搜索范围缩小到 15.000 个广告(行),而不是 150 万个。但是,最终结果无论如何都会是 1500 万,因此可能不值得。
  • 1对1 id表是什么意思?关于性能:我刚刚在笔记本电脑上进行了测试。数 6 mio 行需要一段时间(20 秒),所以我怀疑你能不能更快。
  • 这是一张表,2个id从左到右,id1要id2。
  • 添加and m.AnnonId != m.AnnonId2怎么样?对所有三个表(m、m1、m2)执行此操作。这可能会有所帮助,但正如你所说,它们无论如何都会得到解决。
【解决方案2】:

看起来你已经很好地索引了这个。您可以尝试通过添加正确的多列索引将哈希转换为合并连接,但它不会为您提供所需的 60 倍加速。

我认为这个索引应该在 annonsid, annonsid2 上,尽管我可能在这里犯了一个错误。

实现所有这些会很好,但索引视图不支持自连接。您可以尝试将此查询(未聚合)具体化到一个新表中。每当您对基表执行 DML 时,还要更新第二个表(使用应用程序逻辑或触发器)。这将使您能够以极快的速度进行查询。

【讨论】:

    【解决方案3】:

    您应该使这个查询更加分开。我认为首先您应该创建一个表,您可以在其中存储主键 + annonsid,annonsid2 -如果 annosid 本身不是主键。

    DECLARE @AnnonsIds TABLE
    (
    primaryKey int,
    -- if you need later more info from the original rows like captions  
    -- AND it is not (just) the annonsid
    annonsid int,
    annonsid2 int
    )
    

    如果你声明一个表,并且你在这个列上有索引,那么通过WHERE annonsid = @annonsid OR annonsid2 = @annosid获取指定的行是相当快的

    在第一步之后,您可以使用一个更小(我猜)且“薄”的桌子。然后你只需要在这里使用连接或在其上创建一个临时表和一个 CTE。

    我认为它应该更快,这取决于WHERE 中条件的选择性,如果 110 万行适合它,那么它没有意义,但如果只有几百或 tousend,那么你应该试一试!

    【讨论】:

      【解决方案4】:

      1 - 将选择 Count(*) 更改为 Count(1) 或 Count(id)

      2 - 在存储过程的第一个或查询的第一个写 set Nocount on

      3 - 在annonsid、annonsid2 上使用索引

      4 - 将索引放在表中的主键之后

      【讨论】:

      • 1-count(*)和count(1)`等没有区别,我测试过了。
      • 正确。在计算什么方面存在差异。但这不适用于infinity1975的问题,据我所知。但是 +1 指出这一点。
      【解决方案5】:

      您可以通过添加带有AnnonsId、RelatedAnnonId 和Distance 的表RelatedIds 来非规范化数据。对于AnnonsId 的每个值,该表将包含每个RelatedAnnonId 的行以及需要遍历才能到达它的关系数,即Distance。现有MatchingIds 表上的触发器将使用Distance 的某些已配置最大值来维护新表,例如3 处理矩形股。索引表 (AnnonsId, Distance)。

      编辑: (Distance, AnnonsId) 上的索引将允许您快速找到具有足够相关条目以形成特定形状的行。如果您希望能够排除基于三角形但非矩形关系的行,为MaxDistance 添加一列可能会很有用。

      新查询将inner join RelatedIds as RI on RI.AnnonsId = m.AnnonsId and RI.Distance <= @MaxDistance 与所需的“形状”指示@MaxDistance 的值。

      它应该在select 上提供更好的性能。缺点是另一个表在更改MatchingIds 表时具有大量行和触发器开销。

      示例:Matching_IDs 中有两个条目:(1,2) 和 (2,3)。 新表将包含 3 个条目:
      1-> 2:距离 = 1
      1-> 3:距离 = 2(从 1 到 3 需要一个中间“节点”)
      2-> 3: 距离 = 1

      向匹配的 id (3,1) 添加一个条目将导致另一个条目:
      1-> 1: 距离 = 3

      瞧:你找到了一个三角形(距离=3)。

      现在,要查找所有三角形,只需执行以下操作:

      select * 
        from RelatedIds 
       where AnnonsId=RelatedAnnonId 
         and Distance=3
      

      【讨论】:

      • 这对我来说太复杂了:D,我已经有一个表相关ID,这是表“Matching_IDs”谁有(annonsid,annonsid2),我不明白的是“距离”以及如何获得它?以及触发器会做什么。
      • 我认为问题仍然存在?问题是性能,而不是创造结果。此触发器与使用我的查询相同,方法是提供一个 id 使其仅在该 id 上运行,然后将结果保存在第二个表中。我错了吗?
      • @infinity1975 - 不同之处在于,每次更改基表时,触发器都会将繁重的工作分成相对较少的工作量。一个缺点是工作总是被执行,即使你从不查询“形状”。好处是“形状”查询应该更快。
      • 是的,我有一个解决方案,除了距离参数。我有一张用于直接交换的表和一张用于三角形交换的表。
      • 我有一张用于 directswap 的表和一张用于 triangleswaps 的表。但我想找到一种解决方案,让第一次选择更快。这仍然是一项耗时的任务。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-05
      • 1970-01-01
      • 1970-01-01
      • 2018-12-01
      • 1970-01-01
      • 2013-01-12
      • 2021-09-24
      相关资源
      最近更新 更多