Почему вставляет в и присоединяется к #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 имеет ~ 5 000 строк A и B ~ 4 000 строк каждый

Этот запрос имеет производительность во время выполнения ~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 делает неправильный тип "соединения" с подзапросом, но возможно я пропустил его, нет никакого способа указать тип соединения для использования со связанными подзапросами.

Существует ли способ достигнуть этого, не используя #temp таблицы / табличные переменные через индексы и/или подсказки?

Править: Отметьте это dbo. TableReturningFunc1 и dbo. TableReturningFunc2 является встроенный TVF's, не составной, или они "параметризованы" операторы представления.

12
задан Joseph Kingry 29 May 2009 в 21:08
поделиться

6 ответов

Your procedures are being reevaluated for each row in P.

What you do with the temp tables is in fact caching the resultset generated by the stored procedures, thus removing the need to reevaluate.

Inserting into a temp table is fast because it does not generate redo / rollback.

Joins are also fast, since having a stable resultset allows possibility to create a temporary index with an Eager Spool or a Worktable

You can reuse the procedures without temp tables, using CTE's, but for this to be efficient, SQL Server needs to materialize the results of CTE.

You may try to force it do this with using an ORDER BY inside a subquery:

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  …

, which may result in Eager Spool generated by the optimizer.

However, this is far from being guaranteed.

The guaranteed way is to add an OPTION (USE PLAN) to your query and wrap the correspondind CTE into the Spool clause.

See this entry in my blog on how to do that:

This is hard to maintain, since you will need to rewrite your plan each time you rewrite the query, but this works well and is quite efficient.

Using the temp tables will be much easier, though.

16
ответ дан 2 December 2019 в 07:22
поделиться

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

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
2
ответ дан 2 December 2019 в 07:22
поделиться

Рассмотрите возможность использования конструкции WITH common_table_expression для того, что у вас теперь есть в качестве подвыборки или временных таблиц, см. http://msdn.microsoft.com/en -us / library / ms175972 (SQL.90) .aspx .

0
ответ дан 2 December 2019 в 07:22
поделиться

Существует несколько возможных причин, по которым использование промежуточных таблиц Temp может ускорить запрос, но наиболее вероятно, что в вашем случае выполняемые функции вызванные (но не перечисленные), вероятно, являются TVF с несколькими операторами, а не встроенными TVF. TVF с несколькими операторами непрозрачны для оптимизации вызывающих запросов, и поэтому оптимизатор не может определить, есть ли какие-либо возможности для повторного использования данных или других оптимизаций изменения порядка логических / физических операторов. Таким образом, все, что он может сделать, - это повторно выполнять TVF каждый раз, когда содержащийся в нем запрос должен создавать новую строку со столбцами XML.

Короче говоря, многопользовательские TVF расстраивают оптимизатор.

Обычные решения , в порядке (типичного) предпочтения:

  1. Перепишите вызывающую ошибку TVF с несколькими операторами, чтобы она была встроенной TVF.
  2. Вставьте код функции в вызывающий запрос, или
  3. Выгрузите данные вызывающей ошибки TVF во временную таблицу. это то, что вы сделали ...
1
ответ дан 2 December 2019 в 07:22
поделиться

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

С временными таблицами вы явно указываете серверу Sql, какое промежуточное хранилище использовать. Но если вы поместите все в большой запрос, Sql Server решит сам. На самом деле разница не такая уж и большая; в конце концов, используется временное хранилище, независимо от того, указываете вы его как временную таблицу или нет.

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

0
ответ дан 2 December 2019 в 07:22
поделиться

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

Здесь есть хорошая статья о различиях и влиянии на производительность:

http://www.codeproject.com/KB/database/SQP_performance.aspx

-2
ответ дан 2 December 2019 в 07:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: