SQL Server 性能不佳,有很多 OR 和使用 UPPER() 的重复条件

Posted

技术标签:

【中文标题】SQL Server 性能不佳,有很多 OR 和使用 UPPER() 的重复条件【英文标题】:SQL Server bad performance with a lot of ORs and repeating criteria using UPPER() 【发布时间】:2020-03-07 23:36:56 【问题描述】:

软件会生成很多这样的非最佳查询:

SELECT
    <List of Columns>
FROM <Table>
WHERE(
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v1')) OR
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v2')) OR
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v4')) OR
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v6')) OR
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v8')) OR
      <...>
     )

执行计划:https://www.brentozar.com/pastetheplan/?id=rJGtaBzSU

执行此查询会导致执行索引查找大约需要 1 秒。将查询重构为以下语句导致执行时间为 3ms:

SELECT
    <List of Columns>
FROM <Table>
WHERE([COL1] = UPPER('CONST_VALUE') AND (
    [COL2] = UPPER('v1') OR
    [COL2] = UPPER('v2') OR
    [COL2] = UPPER('v4') OR
    [COL2] = UPPER('v6') OR
    [COL2] = UPPER('v8') OR
    <...>
    ))

索引看起来最佳 afaik,COL1 和 COL2 上的索引包括所有选定的其他列。由于我们暂时无法更改软件,有没有办法加快执行时间?添加不同类型的索引。我也在考虑查询重写之类的东西,但是在SQL Server中找不到这样的东西。

【问题讨论】:

您可以使用in 运算符。像where col1='const_value' and col2 in ('v1','v2','v3') 这样的东西。它可读性更好,但在内部编译到您的第二个示例中。 如果您无法更改查询,您的选择将受到限制。您是否尝试过关于 COL 的索引!和 COL2 与 作为包含的列? 您检查过实际执行计划吗?请参阅paste the plan,了解在您的问题中包含执行计划的方法。指数统计数据是最新的吗?请参阅update statistics 了解更多信息。 UPPER 的使用导致了问题,否则它会很好地折叠谓词。由于某种原因,它不会不断折叠该函数,并且可能只是将其视为对 UPPER('000049') 的不同引用可能不会评估为同一事物 问题在于,如果没有不断的折叠,它就不能盲目地重复搜索。 UPPER('a') 和 UPPER('A') 都评估为相同的值,但不应该返回的匹配行加倍。所以它需要一个重复删除的步骤。您的手动重写使用合并间隔来完成。当两列可能不同时,不确定这是否不可用 【参考方案1】:

如果您能够对查询进行更改,则删除 UPPER - 如果您使用不区分大小写的排序规则(迄今为止最常见的情况),则可以直接删除 - 否则您需要添加逻辑确保在添加到查询之前将值大写。 UPPER 不是固定折叠的,它可以提供比简单字符串文字更糟糕的计划,如下面的各种示例所示。

示例数据

CREATE TABLE [Table]
(
[COL1] VARCHAR(20),
[COL2] VARCHAR(10),
PRIMARY KEY ([COL1],[COL2])
)

INSERT INTO [Table]
SELECT TOP 100 'CONST_VALUE',  CONCAT('v', ROW_NUMBER() OVER (ORDER BY @@SPID))
FROM sys.all_columns

查询 1

SELECT *
FROM [Table]
WHERE(
      ([COL1] = 'CONST_VALUE' AND [COL2] = 'V1') OR
      ([COL1] = 'CONST_VALUE' AND [COL2] = 'V1') OR
      ([COL1] = 'CONST_VALUE' AND [COL2] = 'V4') 
     )

这个执行计划有一个索引查找操作符。查看计划的属性显示搜索实际上包含两个不同的多列搜索谓词(不是三个搜索。执行两次“V1”搜索并返回两次这些行将是错误的,即使它出现在WHERE子句两次)

查询 2

SELECT *
FROM [Table] 
WHERE(
      ([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v1')) OR
      ([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('V1')) OR
      ([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v2')) 
     )

这个执行计划看起来很有希望,但仔细观察,seek 只在单个列上 COL1 - 因为表中的所有行都有值 'CONST_VALUE' 在这种情况下,seek 什么也没做,所有的工作都完成了一个残差谓词。

查询 3

SELECT *
FROM [Table] WITH (FORCESEEK)
WHERE(
      ([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v1')) OR
      ([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('V1')) OR
      ([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v2')) 
     )

这与之前的相同,但添加了FORCESEEK 提示。由于某种原因,UPPER 的结果在编译时不是恒定折叠的,因此它在计划中添加了额外的运算符来评估 UPPER,然后折叠相同的结果以执行两个所需的多列索引查找。

查询 4

SELECT *
FROM [Table] 
WHERE(
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v1')) OR
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('V1')) OR
      ([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v2')) 
     )

现在 SQL Server 放弃了,只进行了一次扫描

查询 5

SELECT *
FROM [Table] 
WHERE [COL1] = UPPER('CONST_VALUE') AND  
(
      [COL2] = UPPER('v1') OR
      [COL2] = UPPER('V1') OR
      [COL2] = UPPER('v2') 
)

此重写提供了与查询 2 相同的执行计划 - 在 Col1 上查找和在 Col2 上的残差谓词,这对我的示例数据没有用,但在更实际的情况下会有用。

查询 6

SELECT *
FROM sys.all_objects
where 'v1' <> 'v1'

SQL Server 在编译时检测到矛盾,并给出一个非常简单的方案

查询 7

SELECT *
FROM sys.all_objects
where UPPER('v1') <> UPPER('v1')

尽管表达式是确定性的并且具有完全相同的输入值,但不会发生矛盾检测

【讨论】:

【参考方案2】:

问题是在这种情况下查询优化器无法做很多事情。你陷入类似于 IN 的陷阱,带有许多参数 -> 后备是表扫描。

建立一个表变量,其中包含比较值、2 个字段和主键,然后您可以在连接中使用该表变量,主键上的索引为查询优化器提供统计信息。

【讨论】:

以上是关于SQL Server 性能不佳,有很多 OR 和使用 UPPER() 的重复条件的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 查询性能不佳

索引视图以提高 SQL Server 上多个连接的性能

性能不佳的 SQL 查询

如何纠正 T-SQL 视图中的性能不佳

创作赢红包SQL Server之索引设计

SQL Server:将多行合并为 1 行