Einer meiner Jobs ist die Analyse von Performanceproblemen bei unseren Anwendungen. Deswegen habe ich hier einfach mal ein paar typische Performance-Fallen bei datenbanknutzenden Anwendungen geschrieben. Natürlich ist es schwierig pauschale Ratschläge abzugeben. Es besteht immer die Gefahr platt oder arrogant zu wirken. So ist es nicht gemeint.
Das alles läuft unter dem Motto: Gefahren, die man kennt, kann man umschiffen…
* Nur die wirklich notwendigen Datensätze anfordern
Werden in der Anwendung beispielsweise Cursor verwendet (gerne bei ODBC oder MFC), dann werden oft nur die ersten 30 Datensätze lesen (mehr passt nicht auf den Bildschirm, der Rest wird dann nur im Bedarfsfalls gelesen (z.B. in einem virtuellen List-Control). Je nachdem welchen Cursor-Typ man verwendet und wie komplex das Statement ist, muss der SQL-Server aber dennoch alle vom Datensatz betroffenen Datensätze lesen, z.B. 10000, obwohl in der Regel nur die aktuellesten 20 tatsächlich von Kunden tatsächlich werden. Das gilt häufig dann, wenn der SQL-Server die Datensätze sortiert liefern soll, aber kein entsprechender Index da ist.
* Immer alle benötigen Daten auf einen Schlag anfordern
Ich sehe immer wieder, dass eine Reihe von Datensätzen aus einer Tabelle gelesen wird. Sagen wir mal die Daten einer Rechnung. Dann wird pro Rechnung nachgelesen wie der verantwortliche Sachbearbeiter – von dem ja nur die ID in dem Datensatz steckt – heißt. Dann wird der Name des Kunden (aus dem Gleichen Grund) pro Rechnung nachgelesen. Das sollte man bitte auf keinen Fall machen. Damit belegt man nur, dass der Chef das Geld oder die Zeit für eine SQL-Schulung nicht ausgeben wollte. Und kauft damit eine schlechte Performance ein.
Statt dessen verwendet man einen Join, um die beiden Namen zusammen mit den Rechnungsinformationen zu lesen. Das ist einfach und effizient.
* die Objektorientierung nicht um seiner selbst Willen durchziehen
Leider passiert es sonst regelmäßig, dass die aufgerufene Klasse, die ja meist nicht weiß in welchem Kontext sie gerufen wird, sich unsinnig verhält.
Beispielsweise soll der Status von allen Bestellungen eines Kunden auf "fakturiert" gesetzt werden, wenn die Rechnung dafür raus ging. Die beste Performance bekommt man wenn man nur ein einziges UPDATE dafür absetzt. Wenn man aber (vermeintlich) "objektorientiert" vorgeht, dann werden in einer Schleife alle Bestellungen durch genudelt, und pro Bestellung ein UPDATE abgesetzt. Das kostet unglaublich Performance und ist so als ob man jede Morgen für jedes einzelne Brötchen wieder einzeln zum Bäcker fährt. Dabei könnte man doch mit einer Fuhre alle 10 Brötchen mitbringen.
* Schleifen
Das gilt natürlich nicht nur für die objekt-orientierte Vorgehensweise: Das gilt für alle Programmiersprachen und Architekturen. Deswegen nenne ich es noch als eigenen Punkt: Jede Schleife kostet unnötig Zeit. Mehr Zeit als man denkt.
* keine oder unsinnige Clustered-Indexe
Den Primärschlüssel zum Clustered-Index zu machen ist nach meiner Erfahrung meistens suboptimal und lohnt sich nur bei zusammengesetzten Primärschlüsseln. Wenn man eine ID verwendet, dann sollte man Clustered-Index lieber auf ein oder mehrere fachliche Felder legen, die unter Umständen sogar Fremdschlüssel irgendwohin sind. Dadurch erreicht man, dass fachlich zusammengehörige Felder auf der gleichen oder wenigstens auf benachbarten Datenseiten liegen.
Der Clustered-Index sollte übrigens nicht zu "breit" sein, weil am SQL-Server-2005 die anderen Index nicht mehr auf die Datenseiten verweisen, sondern auf den Eintrag im Clustered-Index…
Früher hatte ich auch noch eine ganze Palette an Tricks zur Mikro-Optimierung auf Lager. Aber der Optimizer des SQL-Servers ist jetzt so clever geworden, dass die meisten zum alten Eisen gehören. Außerdem habe ich echt die Erfahrung gemacht, dass die dicken Brocken so viel Zeit verschlingen, dass sich die Mikrogeschichten (wie z.B. Parametrisierung) erst sehr spät rentieren…