Почему вставка и соединение таблиц #temp быстрее?
У меня есть запрос, который выглядит как
SELECT
P.Column1,
P.Column2,
P.Column3,
...
(
SELECT
A.ColumnX,
A.ColumnY,
...
FROM
dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A
WHERE
A.Key = P.Key
FOR XML AUTO, TYPE
),
(
SELECT
B.ColumnX,
B.ColumnY,
...
FROM
dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B
WHERE
B.Key = P.Key
FOR XML AUTO, TYPE
)
FROM
(
<joined tables here>
) AS P
FOR XML AUTO,ROOT('ROOT')
P имеет ~ 5000 строк
A и B ~ 4000 строк
Этот запрос имеет производительность выполнения ~ 10 + минут.
Изменить это на это:
SELECT
P.Column1,
P.Column2,
P.Column3,
...
INTO #P
SELECT
A.ColumnX,
A.ColumnY,
...
INTO #A
FROM
dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A
SELECT
B.ColumnX,
B.ColumnY,
...
INTO #B
FROM
dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B
SELECT
P.Column1,
P.Column2,
P.Column3,
...
(
SELECT
A.ColumnX,
A.ColumnY,
...
FROM
#A AS A
WHERE
A.Key = P.Key
FOR XML AUTO, TYPE
),
(
SELECT
B.ColumnX,
B.ColumnY,
...
FROM
#B AS B
WHERE
B.Key = P.Key
FOR XML AUTO, TYPE
)
FROM #P AS P
FOR XML AUTO,ROOT('ROOT')
имеет производительность ~ 4 секунды.
Это не имеет большого смысла, так как это может стоить вставить в временную таблицу, а затем сделать соединение по умолчанию выше. Моя склонность заключается в том, что SQL делает неправильный тип "join" с подзапросом, но, возможно, я пропустил его, нет способа указать тип соединения для использования с коррелированными подзапросами.
Есть ли способ достичь этого без использования #temp tables/@переменных таблицы с помощью индексов и/или подсказок?
EDIT: Обратите внимание, что dbo.TableReturningFunc1 и dbo.TableReturningFunc2 являются встроенными TVF, а не многозадачными, или они являются "параметризованными" инструкциями вида.
Ответы
Ответ 1
Ваши процедуры переоцениваются для каждой строки в P
.
То, что вы делаете с временными таблицами, фактически кэширует набор результатов, созданный хранимыми процедурами, тем самым устраняя необходимость переоценки.
Вставка в временную таблицу выполняется быстро, поскольку она не генерирует redo
/rollback
.
Соединения также бывают быстрыми, поскольку наличие стабильного набора результатов позволяет создавать временный индекс с помощью Eager Spool
или Worktable
Вы можете повторно использовать процедуры без временных таблиц, используя CTE
, но для того, чтобы это было эффективно, SQL Server
должно материализовать результаты CTE
.
Вы можете попытаться заставить его сделать это с помощью ORDER BY
внутри подзапроса:
WITH f1 AS
(
SELECT TOP 1000000000
A.ColumnX,
A.ColumnY
FROM dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A
ORDER BY
A.key
),
f2 AS
(
SELECT TOP 1000000000
B.ColumnX,
B.ColumnY,
FROM dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B
ORDER BY
B.Key
)
SELECT …
что может привести к Eager Spool
, генерируемому оптимизатором.
Однако это далеко не гарантировано.
Гарантированный способ заключается в добавлении OPTION (USE PLAN)
к вашему запросу и завершении соответствия CTE
в предложение Spool
.
Посмотрите эту запись в своем блоге о том, как это сделать:
Это сложно поддерживать, так как вам придется переписывать свой план каждый раз, когда вы переписываете запрос, но это работает хорошо и достаточно эффективно.
Использование временных таблиц будет намного проще.
Ответ 2
Этот ответ должен быть прочитан вместе с Quassnoi article
http://explainextended.com/2009/05/28/generating-xml-in-subqueries/
При разумном применении CROSS APPLY вы можете принудительно выполнить кэширование или краткую оценку встроенных TVF. Этот запрос возвращается мгновенно.
SELECT *
FROM (
SELECT (
SELECT f.num
FOR XML PATH('fo'), ELEMENTS ABSENT
) AS x
FROM [20090528_tvf].t_integer i
cross apply (
select num
from [20090528_tvf].fn_num(9990) f
where f.num = i.num
) f
) q
--WHERE x IS NOT NULL -- covered by using CROSS apply
FOR XML AUTO
Вы не представили реальные структуры, поэтому сложно создать что-то значимое, но техника должна также применяться.
Если вы измените многозадачность TVF в статье Quassnoi на встроенный TVF, план станет еще быстрее (по крайней мере, на один порядок), и план магически сводится к тому, что я не могу понять (это слишком просто!).
CREATE FUNCTION [20090528_tvf].fn_num(@maxval INT)
RETURNS TABLE
AS RETURN
SELECT num + @maxval num
FROM t_integer
Статистика
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
(10 row(s) affected)
Table 't_integer'. Scan count 2, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 2 ms.
Ответ 3
Это проблема с вашим подзапросом, ссылающимся на ваш внешний запрос, то есть запрос должен быть скомпилирован и выполнен для каждой строки во внешнем запросе.
Вместо использования явных временных таблиц вы можете использовать производную таблицу.
Чтобы упростить свой пример:
SELECT P.Column1,
(SELECT [your XML transformation etc] FROM A where A.ID = P.ID) AS A
Если P содержит 10 000 записей, тогда SELECT A.ColumnX FROM A, где A.ID = P.ID будет исполнено 10 000 раз.
Вместо этого вы можете использовать производную таблицу следующим образом:
SELECT P.Column1, A2.Column FROM
P LEFT JOIN
(SELECT A.ID, [your XML transformation etc] FROM A) AS A2
ON P.ID = A2.ID
Хорошо, не этот иллюстративный псевдокод, но основная идея такая же, как и временная таблица, за исключением того, что SQL Server делает все в памяти: сначала выбирает все данные в "A2" и строит временную таблицу в памяти, затем присоединяется к нему. Это избавит вас от необходимости выбирать его для TEMP.
Просто, чтобы дать вам пример принципа в другом контексте, где он может иметь более непосредственный смысл. Рассмотрите информацию о сотрудниках и отсутствии, где вы хотите показать количество дней отсутствия, зарегистрированных для каждого сотрудника.
Плохо: (выполняется столько запросов, сколько есть сотрудников в БД)
SELECT EmpName,
(SELECT SUM(absdays) FROM Absence where Absence.PerID = Employee.PerID) AS Abstotal
FROM Employee
Хорошо: (выполняется только два запроса)
SELECT EmpName, AbsSummary.Abstotal
FROM Employee LEFT JOIN
(SELECT PerID, SUM(absdays) As Abstotal
FROM Absence GROUP BY PerID) AS AbsSummary
ON AbsSummary.PerID = Employee.PerID
Ответ 4
Существует несколько возможных причин, по которым использование промежуточных таблиц Temp может ускорить запрос, но наиболее вероятным в вашем случае является то, что функции, которые вызываются (но не указаны), вероятно, являются многопозиционными TVF, а не -line TVF. Multi-statement TVF непрозрачны для оптимизации своих вызывающих запросов, и, таким образом, оптимизатор не может определить, есть ли какие-либо возможности для повторного использования данных или другие оптимизации логического/физического переопределения операторов. Таким образом, все, что он может сделать, состоит в том, чтобы повторно запускать TVF каждый раз, когда содержательный запрос должен создавать другую строку с столбцами XML.
Короче говоря, многозадачность TVF расстраивает оптимизатор.
Обычными решениями в порядке (типичных) предпочтений являются:
- Повторно напишите оскорбительный мультирежим TVF как встроенный TVF
- Ввести код функции в вызывающий запрос или
- Сбросьте оскорбительные данные TVF в таблицу temp. это то, что вы сделали...
Ответ 5
Рассмотрите возможность использования конструкции WITH common_table_expression
для того, что у вас есть в качестве подвыборов или временных таблиц, см. http://msdn.microsoft.com/en-us/library/ms175972(SQL.90).aspx.
Ответ 6
Это не имеет большого смысла, поскольку казалось бы, затраты на temp, а затем выполните соединение быть выше по de > Это не имеет большого смысла, поскольку это казалось бы, затраты на temp, а затем выполните соединение выше по умолчанию.
С временными таблицами вы явно проинструктируете Sql Server о том, какое промежуточное хранилище использовать. Но если вы вложите все в большой запрос, Sql Server решит сам. Разница на самом деле не такая большая; в конце дня используется временное хранилище, независимо от того, указали ли вы его как временную таблицу или нет.
В вашем случае временные таблицы работают быстрее, поэтому почему бы не придерживаться их?
Ответ 7
Я согласился, таблица Temp - хорошая концепция. Когда число строк увеличивается в таблице, пример 40 миллионов строк, и я хочу обновить несколько столбцов в таблице, применяя объединения с другой таблицей, в этом случае я всегда предпочитаю использовать выражение Common table для обновления столбцов в выражении select, используя case, теперь мой набор результатов для набора предложений содержит обновленные строки. Включение 40 миллионов записей в таблицу temp с помощью оператора select с использованием case case заняло у меня 21 минуту, а затем создание индекса заняло 10 минут, так что время создания и создания индекса заняло 30 минут, Затем я собираюсь применить обновление, присоединив обновленную таблицу результатов temp table с основной таблицей. Потребовалось 5 минут, чтобы обновить 10 миллионов записей из 40 миллионов записей, поэтому мое общее время обновления для 10 миллионов записей заняло почти 35 минут против 5 минут от выражения Common table. Мой выбор в этом случае является общим табличным выражением.
Ответ 8
Если временные таблицы оказываются более быстрыми в вашем конкретном экземпляре, вместо этого вы должны использовать переменную таблицы.
Здесь есть хорошая статья о различиях и последствиях:
http://www.codeproject.com/KB/database/SQP_performance.aspx