Ich habe festgestellt, dass in Ausführungsplänen, die gemeinsame Teilausdrucksammlungen verwenden, die gemeldeten logischen Lesevorgänge für große Tabellen ziemlich hoch werden.Warum sind logische Lesevorgänge für Fenster-Aggregatfunktionen so hoch?
Nach einigem Versuch und Irrtum habe ich eine Formel gefunden, die für das Testskript und den Ausführungsplan unten zu halten scheint. Worktable logical reads = 1 + NumberOfRows * 2 + NumberOfGroups * 4
Ich verstehe nicht den Grund, warum diese Formel gilt. Es ist mehr als ich es für notwendig gehalten hätte, den Plan zu betrachten. Kann jemand einen Schlag nach dem anderen geben, was passiert?
Oder fehlgeschlagen, gibt es eine Möglichkeit zu verfolgen, welche Seite in jedem logischen Lese gelesen wurde, so dass ich es für mich selbst erarbeiten kann?
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;
Ausgabe
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.
Zusätzliche Information:
Es gibt eine gute Erklärung dieser Spulen in Kapitel 3 des Buches Query Tuning and Optimization und this blog post by Paul White.
Zusammenfassend fügt der Segment-Iterator am Anfang des Plans den Zeilen, die er sendet, ein Flag hinzu, das angibt, wann es sich um den Start einer neuen Partition handelt. Der primäre Segment-Spool ruft vom Segment-Iterator jeweils eine Zeile ab und fügt sie in eine Arbeitstabelle in tempdb ein. Sobald das Flag angezeigt wird, dass eine neue Gruppe gestartet wurde, wird eine Zeile an die oberste Eingabe des Nested Loop-Operators zurückgegeben. Dies bewirkt, dass das Stream-Aggregat über die Zeilen in der Arbeitstabelle aufgerufen wird. Der Durchschnittswert wird berechnet, und dieser Wert wird mit den Zeilen in der Arbeitstabelle verknüpft, bevor die Arbeitstabelle für die neue Gruppe abgeschnitten wird. Die Segmentspule sendet eine Dummy-Zeile aus, um die endgültige Gruppe zu verarbeiten.
Soweit ich verstehe, ist die Worktable ein Haufen (oder es würde im Plan als Index-Spool bezeichnet werden). Wenn ich jedoch versuche, den gleichen Prozess zu replizieren, benötigt er nur 11 logische Lesevorgänge.
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
Gibt es einen Unterschied in der Leistung, wenn wir Indizes für temporäre Tabellen erstellen? – RGS