Оптимизация SQL - изменения плана выполнения в зависимости от значения ограничения. Почему? - PullRequest
6 голосов
/ 05 марта 2010

У меня есть таблица ItemValue , заполненная данными на сервере SQL 2005, работающем в режиме совместимости 2000, который выглядит примерно так (это таблица определяемых пользователем значений):

ID    ItemCode     FieldID   Value
--    ----------   -------   ------
 1    abc123             1   D
 2    abc123             2   287.23
 4    xyz789             1   A
 5    xyz789             2   3782.23
 6    xyz789             3   23
 7    mno456             1   W
 9    mno456             3   45
                                 ... and so on.

FieldID взято из таблицы ItemField :

ID   FieldNumber   DataFormatID   Description   ...
--   -----------   ------------   -----------
 1             1              1   Weight class
 2             2              4   Cost
 3             3              3   Another made up description
 .             .              x   xxx
 .             .              x   xxx
 .             .              x   xxx
 x             91  (we have 91 user-defined fields)

Поскольку я не могу PIVOT в режиме 2000, мы застряли в создании уродливого запроса с использованием CASEи GROUP BY, чтобы данные выглядели так, как это должно быть для некоторых унаследованных приложений, а именно:

ItemNumber   Field1   Field2    Field3 .... Field51
----------   ------   -------   ------
    abc123   D        287.23    NULL
    xyz789   A        3782.23   23
    mno456   W        NULL      45

Вы можете видеть, что нам нужна только эта таблица для отображения значений до 51-го UDF.Вот запрос:

SELECT
    iv.ItemNumber,
    ,MAX(CASE WHEN f.FieldNumber = 1 THEN iv.[Value] ELSE NULL END) [Field1]
    ,MAX(CASE WHEN f.FieldNumber = 2 THEN iv.[Value] ELSE NULL END) [Field2]
    ,MAX(CASE WHEN f.FieldNumber = 3 THEN iv.[Value] ELSE NULL END) [Field3]
        ...
    ,MAX(CASE WHEN f.FieldNumber = 51 THEN iv.[Value] ELSE NULL END) [Field51]
FROM ItemField f
LEFT JOIN ItemValue iv ON f.ID = iv.FieldID
WHERE f.FieldNumber <= 51
GROUP BY iv.ItemNumber

Когда ограничение FieldNumber равно <= 51, план выполнения выглядит примерно так: </p>

SELECT <== Computer Scalar <== Stream Aggregate <== Sort (Cost: 70%) <== Hash Match <== (Clustered Index Seek && Table Scan)

и это быстро!Я могу вернуть более 100 000 записей за секунду, что соответствует нашим потребностям.

Однако, если у нас было больше UDF, и я изменил бы ограничение на что-то выше 66 (да, я проверялодин за другим) или, если я удаляю его полностью, я теряю сортировку в плане выполнения, и она заменяется целой кучей блоков параллелизма, которые собирают, перераспределяют и распределяют потоки, и все это происходит медленно (30 секунддаже для 1 записи).

FieldNumber имеет кластеризованный уникальный индекс и является частью составного первичного ключа со столбцом ID (некластеризованный индекс)в таблице ItemField .Столбцы ItemValue ID и ItemNumber таблицы образуют PK, и для столбца ItemNumber существует дополнительный некластеризованный индекс.

В чем причина этого?Почему изменение моего простого целочисленного ограничения меняет весь план выполнения?

И если вы готовы к этому ... что бы вы сделали по-другому? Планируется обновление SQLв течение пары месяцев, но мне нужно решить эту проблему до этого.

Ответы [ 3 ]

4 голосов
/ 05 марта 2010

SQL Server достаточно умен, чтобы учитывать ограничения CHECK при оптимизации запросов.

Ваш f.FieldNumber <= 51 оптимизирован, и оптимизатор видит, что все две таблицы должны быть объединены (что лучше всего сделать с HASH JOIN).

Если у вас нет ограничения, движок должен проверить условие и, скорее всего, использует обход индекса для этого. Это может быть медленнее.

Не могли бы вы опубликовать все планы по запросам? Просто запустите SET SHOWPLAN_TEXT ON, а затем запросы.

Обновление:

В чем причина этого? Почему изменение моего простого целочисленного ограничения меняет весь план выполнения?

Если под ограничением вы подразумеваете условие WHERE, возможно, это другое дело.

Операции над множествами (это то, что делает SQL) не имеют единственного наиболее эффективного алгоритма: эффективность каждого алгоритма сильно зависит от распределения данных в наборах.

Скажем, для взятия подмножества (это то, что делает предложение WHERE), вы можете либо найти диапазон записей в индексе и использовать указатели записей индекса, чтобы найти строки данных в таблице, либо просто отсканировать все записи и отфильтруйте их, используя условие WHERE.

Эффективность первой операции m × const, второй - n, где m - количество записей, удовлетворяющих условию, n - общее количество записей в таблице и * 1036. *.

Это означает, что при больших значениях m полное сканирование более эффективно.

SQL Server знает об этом и меняет планы выполнения в соответствии с константами, которые влияют на распределение данных в заданных операциях.

Для этого SQL Server ведет статистику : агрегированные гистограммы распределения данных в каждом проиндексированном столбце и использует их для построения планов запросов.

Таким образом, изменение целого числа в условии WHERE фактически влияет на размер и распределение данных базовых наборов и заставляет SQL Server пересмотреть алгоритмы, наилучшим образом подходящие для работы с наборами такого размера и макета.

0 голосов
/ 05 марта 2010

В 66 вы достигаете некоторого внутреннего порога оценки стоимости, который решает, что лучше использовать один план против другого. Что это за порог и почему это происходит, не очень важно. Обратите внимание, что ваш запрос отличается для каждого значения FieldNumber, поскольку вы не только изменяете WHERE: вы также изменяете проецируемые поля псевдо-'pivot'.

Теперь я не знаю всех деталей вашей таблицы и ваших запросов и вставки / обновления / удаления / шаблона, но для конкретного запроса, который вы разместили правильную структуру кластеризованного индекса для таблицы ItemValue, это:

CREATE CLUSTERED INDEX  [cdxItemValue] ON ItemValue (FieldID, ItemNumber);

Эта структура избавляет от необходимости промежуточной сортировки результатов для этого запроса "pivot".

0 голосов
/ 05 марта 2010

заменяется целой кучей блоков параллелизма

Попробуйте это:

SELECT
    iv.ItemNumber,
    ,MAX(CASE WHEN f.FieldNumber = 1 THEN iv.[Value] ELSE NULL END) [Field1]
    ,MAX(CASE WHEN f.FieldNumber = 2 THEN iv.[Value] ELSE NULL END) [Field2]
    ,MAX(CASE WHEN f.FieldNumber = 3 THEN iv.[Value] ELSE NULL END) [Field3]
        ...
    ,MAX(CASE WHEN f.FieldNumber = 51 THEN iv.[Value] ELSE NULL END) [Field51]
FROM ItemField f
LEFT JOIN ItemValue iv ON f.ID = iv.FieldID
WHERE f.FieldNumber <= 51
GROUP BY iv.ItemNumber
OPTION (Maxdop 1)

При использовании Option (Maxdop 1) это должно предотвратить пареллизм в плане выполнения.

...