SQL Плохая производительность сервера с большим количеством операций и повторяющихся критериев при использовании UPPER () - PullRequest
2 голосов
/ 08 марта 2020

Программное обеспечение генерирует множество таких неоптимальных запросов:

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
    <...>
    ))

Индексы выглядят оптимально, индекс COL1 и COL2, включая все выбранные другие столбцы. Поскольку мы не можем сейчас изменить программное обеспечение, есть ли способ ускорить время выполнения? Добавление другого вида индекса. Я также думал о переписывании запросов или о чем-то подобном, но не смог найти такого в SQL Server.

Ответы [ 2 ]

1 голос
/ 08 марта 2020

Если вы можете внести изменения в запрос, то удалите UPPER - Это можно легко удалить, если вы используете сортировку без учета регистра (безусловно, самый распространенный случай) - в противном случае вам нужно будет добавить лог c, чтобы обеспечить добавление значений в верхнем регистре перед добавлением в запрос. 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 пункт дважды)

enter image description here

Запрос 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 - поскольку все строки в таблице имеют значение 'CONST_VALUE', в этом случае поиск ничего не достигает, и вся работа выполняется с остаточным предикатом.

enter image description here

Запрос 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, а затем свертываются идентичные результаты для выполнения двух необходимых многостолбительных поисков индекса.

enter image description here

Запрос 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 Сервер сдается и просто сканирует

enter image description here

Query 5

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

Это переписывание дает тот же план выполнения, что и Query 2 - с поиском на Col1 и остаточный предикат на Col2, это бесполезно с моими примерами, но было бы более реалистично c случаев.

Запрос 6

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

SQL Сервер обнаруживает противоречие во время компиляции и дает очень простой план

enter image description here

Запрос 7

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

Несмотря на то, что выражения являются детерминированными c и имеют точно такие же входные значения, обнаружение противоречий не происходит

enter image description here

0 голосов
/ 09 марта 2020

Проблема в том, что оптимизатор запросов в этом случае мало что может сделать. Вы попадаете в ловушку, похожую на IN, со многими аргументами -> отступление - это сканирование таблицы.

Создайте табличную переменную со значениями сравнения, 2 полями и первичным ключом, затем вы можете использовать эту переменную таблицы в объединение и индекс первичного ключа предоставляют статистику для работы оптимизатора запросов.

...