Выбор SQL занимает слишком много времени для выполнения

9

Это простой выбор из временной таблицы, оставляющий присоединение к существующей таблице по его первичному ключу, с двумя подвыборками, использующими top 1 со ссылкой на объединенную таблицу.

В коде:

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn1 AND 
    TempTable.PKColumn2 = JoinedTable.PKColumn2)
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Это точная копия моего запроса.

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

Я проверил, есть ли у каждой таблицы первичный ключ, все они имеют. Все они имеют индексы И статистику для своих важных столбцов, например, в этих предложениях WHERE и в предложениях JOIN. Единственная таблица, для которой не определен ни первичный ключ, ни индекс, является временной таблицей, но это не проблема, потому что она не связана с медленным суб-выбором, и, как я уже говорил, без суб-выбора она работает просто отлично.

Без них TOP 1он возвращает более одного результата и выдает ошибку.

Помогите кому-нибудь?

РЕДАКТИРОВАТЬ :

Таким образом, план выполнения сказал мне, что я пропустил Индекс. Я создал его и пересоздал некоторые другие индексы. Через некоторое время план выполнения использовал их, и теперь запрос выполняется быстро. Единственная проблема в том, что мне не удалось сделать это снова на другом сервере, для того же запроса. Так что моим решением будет HINT, какой индекс будет использовать SQL Server.

Smur
источник
Вау, это впечатляет. Но можете ли вы вместо этого разделить это на несколько отдельных утверждений? А как насчет хранимых процедур?
2
@Adel Этот выбор на самом деле является дополнительным выбором внутри хранимой процедуры. Все это на самом деле довольно большое, но я на 100% уверен, что это именно та часть, которая требует времени для выполнения.
Изменение в плане выполнения, включая автоматически выбранные индексы, скорее всего, связано с изменением данных. Я бы позаботился о том, чтобы ваши индексы полностью покрывали, иначе движок будет использовать неожиданные пути, такие как сканирование таблицы Я предлагаю пересмотреть план выполнения на новом сервере (без подсказок), чтобы увидеть, где вы испытываете отклонения от исходной системы.
Роберт Миллер
Понимаю. Я только поменял сервер, база данных такая же, с такими же индексами. Тем не менее, он, кажется, автоматически не решил использовать мои индексы. Он делает именно то, что вы сказали: сканирование таблицы.
Смур
Похоже, что opitmizer запроса не похож ни на один из индексов таблицы для вашего запроса. Был ли в плане выполнения отсутствующий индекс?
Роберт Миллер

Ответы:

7

Я думаю, что в запросе на миллион записей вы должны избегать подобных вещей OUTER JOINS. Я предлагаю вам использовать UNION ALLвместо LEFT JOIN. Пока я думаю, что CROSS APPLYэто более эффективно, чем подзапрос в предложении select, я буду модифицировать запрос, написанный Conard Frix, что я считаю правильным.

Сейчас: когда я начал изменять ваш запрос , я заметил , что у вас есть ИНЕКЕ говоря: JoinedTable.WhereColumn IN (1, 3). в этом случае, если поле равно нулю, условие станет ложным. тогда почему вы используете LEFT JOIN при фильтрации строк с нулевым значением? Просто замените LEFT JOINС INNER JOIN, я гарантирую, что это станет быстрее.

о ИНДЕКС:

обратите внимание, что когда у вас есть индекс в таблице, скажем,

table1(a int, b nvarchar)

и ваш индекс:

nonclustered index ix1 on table1(a)

и вы хотите сделать что-то вроде этого:

select a,b from table1
where a < 10

в свой индекс вы не включили столбец, bтак что происходит?

Если sql-сервер использует ваш индекс, ему придется искать в индексе, называемом «Index Seek», а затем обращаться к основной таблице, чтобы получить столбец b, который называется «Look Up» . Эта процедура может занять гораздо больше времени, чем сканирование самой таблицы: «Сканирование таблицы» .

но основываясь на статистике, которую имеет sql-сервер, в таких ситуациях он может вообще не использовать ваш индекс.

поэтому прежде всего проверьте, используется Execution Planли индекс вообще.

если да или нет и то и другое, измените свой индекс, чтобы включить все выбранные столбцы. скажи как:

nonclustered index ix1 on table1(a) include(b)

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


источник
1
Я не могу поменять это левое соединение на Внутреннее соединение, оно бы испортило результаты, это бизнес-правило: вторая таблица не обязательно должна иметь связанную запись. Кроме того, столбец в предложении WHERE не принимает нулевые значения.
Смур
6

Это суб выбирает в вашем выборе столбца, который вызывает медленный возврат. Вы должны попытаться использовать субвыборы в левых соединениях или использовать производную таблицу, как я определил ниже.

Использование левого соединения к двум экземплярам третьей таблицы

SELECT
  TempTable.Col1,
  TempTable.Col2,
  TempTable.Col3,
  JoinedTable.Col1,
  JoinedTable.Col2,
  ThirdTable.Col1 AS ThirdTableColumn1,
  ThirdTable2.Col1 AS ThirdTableColumn2
FROM #TempTable as TempTable
LEFT JOIN JoinedTable ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTable.PKColumn2)
LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Использование производной таблицы

 SELECT 
      TempTable.Col1,
      TempTable.Col2,
      TempTable.Col3,
      DerivedTable.Col1,
      DerivedTable.Col2,
      DerivedTable.ThirdTableColumn1,
      DerivedTable.ThirdTableColumn2
 FROM #TempTable as TempTable
    LEFT JOIN (SELECT
                 JoinedTable.PKColumn2,
                 JoinedTable.Col1,
                 JoinedTable.Col2,
                 JoinedTable.WhereColumn,
                 ThirdTable.Col1 AS ThirdTableColumn1,
                 ThirdTable2.Col1 AS ThirdTableColumn2
               FROM JoinedTable
               LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
               LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn) 
        DerivedTable ON (TempTable.PKColumn1 = DerivedTable .PKColumn2 AND 
        TempTable.PKColumn2 = DerivedTable.PKColumn2)
    WHERE
        DerivedTable.WhereColumn IN  (1, 3)
Джон Хартсок
источник
2

Попробуйте применить кросс вместо

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    ThirdTableColumn1.col1,
    ThirdTableColumn2.col1

FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTablePKColumn2)

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Вы также можете использовать CTE и row_number или встроенный запрос, используя MIN

Конрад Фрикс
источник
2

Переместите биты JOIN из основной части предложения и поместите его как подвыбор. Перемещение его в разделы WHERE и JOIN гарантирует, что вам не придется выбирать TOP 1 снова и снова, что, по-моему, и является причиной медлительности. Если вы хотите проверить это, изучите план выполнения.


источник
2

В ThirdTableссылки, (суб выбирает в вашем примере), необходимо и тот же индекс внимания , как и любая другая часть запроса.

Независимо от того, используете ли вы суб выбирает:

(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeColumn = JoinedTable.SomeColumn
) as ThirdTableColumn1,
(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
) as ThirdTableColumn2,

ЛЕВЫЕ СОЕДИНЕНИЯ (как предложил Джон Хартсок):

LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn

ПРИМЕНЕНИЕ КРЕСТА (как предложено Конрадом Фриксом):

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2

Вы должны убедиться , covering indexesопределены для ThirdTable.SomeColumnи ThirdTable.SomeOtherColumnи индексы являются уникальными. Это означает, что вам нужно будет дополнительно квалифицировать ThirdTableссылки, чтобы исключить выбор нескольких строк и повысить производительность. Выбор sub selects, LEFT JOINили CROSS APPLYбудет на самом деле не имеет значения , пока вы не улучшить селективность в отношении ThirdTable.SomeColumnи ThirdTable.SomeOtherColumnпути включения большего количества столбцов для обеспечения уникальной селективности. До тех пор, я ожидаю, что ваша производительность будет продолжать страдать.

covering indexТема хорошо представлена Maziar Тахери; не повторяя его работы, я подчеркиваю необходимость принять близко к сердцу использование указателей покрытия.

Короче говоря: повышение селективности для ThirdTable.SomeColumnи ThirdTable.SomeOtherColumnзапросов (или соединение) путем добавления связанных в таблице-столбцы , чтобы обеспечить уникальное совпадение строк. Если это невозможно, то вы будете продолжать страдать от проблем с производительностью, так как двигатель занят вытягиванием рядов, которые впоследствии выбрасываются. Это влияет на ваш ввод / вывод, процессор и, в конечном итоге, на план выполнения.

Роберт Миллер
источник