2010-11-20 3 views
21

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

После некоторых проб и ошибок я нашел формулу, которая, как представляется, содержит тестовый сценарий и план выполнения ниже. 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 книги Query Tuning and Optimization и this blog post by Paul White.

Таким образом, итератор сегмента в верхней части плана добавляет флаг в строки, которые он отправляет, указывающий, когда это начало нового раздела. Основная катушка сегмента получает строку за раз от итератора сегмента и вставляет ее в рабочую таблицу в 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 
+0

Есть ли разница в производительности при создании индексов для таблиц Temp? – RGS

ответ

21

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

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

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

Paul

+1

А, Это действительно объясняет вещи. Большое спасибо за то, что нашли время ответить на это, поскольку это озадачило меня в течение нескольких месяцев! –

+0

@MartinSmith & SQLkiwi - достаточно ли здесь для производного вопроса (я отправлю сообщение, если вы считаете, что есть достаточно мяса). Как вы сравниваете два эквивалентных запроса для эффективного использования оконных функций, которых нет? Чтение/запись/cpu в профилировщике и плане выполнения - это мой подход, но это усложняет этот вопрос. – EBarr

+1

@EBarr Я думаю, что этот вопрос имеет свои достоинства, хотя для его фокусировки потребуется тщательная формулировка. Может быть лучше всего поставлено на http://dba.stackexchange.com/. Мое мнение состоит в том, что, как правило, показатель логического считывания задается * слишком большим весом. –

0

В формуле вы даете NumberOfRows * 2 будет справедливо и из-за функции сортировки и поток агрегатных показать на диаграмме выполнения как нужно все строки для завершения обработки. Вы можете Comfirm снижения логического чтения, когда «где» пункт добавляются для:

  1. стоимости фрахта
  2. КодКлиента
+0

Вы видели какие-либо официальные чтения для этого запроса или во время разработки формулы? Можете ли вы использовать DBCC DROPCLEANBUFFERS и повторить физическое и логическое чтение? Не могли бы вы добавить предложение WHERE к querey и повторить? Спасибо –

Смежные вопросы