Почему логические чтения для оконных агрегатных функций так высоки? - PullRequest
22 голосов
/ 20 ноября 2010

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

После некоторых проб и ошибок я нашел формулу, которая, кажется, справедлива дляСценарий тестирования и план выполнения ниже.Worktable logical reads = 1 + NumberOfRows * 2 + NumberOfGroups * 4

Я не понимаю, почему эта формула справедлива.Это больше, чем я думал, что нужно смотреть на план.Может ли кто-нибудь дать удар за ударом отчет о том, что происходит, что объясняет это?

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

SET STATISTICS IO OFF; SET NOCOUNT ON;

IF Object_id('tempdb..#Orders') IS NOT NULL
  DROP TABLE #Orders;

CREATE TABLE #Orders
  (
     OrderID    INT IDENTITY(1, 1) NOT NULL PRIMARY KEY CLUSTERED,
     CustomerID NCHAR(5) NULL,
     Freight    MONEY NULL,
  );

CREATE NONCLUSTERED INDEX ix
  ON #Orders (CustomerID)
  INCLUDE (Freight);

INSERT INTO #Orders
VALUES (N'ALFKI', 29.46), 
       (N'ALFKI', 61.02), 
       (N'ALFKI', 23.94), 
       (N'ANATR', 39.92), 
       (N'ANTON', 22.00);

SELECT PredictedWorktableLogicalReads = 
        1 + 2 * Count(*) + 4 * Count(DISTINCT CustomerID)
FROM   #Orders;

SET STATISTICS IO ON;

SELECT OrderID,
       Freight,
       Avg(Freight) OVER (PARTITION BY CustomerID) AS Avg_Freight
FROM   #Orders; 

Выход

PredictedWorktableLogicalReads
------------------------------
23

Table 'Worktable'. Scan count 3, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#Orders___________000000000002'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Execution Plan

Дополнительная информация:

Хорошее объяснение этих катушек можно найти в главе 3 Настройка и оптимизация запросов Книга и в этом блоге Пола Уайта .

Таким образом, итератор сегмента в верхней части плана добавляет флаг в строки, которые он отправляет, указывая, когда начинается новый раздел.Первичная катушка сегмента получает строку за раз от итератора сегмента и вставляет ее в рабочую таблицу в базе данных tempdb.Как только флаг получает сообщение о начале новой группы, он возвращает строку в верхний вход оператора вложенных циклов.Это приводит к тому, что агрегат потока вызывается по строкам в рабочей таблице, вычисляется среднее значение, а затем это значение объединяется со строками в рабочей таблице до того, как рабочая таблица усекается и готова к новой группе.Буфер сегмента испускает фиктивную строку для обработки окончательной группы.

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

CREATE TABLE #WorkTable
  (
     OrderID    INT,
     CustomerID NCHAR(5) NULL,
     Freight    MONEY NULL,
  )

DECLARE @Average MONEY

PRINT 'Insert 3 Rows'

INSERT INTO #WorkTable
VALUES      (1, N'ALFKI', 29.46) /*Scan count 0, logical reads 1*/

INSERT INTO #WorkTable
VALUES      (2, N'ALFKI', 61.02) /*Scan count 0, logical reads 1*/

INSERT INTO #WorkTable
VALUES      (3, N'ALFKI', 23.94) /*Scan count 0, logical reads 1*/
PRINT 'Calculate AVG'

SELECT @Average = Avg(Freight)
FROM   #WorkTable /*Scan count 1, logical reads 1*/
PRINT 'Return Rows - With the average column included'

/*This convoluted query is just to force a nested loops plan*/
SELECT *
FROM   (SELECT @Average AS Avg_Freight) T /*Scan count 1, logical reads 1*/
       OUTER APPLY #WorkTable
WHERE  COALESCE(Freight, OrderID) IS NOT NULL
       AND @Average IS NOT NULL

PRINT 'Clear out work table'

TRUNCATE TABLE #WorkTable

PRINT 'Insert 1 Row'

INSERT INTO #WorkTable
VALUES      (4, N'ANATR', 39.92) /*Scan count 0, logical reads 1*/
PRINT 'Calculate AVG'

SELECT @Average = Avg(Freight)
FROM   #WorkTable /*Scan count 1, logical reads 1*/
PRINT 'Return Rows - With the average column included'

SELECT *
FROM   (SELECT @Average AS Avg_Freight) T /*Scan count 1, logical reads 1*/
       OUTER APPLY #WorkTable
WHERE  COALESCE(Freight, OrderID) IS NOT NULL
       AND @Average IS NOT NULL

PRINT 'Clear out work table'

TRUNCATE TABLE #WorkTable

PRINT 'Insert 1 Row'

INSERT INTO #WorkTable
VALUES      (5, N'ANTON', 22.00) /*Scan count 0, logical reads 1*/
PRINT 'Calculate AVG'

SELECT @Average = Avg(Freight)
FROM   #WorkTable /*Scan count 1, logical reads 1*/
PRINT 'Return Rows - With the average column included'

SELECT *
FROM   (SELECT @Average AS Avg_Freight) T /*Scan count 1, logical reads 1*/
       OUTER APPLY #WorkTable
WHERE  COALESCE(Freight, OrderID) IS NOT NULL
       AND @Average IS NOT NULL

PRINT 'Clear out work table'

TRUNCATE TABLE #WorkTable

PRINT 'Calculate AVG'

SELECT @Average = Avg(Freight)
FROM   #WorkTable /*Scan count 1, logical reads 0*/
PRINT 'Return Rows - With the average column included'

SELECT *
FROM   (SELECT @Average AS Avg_Freight) T
       OUTER APPLY #WorkTable
WHERE  COALESCE(Freight, OrderID) IS NOT NULL
       AND @Average IS NOT NULL

DROP TABLE #WorkTable 

Ответы [ 2 ]

21 голосов
/ 04 марта 2011

Логические операции чтения считаются по-разному для рабочих таблиц: существует одно «логическое чтение» для строки чтения. Это не означает, что рабочие таблицы как-то менее эффективны, чем «реальные» таблицы спула (как раз наоборот); логические чтения только в разных единицах.

Я полагаю, что мысль о том, что подсчет хэшированных страниц для логических операций чтения таблицы не будет очень полезен, поскольку эти структуры являются внутренними для сервера. Строки отчетов, помещенные в буфер логических чтений, делают число более значимым для анализа.

Это понимание должно прояснить причину, по которой ваша формула работает четко. Две вторичные катушки полностью читаются дважды (2 * COUNT (*)), и первичная катушка испускает (количество значений группы + 1) строки, как описано в моей записи блога, давая компонент (COUNT (DISTINCT CustomerID) + 1) , Плюс - для дополнительной строки, испускаемой первичной катушкой, чтобы указать, что окончательная группа закончилась.

Пол

0 голосов
/ 20 ноября 2010

В формуле, которую вы передаете NumberOfRows, значение * 2 будет иметь значение true, потому что функция Sort и Stream Aggregate show на вашей диаграмме выполнения требуют, чтобы все строки завершили обработку. Можете ли вы подтвердить уменьшение числа логических операций чтения, когда добавлено предложение "где" для:

  1. стоимость груза
  2. CustomerID
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...