SQL 服务器性能不佳,大量 OR 和使用 UPPER() 的重复条件
SQL Server bad performance with a lot of ORs and repeating criteria using UPPER()
一个软件生成了很多这样的非最佳查询:
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 秒。将查询重构为以下语句导致执行时间为 3 毫秒:
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 服务器中找不到这样的东西。
如果您能够对查询进行更改,然后删除 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' 查找和两次 return 这些行将是错误的,即使它在 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'))
)
这个执行计划看起来很有希望,但仔细检查后,搜索仅在单个列 COL1
上 - 因为 table 中的所有行都具有值 '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 服务器放弃并进行扫描
查询 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 服务器在编译时检测到矛盾并给出一个非常简单的方案
查询 7
SELECT *
FROM sys.all_objects
where UPPER('v1') <> UPPER('v1')
尽管表达式是确定性的并且具有完全相同的输入值,但不会发生矛盾检测
问题是查询优化器在这种情况下无能为力。你落入了类似于 IN 的陷阱,有很多参数 -> 回退是 table scan.
设置一个table变量,其中包含比较值、2个字段和主键,然后您可以在连接中使用该table变量,主键上的索引提供统计信息供查询优化器使用。
一个软件生成了很多这样的非最佳查询:
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 秒。将查询重构为以下语句导致执行时间为 3 毫秒:
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 服务器中找不到这样的东西。
如果您能够对查询进行更改,然后删除 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' 查找和两次 return 这些行将是错误的,即使它在 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'))
)
这个执行计划看起来很有希望,但仔细检查后,搜索仅在单个列 COL1
上 - 因为 table 中的所有行都具有值 '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 服务器放弃并进行扫描
查询 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 服务器在编译时检测到矛盾并给出一个非常简单的方案
查询 7
SELECT *
FROM sys.all_objects
where UPPER('v1') <> UPPER('v1')
尽管表达式是确定性的并且具有完全相同的输入值,但不会发生矛盾检测
问题是查询优化器在这种情况下无能为力。你落入了类似于 IN 的陷阱,有很多参数 -> 回退是 table scan.
设置一个table变量,其中包含比较值、2个字段和主键,然后您可以在连接中使用该table变量,主键上的索引提供统计信息供查询优化器使用。