【问题标题】:SQL Sever Query Join OptimizationSQL Server 查询连接优化
【发布时间】:2018-12-27 09:32:34
【问题描述】:

我已经在网上寻找答案,但找不到明确的答案。例如,您有 2 个连接子句:

1.

JOIN T2 ON T1.[ID] = T2.[ID]

2.

JOIN T2 ON T1.[ID] = REPLACE(T2.[ID],'A', '')

由于连接子句中的功能,现在第二个性能更差。这究竟是什么原因?

例如,如果这段代码在一个存储过程中,那么优化它的最佳方法是什么?要删除替换功能并将其添加到表级别,以便在任何连接之前完成所有这些操作?

任何建议或更多信息的链接都会很棒。谢谢

【问题讨论】:

  • 第二个JOIN的参数不是sargable。该词不在字典中,但它是您要用于调查此问题的关键词。
  • 当您将函数应用于 WHERE 子句中的连接参数或字段时,服务器不能在该字段上使用任何索引。索引是使用字段的值创建的,而查询想要使用 不同的 值。为了生成这些值,服务器必须扫描整个表
  • 顺便说一句,为什么 ID 会包含需要替换的任何内容?也许是两个领域冒充一个?糟糕的设计 - 键不应该具有可以或需要更改的商业意义
  • 停留在第一个连接上并添加一个条件以排除 t2 'A' 记录。然后使用所有 t2 'a' 行联合选择并直接再次加入 - 请参阅下面我的答案

标签: sql sql-server join optimization


【解决方案1】:

在第二个示例中,您尝试在 T2 中查找记录 - 但值不是 T1.ID 值,而是将函数应用于 T2.ID - REPLACE(T2.[ID],'A', '')

如果您在 T2.ID 上有一个索引 - 充其量它会扫描索引而不是查找它 - 从而导致性能差异。

这是更难解释的地方 - 索引存储为表中 T2.ID 的值的 b+树。索引理解该字段并可以按它进行搜索/排序,但它不理解应用于它的任何逻辑。

它不知道REPLACE('A123','A', '') = 123 - 没有对索引中的值执行函数并检查结果是否相等。

AAA123 也将相等,1A23、12A3、123A 等,实际上匹配的组合数量无穷无尽 - 但它可以确定单个索引条目是否匹配的唯一方法是运行通过函数取值,然后检查相等性。

如果它只能在通过函数运行索引值时弄清楚——它只有在对索引中的每个条目都这样做时才能正确回答查询——例如对每个条目进行索引扫描,将其传递给函数并检查输出。

正如 Jeroen 提到的术语是 SARGable 或 SARGability,Search ARGumentABLE,尽管我个人更喜欢将其解释为 Seek ARGumentABLE,因为这是更接近的匹配到查询计划运算符。

需要注意的是,这个概念与它是连接无关,SQL 中的任何谓词都有这个限制 - 带有 where 谓词的单个表查询可能有同样的问题。

这个问题可以避免吗?它可以,但仅在某些情况下,您可以反转操作。

考虑一个带有 ID 列的表,我可以构造一个谓词,如下所示: WHERE ID * 2 = @paramValue

SQL Server 知道乘以 2 的 ID 条目是否是传入值的唯一方法是处理每个条目,将其加倍并检查。这又是索引扫描场景。

在这种情况下,我们可以重写它: WHERE ID = @paramValue / 2.0

现在 SQL Server 将执行一次数学运算,将传入的值相除,然后它可以以可搜索的方式检查索引。编写的 SQL 的差异在陈述问题方面看起来可能微不足道,但对数据库如何解析谓词有很大的不同。

【讨论】:

    【解决方案2】:

    SQL Server 有四种处理连接的基本方法(与其他数据库一样):

    1. 没有索引的嵌套循环。这就像两个嵌套的 for 循环,通常是最慢的方法。
    2. 索引循环(带有索引的嵌套循环)。这是对一个表的扫描,并在第二个表中进行查找。
    3. 合并联接。这假设这两个表是有序的,并且同时遍历这两个表(这也可以使用索引来完成)。
    4. 哈希连接。这两个表的键是散列的,散列表用于匹配。

    一般来说,第一个是最慢的,第二个(使用索引)是最快的。 (也有例外)。第二个通常是最快的。

    当您在表中的两列之间使用相等比较时,SQL Server 有很多信息可用于决定要使用的最佳连接算法:

    • 它有关于索引的信息。
    • 它有关于该列的统计信息。

    如果没有这些信息,SQL Server 通常会默认使用嵌套循环连接。我发现即使它可以将表达式用于基于合并或哈希的连接,它也会这样做。

    请注意,您可以使用计算列解决此问题:

    alter table t2 add id_no_a as (replace(id, 'A', '')) persisted;
    
    create index idx_t2_id_no_a on t2(id_no_a);
    

    然后短语

    on T1.[ID] = t2.id_no_a
    

    【讨论】:

      【解决方案3】:

      使用联合避免无索引搜索的示例:

      DECLARE @T1 TABLE (ID VARCHAR(16), CODE INT)
      DECLARE @T2 TABLE (ID VARCHAR(16), CODE INT)
      
      INSERT INTO @T1 VALUES ('ASD',1)
      INSERT INTO @T1 VALUES ('DFG',2)
      INSERT INTO @T1 VALUES ('RTY',3)
      INSERT INTO @T1 VALUES ('AZX',4)
      INSERT INTO @T1 VALUES ('GTY',5)
      INSERT INTO @T1 VALUES ('KKO',6)
      
      INSERT INTO @T2 VALUES ('ASD',1)
      INSERT INTO @T2 VALUES ('SD',2)
      INSERT INTO @T2 VALUES ('DFG',3)
      INSERT INTO @T2 VALUES ('RTY',4)
      INSERT INTO @T2 VALUES ('AZX',5)
      INSERT INTO @T2 VALUES ('ZX',6)
      INSERT INTO @T2 VALUES ('GTY',7)
      INSERT INTO @T2 VALUES ('GTYA',8)
      INSERT INTO @T2 VALUES ('KKO',9)
      INSERT INTO @T2 VALUES ('KKOA',10)
      INSERT INTO @T2 VALUES ('AKKOA',11)
      
      
      
      SELECT * FROM @T1 T1 INNER JOIN (SELECT ID FROM @T2 WHERE ID NOT LIKE '%A%')T2 ON T2.ID = T1.ID
      UNION ALL 
      SELECT * FROM @T1 T1 INNER JOIN (SELECT REPLACE(ID,'A','')ID FROM @T2 WHERE ID LIKE '%A%')T2 ON T2.ID = T1.ID
      

      这是您可以在不更改架构的情况下执行的操作。 通过架构更改,您需要在 T2 中创建一个计算索引列并与之连接。这要快得多,并且大部分精力都放在插入/更新上,以维护额外的列及其上的索引。

      【讨论】:

      • T1.ID = 'BNN'T2.ID = 'BANANA' 的建议替换失败。只有当我们知道包含AT2.ID 的唯一值实际上正是A 而不是其他值时,它才是正确的。
      • Jeroen Mostert 对,我把替换乐趣弄错了。我的解决方案仍然可以始终使用索引搜索来完成工作。
      • 不幸的是,没有。即使ID 已被完全索引,LIKE '%A%' 也不是 sargable,因此无论如何您仍然会在第二部分进行全索引扫描。您可以通过将 ID 上的索引添加到表来验证这一点。总的来说,这个查询比优化器自己产生的查询要慢得多——在这种情况下,拆分条件并没有增加太多。这种技术确实有一些应用,以防一个(或两个)半部分确实允许搜索并且优化器不够聪明,无法看到它,但在这种情况下,它只会让它变得更加复杂而无益。
      • 是的,但这里的主要区别是基数估计器无法正确估计在 JOIN T2 ON T1.[ID] = REPLACE(T2.[ID],'A' 的情况下的行数, '')。在我的情况下,估计器可以更准确地估计返回的行,这非常重要。行的估计极大地影响执行计划的选择。我只是不想那么深。但你是对的 - 在处理 LIKE 时,现在有比全索引搜索更好的方法。
      猜你喜欢
      • 2016-02-18
      • 1970-01-01
      • 2017-04-24
      • 1970-01-01
      • 2018-03-24
      • 2013-08-11
      • 1970-01-01
      • 1970-01-01
      • 2012-09-26
      相关资源
      最近更新 更多