Wie messe ich eigentlich den Erfolg der Optimierung einer SQL Abfrage?

Hier ein Thema, welches mir immer wieder in Coachings von Einsteigern und Juniorberatern begegnet. Daher speziell für diese Zielgruppe ein paar Grundlagen.

Wie messe ich eigentlich, ob dank einer Maßnahme eine Query "schneller" geworden ist?

Der eine oder andere mag denken, dass das doch eine überflüssige Frage ist. Die Optimierung war erfolgreich, wenn eine Abfrage schneller fertig ist. Wo ist also das Problem? Und schließlich hat doch das Management Studio in der rechten untern Ecke extra dafür einen Timer. Und im Profiler kann ich mir doch auch die Dauer einer Abfrage anschauen. Damit sollte das Thema doch eigentlich durch sein, oder? Und bei vielen Queries steht da unten doch eh nur 00:00:00, die können also doch gar nicht weh tun.

Nun ja, ganz so einfach ist es dann doch nicht. Die Zeit, welche eine Query benötigt, ist immer relativ und kann von vielem abhängig sein. Die wesentlich entscheidendere Größe beim Tuning ist die Anzahl der für die Beantwortung notwendigen Datenbankseiten! Und dabei ist es fast egal, ob die Seiten gerade warm und trocken im Cache liegen oder gar erst vom Plattensystem gelesen werden müssen. Seite bleibt Seite. Daher sollte ein Ziel von Optimierungsmaßnahmen immer sein, dass die Anzahl der für ein Resultset notwendigen Datenbankseiten minimiert wird! Auch bei Abfragen, welche gefühlt flott laufen, kann es Sinn machen diese entsprechend zu optimieren, wenn diese auf einem Server vielleicht viele tausend Mal am Tag ausgeführt wird.

Ok, nur wie kommen wir nun an diese Anzahl der notwendigen Seiten und wie reduzieren wir diese?

Für den Einstieg in das Thema ist es nicht immer notwendig gleich den SQL Profiler mitlaufen zu lassen. Auch Querypläne müssen nicht immer gleich zerlegt werden, auch wenn das einen bereits deutlich nach vorne bringen würde.

Mit folgender Anweisung schalten wir die Darstellung einer Statistik ein, welche uns die Anzahl der Datenbankenseiten pro verwendeter Tabelle zeigen wird:

set statistics io on;

Und nun noch eine kleine Abfrage für die gute alte AdventureWorksDW2008:

select SUM(SalesAmount), COUNT(*)
from dbo.FactInternetSales fis
left join dbo.DimCustomer dc on dc.CustomerKey = fis.CustomerKey
where dc.YearlyIncome > 50000;

Und schon sehen wir im Management Studio unter Meldungen folgendes:

(1 Zeile(n) betroffen)
Worktable-Tabelle. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Read-Ahead-Lesevorgänge 0, logische LOB-Lesevorgänge 0, physische LOB-Lesevorgänge 0, Read-Ahead-LOB-Lesevorgänge 0.
FactInternetSales-Tabelle. Scananzahl 1, logische Lesevorgänge 1030, physische Lesevorgänge 0, Read-Ahead-Lesevorgänge 0, logische LOB-Lesevorgänge 0, physische LOB-Lesevorgänge 0, Read-Ahead-LOB-Lesevorgänge 0.
DimCustomer-Tabelle. Scananzahl 1, logische Lesevorgänge 978, physische Lesevorgänge 0, Read-Ahead-Lesevorgänge 0, logische LOB-Lesevorgänge 0, physische LOB-Lesevorgänge 0, Read-Ahead-LOB-Lesevorgänge 0.

Das "Ziel" habe ich mal markiert. Diese Werte gilt es im Rahmen einer Optimierung zu reduzieren. Denn je weniger Seiten benötigt werden, um so schneller wird die Abfrage und, kleiner Nebeneffekt, um so geringer sind Probleme durch Sperren. Performanceschwierigkeiten kann so viele Gründe haben, aber das würde hier zu weit gehen.

Aber wie kommen wir nun zu einer Optimierung? Es gibt zwei grundsätzliche Wege:

  • durchdachtere SQL Abfragen (weites Feld, würde den Rahmen dessen hier für den Moment sprengen)
  • bessere Indizierung (wird von viel zu vielen unterschätzt und von einigen tatsächlich auch überschätzt)

Am Ende geht es um weniger Seiten oder wie der Profi sagt: Weniger IO Operationen.

Wir versuchen es mal mit der Erzeugung von zwei Indizes, um die Lesevorgänge deutlich reduzieren. Für Details einfach mal einen Blick in die BooksOnline des SQL Server werfen.

CREATE NONCLUSTERED INDEX [ix_DimCustomer_YearlyIncome] ON [dbo].[DimCustomer]
(
    [CustomerKey] ASC
)
INCLUDE ( [YearlyIncome]) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [ix_FactInternetSales_CustomerKey_SalesAmount] ON [dbo].[FactInternetSales]
(
    [CustomerKey] ASC
)
INCLUDE ( [SalesAmount]) ON [PRIMARY]

So, identische Query nochmal starten und schon sehen wir, dass unsere Bemühungen erste Erfolge zeigen. Ach ja, natürlich bleibt das Ergebnis der SQL Abfrage identisch!

(1 Zeile(n) betroffen)
FactInternetSales-Tabelle. Scananzahl 1, logische Lesevorgänge 257, physische Lesevorgänge 0, Read-Ahead-Lesevorgänge 0, logische LOB-Lesevorgänge 0, physische LOB-Lesevorgänge 0, Read-Ahead-LOB-Lesevorgänge 0.
DimCustomer-Tabelle. Scananzahl 1, logische Lesevorgänge 44, physische Lesevorgänge 0, Read-Ahead-Lesevorgänge 0, logische LOB-Lesevorgänge 0, physische LOB-Lesevorgänge 0, Read-Ahead-LOB-Lesevorgänge 0.

Was passiert da? Kurzgefasst kann gesagt werden, dass der Queryoptimizer nun nur noch unsere beiden Indizes verwendet, welche genau die Spalten bereitstellen, die unsere Query benötigt. Und damit wäre normalerweise noch nicht Schluss mit der Optimierung, aber für den Moment soll das reichen, um das Konzept zu verdeutlichen wie wir den Erfolg einer Maßnahme messen können. Denn darum ging es mir hier.

Also, nochmal wiederholt: Erfolgreiche Optimierung heißt u. a. Reduzierung von Seitenzugriffen!

Und messen könnt Ihr das u. a. mittels set statistics io on.

  • *****
    Kommentar von: Torsten Schuessler
    06.07.10 @ 08:58:08


    Hallo Sascha,
    Super!

    Ich mag's immer ein wenig detailierter - entweder mit SHOWPLAN_ALL oder STATISTICS XML!

    USE AdventureWorksDW2008;
    GO
    SET SHOWPLAN_ALL ON;
    GO
    select SUM(SalesAmount), COUNT(*)
    from dbo.FactInternetSales fis
    left join dbo.DimCustomer dc on dc.CustomerKey = fis.CustomerKey
    where dc.YearlyIncome > 50000;
    GO
    SET SHOWPLAN_ALL OFF;
    GO

    Ich wünsche Dir einen schönen Tag,

    tosc

  • *****
    Kommentar von: Christoph Muthmann
    06.07.10 @ 12:35:19

    Als einen weiteren Weg der Optimierung würde ich berechnete Spalten empfehlen:
    http://www.insidesql.org/beitraege/entwicklung/berechnete-spalten

    Sicher eine Unterform des durchdachten SQLs.
    Gruß
    Christoph

  • *****
    Kommentar von: Frank Kalis
    06.07.10 @ 21:49:48

    Hier ist noch ein weiterer interessanter Link zu logical & physical IO: http://www.sqlservercentral.com/Forums/Topic874159-360-1.aspx

  • *****
    Kommentar von: Torsten Schuessler
    07.07.10 @ 07:58:32

    ich will nicht sagen, das dieser Link interessanter als die o.g. ist - dafür ist er älter :-)

    http://manuals.sybase.com/onlinebooks/group-as/asg1250e/ptallbk/@ebt-link;pt=47;lang=de?target=%25N%15_52422_START_RESTART_N%25

  • *****
    Kommentar von: Christoph Muthmann
    07.07.10 @ 08:37:41

    Wäre es nicht mal Zeit für eine TOP 5/10 der besten Optimierungen für durchdachte SQL Abfragen?
    Mir kämen da auch noch CTE in den Sinn!

  • Sascha Lorenz
    Kommentar von: Sascha Lorenz
    13.07.10 @ 12:11:06

    Moin Jungs!
    Dann lasst uns doch mal community-weit die "besten" Optimierungstipps einer SQL Query zusammentragen! Neben einer TOP Liste macht da sicherlich auch eine "are evil" Sinn! :-)

Einen Kommentar hinterlassen

Ihre E-Mail-Adresse wird nicht auf dieser Seite angezeigt.
SchlechtExzellent
(Zeilenumbrüche werden zu <br />)
(For my next comment on this site)
(Allow users to contact me through a message form -- Your email will not be revealed!)
Dies ist ein Captcha Bild. Es wird benutzt, um Massenzugriffe von Robotern zu verhindern.
Bitte gib die Zeichen des obigen Bildes ein. (Groß/Kleinschreibung ist wichtig)