Advanced SQL Server Performance Troubleshooting Workshop in Wien

Für alle die schon immer in die Tiefen von SQL Server Tuning und Troubleshooting eintauchen wollten veranstaltet Klaus Aschenbrenner im September (26.-28.) einen dreitägigen “Advanced SQL Server Performance Troubleshooting Workshop” in Wien.

Details, Kosten und Anmeldung: http://www.csharp.at/Registration_AT.aspx

Martin Pöckl
martin.poeckl@microsoft.com

Nächstes Treffen 26.05.: “Partitionieren von Tabellen anhand eines Praxisbeispiels”

Vielen Dank für euere Themenvorschläge!! Die Gewinner der T-Shirts bzw. der Tassen werden von uns schriftlich benachrichtigt.

Wir haben uns für das Thema: “Partitionieren von Tabellen anhand eines Praxisbeispiels” entschieden. Speaker wird Georg Gamsjäger sein.

Agenda:
– Ausgangssituation
– Grundlagen der Partitionierung im Überblick
– Warum ist die Partitionierung in diesem Beispiel so ideal
– Performanceverbesserungen feststellen
– Automatisierte Verwaltung der Partition

Treffpunkt ist wie immer um 18:00 bei Microsoft Österreich, Euro Platz 3, 1120 Wien! Anmeldung bitte unter sqlserverug@live.at

Wir freuen uns schon auf Euer Kommen!

Substring vs. LIKE

Wenn eine TSQL-Aufgabe in der WHERE Klausel einer Abfrage mit der SUBSTRING Funktion oder dem LIKE Operator implementiert werden kann, soll man sich für die Variante mit dem LIKE Operator entscheiden. Entweder ist diese Option besser oder beide Varianten performieren gleich gut.

Eine SUBSTRING Variante ist nie besser als der LIKE-Operator. Deshalb wird es empfohlen (aufgrund der Performance), den LIKE Operator zu verwenden. Außerdem, ist die SUBSTRING-Option fehlerfreundlich und nicht intuitiv.

Das ist eine Ableitung einer generellen Empfehlung, dass ein Funktionsaufruf in der WHERE Klausel, wo eine Tabellenspalte als Funktionsargument verwendet wird, durch einen entsprechenden Operator ersetzt werden soll.

Code-Beispiel

Als Beispiel nehmen wir die Tabelle Person.Contact aus der AdventureWorks Datenbank. Falls ein Index auf die Spalte LastName nicht existiert, legen wir einen an:

IF NOT EXISTS(SELECT 1 FROM sysindexes WHERE name='IX_Contact_LastName')
CREATE NONCLUSTERED INDEX IX_Contact_LastName ON Person.Contact (LastName)

Jetzt sind wir sicher, dass der entsprechende Index existiert und wir können zwei Varianten einer Abfrage probieren.

Abfrage 1 (low selectivity)

Zunächst holen wir alle Kontakten mit Nachnamen, die mit A anfangen. Hier sind die Abfragen:

SELECT * FROM Person.Contact WHERE SUBSTRING(LastName, 1, 1)='A'
GO
SELECT * FROM Person.Contact WHERE LastName LIKE 'A%'

Werfen wir nun einen Blick auf die Ausführungsinformationen:

Beide Abfragen sind logisch äquivalent und liefern 911 Zeilen. Das sind ungefähr 4,5% aller Zeilen, aber mit allen Spalten. Deshalb hat der SQL Server geschätzt, dass die Verwendung des Indizes IX_Contact_LastName und entsprechende Key-Lookups teuer sind und sich für das Scannen der ganzen Tabelle entschieden. Die Abfrage ist nicht genug „selektiv“ für eine Indexverwendung und aus diesem Grund ist es egal, wie die WHERE Klausel implementiert ist.

Abfrage 2 (high selectivity)

Wiederholen wir jetzt diese Aufgabe mit der Änderung, dass der Nachname mit Acho beginnen soll. Hier sind die biden angepassten Abfragen:

SELECT * FROM Person.Contact WHERE SUBSTRING(LastName, 1, 4)='Acho'
GO
SELECT * FROM Person.Contact WHERE LastName LIKE 'Acho%'

Selbstverständlich sind die Abfragen wieder logisch äquivalent. Diesmal liefern sie nur eine Zeile zurück – diese Abfrage ist sehr „selektiv“. Das ist ein Zeichen für den SQL Server Optimizer, dass eine Indexverwendung sinnvoll wäre. Er hat entschieden den Index IX_Contact_LastName zu verwenden. Betrachten wir die Ausführungsdetails:

Wir sehen, dass in beiden Fällen der Index verwendet wird, aber nur die LIKE-Variante verwendet den Index-Seek Operator. In der ersten Abfrage wird die SUBSTRING Funktion für jede äußere Zeile evaluiert und deshalb musste der SQL Server Optimizer den Index-Scan Operator verwenden.

Die LIKE-Variante limitiert den Optimizer nicht und er konnte den besten Plan generieren. Nur fünf Seiten hat der SQL Server lesen müssen, um das Ergebnis liefern zu können. In der ersten Variante waren 60 Seiten dafür notwendig.

Fazit

Verwenden Sie immer LIKE anstatt SUBSTRING. Eine Abfrage mit dem SUBSTRING-Funktionsaufruf performiert nie besser als eine logisch äquivalente Abfrage mit dem LIKE-Operator.

IN vs. BETWEEN

Wenn eine TSQL-Aufgabe in der WHERE Klausel einer Abfrage mit den IN oder BETWEEN Operator implementiert werden kann, soll man sich für die Variante mit dem BETWEEN Operator entscheiden.

Entweder ist diese Option besser oder beide Varianten performieren gleich gut. Der Unterschied ist nicht so groß, aber der Vorteil liegt auf der Seite des BETWEEN-Operators.

Code-Beispiel

Als Beispiel nehmen wir die AdventureWorks Datenbank und die Tabelle SalesOrderHeader aus dem Schema Sales. Wir sollen Order-Details für die Order 43663 und 43664 zurückliefern. Hier sind zwei Abfragen, die jeweils eine Liste mit diesen zwei Orders generieren:

SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID IN (43663,43664)
GO
SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID >= 43663 AND SalesOrderID <= 43664

Die Ausführungspläne sind identisch:

In den Statistikinformationen sieht man aber, dass in der ersten Abfrage sechs, in der zweiten hingegen nur drei logical reads gemacht wurden:

Analyse

In der Statistik für die erste Abfrage können wir sehen, dass die Clustered Index Seek-Operation zweimal (Scan count 2) durchgeführt wurde. Warum zweimal? Weil der Optimizer eine IN Abfrage in eine entsprechende OR Anfrage konvertiert hat. Eine Index Seek-Operation wird für jedes OR-Teil durchgeführt. Schauen wir das genauer an:

SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID IN (43663,43664)
GO
SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID=43663 OR SalesOrderID=43664

Mit Hilfe der Ausführungsinformationen können wir herausfinden, dass beide Abfragen mit dem absolut identischen Ausführungsplan und derselben Anzahl an gelesenen Seiten beendet worden sind.

Fazit

Der BETWEEN oder Range-Operator ist ein bisschen besser als IN. Sehr oft spielt dieser Unterschied keine Rolle, aber es ist nicht schlecht zu wissen, wie der Optimizer mit dem IN Operator umgeht. Außerdem gibt es Situationen, wo jeder Leservorgang wichtig ist und dort kann dieser Unterschied sinnvoll sein

Unique-Constraints und Performance

Ein Unique-Constraint wird in einer Tabelle definiert, wenn in einer Spalte nur eindeutige Werte (inklusive NULL) möglich sind. Das ist der einzige Constraint, der durch eine Index-Erstellung umgesetzt wird. Um einen Unique-Constraint zu erstellen, muss man einen UNIQUE Index anlegen. Constraints sind in erster Linie eine Garantie für Datenintegrität. Man vergisst aber oft, dass diese auch den SQL Server Optimizer bei der Ausführungsplan-Generierung unterstützen.

Code-Beispiel

Als Beispiel erstellen wir eine Tabelle (dbo.OrderTest). Die Tabelle hat nur drei Spalten, die für unser Problem ausreichen:

--Tabelle OrderTest
IF OBJECT_ID('dbo.OrderTest') IS NOT NULL DROP TABLE dbo.OrderTest
GO
CREATE TABLE dbo.OrderTest(
order_id int NOT NULL,
order_date date NOT NULL,
status_id tinyint NOT NULL
CONSTRAINT PK_OrderTest PRIMARY KEY CLUSTERED(order_id)
)
GO

Mit diesem Skript füllen wir die Tabelle mit Order-Details. Nach der Ausführung befinden sich in der Tabelle eine Million Orders, mit dem Datum zwischen dem 1.1.2007 und 31.12.2010 und einer Zahl zwischen 1 und 5 als Status ID.

Aufgabe

Unsere Aufgabe ist einfach: Alle Order-Details anzeigen, wobei die Status ID durch die entsprechenden Bezeichnung ersetzt werden soll.

Diese zusätzliche Anforderung wird mit einer sogenannten Lookup-Tabelle umgesetzt. Erstellen wir nun die Lookup-Tabelle und füllen wir sie mit fünf Einträgen:

--Tabelle StatusLookup anlegen
IF OBJECT_ID('dbo.StatusLookup') IS NOT NULL
DROP TABLE dbo.StatusLookup
GO
CREATE TABLE dbo.StatusLookup(
id tinyint not null,
text varchar(20) not null)
GO
--Tabelle StatusLookup füllen
INSERT INTO dbo.StatusLookup(id, text)
VALUES(1, 'eingelangt'),(2, 'in Bearbeitung'),(3, 'Rückgabe'),(4, 'geschlossen'),(5, 'gelöscht')
GO

Die Tabelle dbo.StatusLookup ist eine ganz einfache Tabelle mit nur fünf Einträgen. Deshalb ist es üblich, dass eine solche Tabelle als Heap Tabelle (ohne Clustered Index) umgesetzt wird.

Ergebnis

Jetzt haben wir beide Tabellen und können die gewünschte Abfrage erstellen:


SELECT o.order_id,o.order_date,
(SELECT s.text FROM dbo.StatusLookup s WHERE s.id=o.status_id) status
FROM dbo.OrderTest o

Wir listen die ID und das Datum des Orders aus und fügen die Statusbezeichnung mit einer Subquery hinzu. Auf meinem Notebook ist die Ausführungszeit für diese Abfrage 10.9 Sekunden. Hier sind die Ausführungsinformationen bzw. der Ausführungsplan:

Ergebnisanalyse

Was kann man hier sehen? Die Tabelle dbo.StatusLookup wurde 1.000.000 mal gescannt! Auf jede Zeile aus der OrdeTest Tabelle wurden drei Operatoren angewandt (Table Scan, Stream Aggregate und Assert). Warum?

Eine Subquery kann nur einen skalaren Wert liefern. Deshalb muss der SQL Server überprüfen, ob die innere Abfrage für jede äußere Zeile nur einen Datensatz zurückliefert. Falls es mehrere Datensätze gibt, wird ein Fehler ausgelöst. Mit den Operatoren Table Scan und Stream Aggregate wird die Anzahl von inneren Datensätzen festgelegt und dann der Assert-Operator überprüft dann, ob diese Anzahl größer als 1 ist. Wie gesagt, wird das für jede Zeile durchgeführt – d.h. 1 Mio Ausführungen!

Diese Überprüfung limitiert den SQL Optimizer bei der Auswahl der Join-Strategie und somit bleibt ihm nur Nested Loops Join als einzige Option. Diese Option ist aber ineffizient, wenn die äußere Tabelle zu groß ist. Die Tabelle dbo.StatusLookup enthält nur fünf Zeilen, ohne ID-Duplikate. Aber Duplikate sind nicht explizit verboten. Daher muss immer überprüft werden, ob es zwei Zeilen mit gleicher ID gibt. Bei der Tabellenerstellung haben wir nicht gesagt, dass ein Duplikat nicht möglich ist.

So, was können wir hier tun? Dem SQL Server Optimizer sagen, dass er diese Überprüfung nicht machen muss! Das sagen wir mit einem Unique Constraint. Erstellen wir noch eine Lookup-Tabelle:

Unique Constraint als Performanceverbesserungsfaktor

IF OBJECT_ID('dbo.StatusLookup2') IS NOT NULL DROP TABLE dbo.StatusLookup2
GO
CREATE TABLE dbo.StatusLookup2(
id tinyint not null,
text varchar(20) not null)
GO
CREATE UNIQUE INDEX ix_StatusLookup2_id on dbo.StatusLookup2(id)
go
INSERT INTO dbo.StatusLookup2(id, text)
VALUES(1, 'eingelangt'),(2, 'in Bearbeitung'),(3, 'Rückgabe'),(4, 'geschlossen'),(5, 'gelöscht')
GO

Diesmal haben wir einen Unique Index hinzugefügt und damit die Werte für die Spalte id beschränkt. Führen wir unsere Abfrage erneut aus – diesmal mit der neuen Lookup-Tabelle:

SELECT o.order_id,o.order_date,
(SELECT s.text FROM dbo.StatusLookup2 s WHERE s.id=o.status_id) status
FROM dbo.OrderTest o

Schauen wir jetzt die Ausführungsinfo an:

Analyse

Die Ausführungszeit ist um 2 Sekunden reduziert und die CPU Zeit von 9.079 auf 749 ms! Anhand des Plans können wir sehen, dass der Optimizer aufgrund des hinzugefügten Unique Constraint nicht mehr Duplikatten in der Lookup-Tabelle überprüft und dass er finden konnte, dass diese Subquery-Abfrage äquivalent zu einem Left Join ist. Deshalb hat er einen optimalen Plan generiert, bei dem die Tabelle dbo.StatusLookup2 nur einmal gelesen werden musste! Wenn wir diese zwei Pläne im SQL Server Management Studio direkt miteinander vergleichen, sehen wir, dass die Version mit dem Unique Constraint nur 8% der gesamten Ressourcen verwendet – d.h. ganz grob gesagt ist dieser Plan mehr als 10 mal besser als die Nested Loop Join Variante.

So, eine Kleinigkeit, die man oft vergißt, da Lookup-Tabellen klein sind und Indizierung dort keine Rolle spielt.

Nun kann man sich fragen, warum die Ausführungszeit mit dem zweiten Plan “nur” zwei Sekunden besser ist, wenn der Plan selbst von SSMS als 10-mal besser bezeichnet wurde?

Abfrageperformance Vergleichen

Die Ergebnisliste wird zunächst generiert und dann noch dem Client (SSMS) gesendet. Die gesamte Zeit ist in diesem Fall keine gute Auswahl für einen Performance-Vergleich, da dem Client 1 Mio Zeilen_gesendet werden sollen. Wenn wir hingegen die CPU-Zeiten vergleichen (9.079 ms vs. 749 ms), bekommen wir ein realistisches Bild. Wenn wir aber die zweite Zeitkomponente annullieren möchten, können wir die Abfragen im SSMS so ausführen, dass die Ergebnisse nicht dem Client gesendet werden. Das können wir mit der Option “Discards results after execution” im Screen “Query/Query Options/Results/Grid” einstellen.

Wenn wir beide Abfragen mit dieser Option ausführen, bekommen wir für die Ausführungszeit 3.613 ms bzw. 573 ms.

Fazit

Eine Lookup-Tabelle kann freilich als Heap-Tabelle implementiert werden, aber dann darf nicht auf ein Unique Constraint auf die ID-Spalte vergessen werden. Und ganz generell: Den SQL Server mit allen relevanten Informationen füttern! Richtige und relevante Informationen können nur zur Generierung eines besseren Ausführungplans führen!

Den Beispiel-Code können Sie hier herunterladen.



SQL Server 2008 R2 Licensing Quick Reference Guide Update: Version 1.1

Für alle SQL Spezialisten die sich nicht nur mit der Technologie sondern auch mit der Lizenzierung beschäftigen wollen oder müssen Zwinkerndes Smiley gibt es einen schnellen, aber fundierten Einstieg in die SQL Server 2008 R2 Lizenzierung (auch in virtuellen Umgebungen!). Natürlich auch wunderbar zur Nachlese geeignet: der SQL Server Licensing Quick Reference Guide. Der Guide wurde jetzt aktualisiert und unter anderem um die SQL Server Parallel Data Warehouse Edition sowie um weitere Infos ergänzt.

Ein Auszug aus dem Inhalt:

image

Download: SQL Server 2008 R2 Licensing Quick Reference Guide – Microsoft

Happy licensing, Martin
martin.poeckl@microsoft.com

Check-Constraints und Performance

Ein CHECK-Constraint wird in einer Tabelle definiert, wenn in einer Spalte nur bestimmte Werte möglich sind. Er ermöglicht, einen beliebigen Ausdruck anzugeben, den jeder Wert einer Spalte erfüllen muss. Constraints sind in erster Linie eine Garantie für Datenintegrität. Man vergisst aber oft, dass Informationen aus den Constraints auch die Entscheidungen des SQL Server Optimizers beeinflussen können.

Code-Beispiel

Als Beispiel nehmen wir die Tabelle Sales.SalesOrderHeader aus der AdventureWorks Datenbank. In dieser Tabelle soll ein CHECK-Constraint definiert sein. Wenn dies in Ihrer Datenbank noch nicht der Fall ist, können Sie ihn mit dem folgenden Code anlegen:

ALTER TABLE Sales.SalesOrderHeader WITH CHECK ADD CONSTRAINT CK_SalesOrderHeader_Status CHECK ((Status>=(0) AND Status<=(8)))

Der Constraint beschränkt die Werte für die Spalte Status auf eine Zahl zwischen 0 und 8. Und was hat diese Information mit dem Optimizer zu tun?

Aufgabe

In der Tabelle Sales.SalesOrderHeader gibt es keine Zeile mit dem Wert 8 für die Spalte Status. Wenn wir eine Abfrage ausführen, um alle Orders mit dem Status 8 anzuzeigen, bekommen wir eine leere Liste:

SELECT * FROM Sales.SalesOrderHeader WHERE Status = 8

Um diese leere Liste liefern zu können, musste SQL Server die ganze Tabelle lesen. Jede Zeile könnte den Wert 8 haben und SQL Server hat alle Zeilen überprüfen müssen. Hier sind die Ausführungsinformationen:

Ausführungsplan

Wiederholen wir jetzt diese Aufgabe mit einem Wert, der außerhalb des definierten Bereichs liegt:

SELECT * FROM Sales.SalesOrderHeader WHERE Status = 9

Selbstverständlich ist die Liste wieder leer, die Ausführungsinformationen sehen aber komisch aus:

Keine einzige Datenseite wurde gelesen! Aufgrund von der CHECK-Constraint Definition hat der SQL Server beschlossen, dass keine Zeile in der Tabelle SalesOrderHeader mit dem Status 9 existieren kann und hat das Ergebnis zurückgeliefert, ohne die Tabelle berühren zu müssen!

Fazit

Der SQL Server Optimizer liest die Informationen aus Constraints-Definitionen. Diese Informationen können die Ausführungsplan-Generierung beeinflussen.

SolidQ Journal

Liebe Gruppenmitglieder!

Ich würde Sie die neue Ausgabe des SolidQ Journals präsentiereren. Die ist zum Online-Lesen und Download auf http://www.solidq.com/sqj verfügbar. Für diese ist lediglich eine einfache, kostenlose Registrierung erforderlich. Autoren sind sowohl die global agierenden Datenbankexperten von SolidQ als auch weitere hervorragende Mitglieder der SQL Community. Hier ein Überblick über die Artikel der aktuellen Ausgabe:

Integration Services Key Feature in BI Projects (Part 2)
by Salvador Ramos

PowerShell with SQL Server Data Mining
by Mark Tabladillo

Logging with SQL Server Agent
by Herbert Albert, Gianluca Hotz

Parameter Sniffing with SQL Server Stored Procedures
by Milos Radivojevic

Encryption in SQL Server
by Dr. Greg Low

Configuring SharePoint 2010 Forms Based Authentication
by Guillermo Bas

Danke und viel Spaß beim Lesen!

LG,
Milos

SSMA: Migration von Oracle/Sybase/MySQL und Access auf SQL Server

Der bewährte SSMA (SQL Server Migration Wizard) wurde ein weiteres mal verbessert und erweitert und liegt jetzt in der Version 5 vor. Der SSMA unterstützt und erleichtert erheblich die Datenbankmigration von Oracle, Sybase, MySQL oder Microsoft Access auf Microsoft SQL Server. Der kostenfreie Assistent verfügt dazu über ausführliche Analyse-, Migrations-, und Testfunktionalitäten die sowohl die Datenbank Struktur (Tabellen, Views, Stored Procedures, usw.) sowie die Daten analysieren und migrieren können.

SQL Server Migration Assistant (SSMA) v5.0 is now available. SSMA simplifies database migration process from Oracle/Sybase/MySQL and Microsoft Access to SQL Server and SQL Azure. SSMA automates all aspects of migration including migration assessment analysis, schema and SQL statement conversion, data migration as well as migration testing to reduce cost and reduce risk of your database migration project.

Die Erweiterungen im Detail:

  • Support for migration to SQL Server “Denali”
  • Multi-thread data migration for improved scale and performance.
  • Globalization support for migrating non-English databases to SQL Server.
  • Support for installation for SSMA Extension Pack on clustered SQL Server environments.
  • Sybase migration enhancement support:
    • Support for migration to SQL Azure
    • Extended data access connectivity to Sybase ASE ADO.NET and ASE ODBC providers
    • Support for proper conversion of case sensitive Sybase database to case sensitive SQL Server
    • Extended support for conversion of Non-ANSI joins for DELETE and UPDATE statements
    • Removed dependency on separate SYSDB database on target SQL Server
  • Report use of Oracle’s User Defined Type in the database object and inside PL/SQL.

Weitere Infos und Download des SSMA: http://www.microsoft.com/sqlserver/en/us/product-info/migration.aspx

Martin
martin.poeckl@microsoft.com