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() 的重复条件的主要内容,如果未能解决你的问题,请参考以下文章