Параметризованный запрос создает неблагоприятный план выполнения. Оптимизировать для параметров, которые не равны NULL - PullRequest
0 голосов
/ 31 октября 2018

Как предупреждение, я работаю с SQL-запросом, сгенерированным из структуры сущностей, хотя структура сущностей не имеет отношения к этому вопросу.

Некоторый контекст: Я пытаюсь извлечь определенные записи из пакета из 4000 объектов C # и выполнить обновление или вставку в них. У меня нет первичного ключа записей, поскольку объекты поступают из API, поэтому мне нужно использовать уникальный набор столбцов для получения правильной записи.

(Упрощенные) запросы и планы их выполнения:

Параметризованный запрос (параметры объявлены со значением, установленным в 1 для демонстрационных целей)

SELECT [x].[Id]
      FROM [Gradebook].[AssignmentScore] AS [x]
      WHERE
         ( ( ((([x].[CourseSectionAssignment_Id] = @__CSA_1) AND ([x].[Student_Id] = @__S_ID_1)) AND ([x].[AssessmentGBID] = @__A_GBID_1))
          OR ((([x].[CourseSectionAssignment_Id] = @__CSA_2) AND ([x].[Student_Id] = @__S_ID_2)) AND ([x].[AssessmentGBID] = @__A_GBID_2)) )
          OR ((([x].[CourseSectionAssignment_Id] = @__CSA_3) AND ([x].[Student_Id] = @__S_ID_3)) AND ([x].[AssessmentGBID] = @__A_GBID_3)) )

И это (неблагоприятный) план выполнения:

Parameterized query execution plan

Запрос литеральных значений:

SELECT [x].[Id]
      FROM [Gradebook].[AssignmentScore] AS [x]
      WHERE
       ( ( ((([x].[CourseSectionAssignment_Id] = 1) AND ([x].[Student_Id] = 2)) AND ([x].[AssessmentGBID] = 3))
        OR ((([x].[CourseSectionAssignment_Id] = 4) AND ([x].[Student_Id] = 5)) AND ([x].[AssessmentGBID] = 6)) )
        OR ((([x].[CourseSectionAssignment_Id] = 7) AND ([x].[Student_Id] = 8)) AND ([x].[AssessmentGBID] = 9)) )

И это (выгодный) план выполнения:

Execution plan of query with literal values

Я знаю, почему, или, по крайней мере, я верю, что знаю, почему планы выполнения отличаются. Это связано с тем, что оптимизатор не знает, будут ли параметры иметь значение NULL, и должен оптимизировать этот случай. Тестирование литерального запроса с NULL создает неблагоприятный план выполнения. (Почему анализатор параметров не видит, что значения не равны NULL, и не создает лучший план выполнения?)

В настоящее время в коде C # я вручную использую деревья выражений, чтобы заменить свойства объекта константами выражений, чтобы сгенерированный запрос был литеральным запросом. Насколько я знаю, литералы заставляют SQL-сервер каждый раз генерировать новый план выполнения, что не очень хорошо.

Мне бы хотелось, чтобы параметризованный запрос генерировал благоприятный план выполнения. Пока что единственный ответ, который я нашел, - это использование подсказки OPTION(RECOMPILE), что не совсем то, что я хочу, поскольку заставляет пересоздать план выполнения.

Как я могу каждый раз использовать один и тот же выгодный план выполнения с параметризованным запросом?

1 Ответ

0 голосов
/ 31 октября 2018

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

SELECT [x].[Id]
FROM [Gradebook].[AssignmentScore] AS [x]
WHERE [x].[CourseSectionAssignment_Id] = 1
AND [x].[Student_Id] = 2
AND [x].[AssessmentGBID] = 3
UNION
SELECT [x].[Id]
FROM [Gradebook].[AssignmentScore] AS [x]
WHERE [x].[CourseSectionAssignment_Id] = 4
AND [x].[Student_Id] = 5
AND [x].[AssessmentGBID] = 6
UNION
SELECT [x].[Id]
FROM [Gradebook].[AssignmentScore] AS [x]
WHERE [x].[CourseSectionAssignment_Id] = 7
AND [x].[Student_Id] = 8
AND [x].[AssessmentGBID] = 9

Кроме того, вы можете создать временную таблицу с «действительными» комбинациями и просто присоединиться к ней в этих трех полях.

...