2010-11-20 6 views
21

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. 

Execution Plan

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 
+0

Gibt es einen Unterschied in der Leistung, wenn wir Indizes für temporäre Tabellen erstellen? – RGS

Antwort

21

Logical liest gezählt anders für Arbeitstische: Es gibt eine ‚logische lesen 'pro Zeile lesen. Dies bedeutet nicht, dass Arbeitstabellen irgendwie weniger effizient sind als eine "echte" Spool-Tabelle (ganz im Gegenteil); Die logischen Lesevorgänge sind nur in verschiedenen Einheiten.

Ich glaube, das Denken war, dass das Zählen von Hash-Seiten für Worktable logische Lesevorgänge nicht sehr nützlich sein würde, da diese Strukturen innerhalb des Servers sind.Die im logischen Lesezähler gespoolten Berichtszeilen machen die Nummer für Analysezwecke aussagekräftiger.

Diese Einsicht sollte den Grund dafür geben, dass Ihre Formel klar funktioniert. Die zwei sekundären Spools werden zweimal vollständig gelesen (2 * COUNT (*)), und der primäre Spool gibt (Anzahl der Gruppenwerte + 1) Zeilen wie in meinem Blog-Eintrag angegeben aus, wobei die Komponente (COUNT (DISTINCT CustomerID) + 1) angegeben wird . Das Pluszeichen ist für die zusätzliche Zeile, die von der primären Spool ausgegeben wird, um anzuzeigen, dass die letzte Gruppe beendet ist.

Paul

+1

Ah, das erklärt tatsächlich die Dinge. Vielen Dank, dass Sie sich die Zeit genommen haben, dies zu beantworten, da es mich ein paar Monate lang verwirrt hat! –

+0

@MartinSmith & SQLkiwi - gibt es hier genug für abgeleitete Frage (Ich werde eine, wenn Sie denken, es gibt genug Fleisch) - Wie vergleichen Sie zwei gleichwertige Abfragen für effizient, wo man Fensterfunktionen verwendet man nicht? Liest/schreibt/CPU in Profiler und Ausführungsplan sind meine gehen, aber das macht die Sache komplizierter. – EBarr

+1

@EBarr Ich denke, die Frage hat ihren Wert, obwohl sie sorgfältig formuliert werden müsste, um sie fokussiert zu halten. Könnte am besten auf http://dba.stackexchange.com/ gestellt werden. Meine Sicht ist, dass das logische Reading-Maß im Allgemeinen zu weit gewichtet ist. –

0

In der Formel geben Sie die NumberOfRows * 2 wegen der Sortierfunktion wahr halten würde und Aggregate zeigen in Ihrer Ausführung Diagramm Streamen beide müssen alle Zeilen Verarbeitung abzuschließen. Können Sie sich eine Abnahme der logischen comfirm liest, wenn ein „where“ -Klausel für hinzugefügt wird:

  1. Wert von Fracht

  2. CustomerID
+0

Haben Sie für diese Abfrage oder während Ihrer Entwicklung der Formel irgendwelche physikalischen Lesevorgänge gesehen? Können Sie DBCC DROPCLEANBUFFERS verwenden und die physischen und logischen Lesevorgänge erneut erfassen? Könnten Sie der QUEREY eine WHERE-Klausel hinzufügen und sie erneut ausführen? Danke –

Verwandte Themen