Datenbanken und mehr

19. Juli 2007

Noch mehr Statistik – noch mehr Performance

Gespeichert unter: Performance — Urs Gehrig @ 22:55

Im Blogg von dieser Woche habe ich ja bereits ein paar Tipps zu Performance Steigerung mittels Index Statistiken gegeben – heute gehe ich noch einen Schritt weiter. Statistiken zu erstellen kann eine ganz aufwendige Sache sein, insbesondere wenn die Tabelle Millionen und mehr von Zeilen hat. Dies ist auch der Grund, warum der SQL Server per Default das Histogramm lediglich auf einem Subset aller Zeilen (Sample) bildet. Die Grösse des Samples bestimmt der SQL Server selbständig. Wenn du denkst, dass du smarter als der SQL Server bist, kannst du aber auch hier wiederum von Hand eingreifen:

UPDATE STATISTICS Person.Address IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode WITH SAMPLE 1 PERCENT
UPDATE
STATISTICS Person.Address IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode WITH SAMPLE 1000 ROWS
UPDATE
STATISTICS Person.Address IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode WITH FULLSCAN

FULLSCAN entspricht dabei einem SAMPLE 100 PERCENT.

Wie gesagt, ein Update der Statistik kann ganz schön lange dauern. Ein Query, das auf eine out-of-date Statistik stösst, muss warten bis diese Statistik aktualisiert wurde; erst dann kann das Query compiliert und ein Resultset zurückgegeben werden. Das kann zu unvorhersehbar langen Antwortzeiten führen, welchen auf Clientseite schon mal zu einem Timeout führen können. Um dem zu entgehen, kennt der SQL Server die DB Option, die Statistik asynchron zu aktualisieren:

ALTER DATABASE AdventureWorks
  SET AUTO_UPDATE_STATISTICS_ASYNC ON

In diesem Falle wir die Statistik im Hintergrund aktualisiert. Queries welche in der Zwischenzeit diese Statistik benötigen werden nicht blockiert sondern verwenden die veraltete Statistik.

Wäre es nicht toll, wenn dir der SQL Server erzählen würde, welche Indizes er gerne hätte um noch effizienter arbeiten zu können? Genau hierfür gibt es eine Dynamic Management View:

SELECT object_name(object_id, database_id) as ‘Object’
      ,equality_columns
      ,inequality_columns
      ,included_columns
FROM sys.dm_db_missing_index_details

Das Resultat kann etwa wie folgt aussehen:

Object equality_columns inequality_columns included_columns
Address [ModifiedDate] NULL NULL

Mit dieser Information kannst du deinen fehlenden Index leicht bilden:

CREATE INDEX NewIndex
 
ON <Object>(<equality_columns>,<inequality_columns>)
  INCLUDE (<included_columns>)
  WITH DROP_EXISTING

Oder konkret:

CREATE INDEX Adress_ModDate_Index
  ON Address(ModifiedDate)
  WITH DROP_EXISTING

Aber Achtung: Diese Daten sind nur solange durch die Dynamic Management View verfügbar, bis du den SQL Server neu startest oder aber an der zugrunde liegenden Tabelle eine Schema Änderung vornimmst.

18. Juli 2007

Mit aktuellen Statistiken zu mehr Performance

Gespeichert unter: Performance — Urs Gehrig @ 23:45

Nein, natürlich meine ich mit Statistiken nicht etwa das Statistische Jahrbuch der Schweiz, sondern viel mehr SQL Servers eigene Index Statistik. Index Statistiken beschreiben die Verteilung von Werten in einer Spalte. Der Query Optimizer nutzt diese statistischen Daten um den optimalen Query Plan zu bestimmen, in dem er die Zugriffskosten für die Benutzung eines bestimmten Index schätzt. Mit Hilfe von DBCC kannst du dir zum Beispiel das Histogramm der Verteilung der Werte anschauen:

DBCC SHOW_STATISTICS (‘Person.Address’,‘IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode’) WITH HISTOGRAM

Du kannst dir das Histogramm natürlich auch mit Excel grafisch darstellen lassen; das sieht dann so aus:

Wann immer du dir einen Index anlegst, legt SQL Server parallel dazu auch eine entsprechende Statistik an. Wenn sich die dem Index zugrunde liegende Daten ändern, kann sich natürlich auch das Histogramm dazu ändern. Dies kann dazu führen, dass der Query Optimizer lediglich einen Suboptimalen Query Plan erstellt, was selbstreden auf die Performance geht. SQL Server wird daher die Statistik automatisch updaten, d.h. neu berechnen, wann immer sich 20% der Zeilen – aber mindestens 500 Zeilen – der entsprechenden Tabelle geändert haben. Ob die Statistik noch aktuell ist, siehst du zum Beispiel im Actual Execution Plan: wenn Actual Number of Rows stark von Estimated Number of Rowas abweicht, dann dürfte die Statistik nicht mehr viel wert sein. In diesem Falle kannst du den Update der Statistik forcieren:

UPDATE STATISTICS Person.Address

Da der SQL Server die Statistiken sowohl selber anlegt als auch selber up-to-date hält, sollte in der Regel das Histogramm aber kaum je unbrauchbar sein – wenn da nicht noch all diese Optionen wären. Sowohl das automatische Anlegen als auch Updaten der Statistiken kannst du nämlich unterbinden. Das kann mal für grosse BULK Operationen interessant sein; sollte aber wirklich die ganz grosse Ausnahme sein. Mit folgenden Kommandos kannst du in das Verhalten der automatischen Statistik eingreifen:

  • sp_autostats: Tabllen und Index weites ein-/ausschalten von AUTO UPDATE
  • STATISTICS_NORECOMPUTE Klausel in einem CREATE INDEX Statement
  • NORECOMPUTE Klausel in einem UPDATE STATISTICS oder CREATE STATISTICS Statement
  • AUTO_CREATE_STATISTICS und AUTO_UPDATE_STATISTICS Datenbank Option (änderbar via ALTER DATABASE)

Nochmals: Überlege es dir gut, ob du wirklich einzelnen Statistiken nicht automatisch up-to-date halten willst oder für einzelne Indizes überhaupt gar keine Statistik willst. Was in Ausnahmefällen mal für eine Statistik gelten kann wird aber kaum je generell für alle Statistiken gelten. Also Hände weg von den Datenbank weiten Optionen zur Ausschaltung der automatischen Statistiken! Die Performance wird es dir danken!

6. Juli 2007

Wie komme ich an meine AS400 Daten ran?

Gespeichert unter: SQL Server — Urs Gehrig @ 08:21

Wenn du von deinem SQL Server aus auf fremde Daten zugreifen möchtest, hast du prinzipiell zwei Möglichkeiten: entweder setzt du einen Linked Server auf oder aber du verwendest ein OPENROWSET Query. In beiden Fällen benötigst du aber selbstverständlich einen Daten Provider für das Fremdsystem – da bildet auch AS400 mit seinem DB2 keine Ausnahme. Bisweilen warst du hierfür immer auf Produkte von Drittfirmen angewiesen, wie zum Beispiel auf IBM mit iSeries Access for Windows OLE DB Provider oder aber du hast teure Produkte von Microsoft erworben, wie zum Beispiel Host Integration Server 2000.

Das hat nun endlich ein Ende. Mit dem Feature Pack für Microsoft SQL Server 2005 – Februar 2007 bietet Microsoft nun einen vollständigen OLE DB Provider für DB2 an und dies erst noch kostenlos. Da dieser OLE DB Provider (DB2OLEDB) als eigenständiges Installationspackage daher kommt, ist es erst noch kompakt (rund 8 MB).

Die Installation von DB2OLEDB ist kinderleicht. Erst einmal installiert bekommst du eine neue Programmgruppe Microsoft OLE DB Provider for DB2. Nebst einer recht ausführlichen Dokumentation findest du dort auch noch ein Data Access Tool zum konfigurieren und testen von Datenquellen und ein SNA Trace Utility zum aufzeichnen des Netzwerkverkehrs für die Fehlersuche bei Verbindungsproblemen.

Da du nun einen funktionierenden OLE DB Provider hast, kannst du jetzt auch einen Linked Server innerhalb des SQL Servers aufsetzen. Wie du das machst findest du ausführlich in Microsofts Knowledge Base Artikel KB222937. Die wichtigsten Dinge habe ich dir hier zusammengefasst:

Erstellen des Linked Servers:

EXEC sp_addlinkedserver 
@server=WNW3XX’
@srvproduct=‘Microsoft OLE DB Provider for DB2′
@catalog=‘OLYMPIA’
@provider=‘DB2OLEDB’
@provstr=‘NetLib=SNA;NetAddr=;NetPort=;RemoteLU=OLYMPIA;LocalLU=LOCAL;ModeName=QPCSUPP;User ID=WNW3XX;Password=WNW3XX;InitCat=OLYMPIA;Default Schema=WNW3XX;PkgCol=WNW3XX;TPName=;Commit=YES;IsoLvl=NC;AccMode=;CCSID=37;PCCodePage=1252;BinAsChar=NO;Data Source=Olympia_WNW3XX’

EXEC sp_addlinkedsrvlogin ‘WNW3XX’, false, NULL, ‘WNW3XX’, ‘WNW3XX’

Und ein paar Beispiele von Verteilten Abfragen:

–Example of SELECT using 4-part name: LinkedServer.Catalog.Schema.Table
SELECT *
FROM WNW3XX.OLYMPIA.WNW3XX.DEPARTMENT

–Example of Pass Through SELECT using OPENQUERY with 3-part name:
SELECT *
FROM OPENQUERY(WNW3XX,„SELECT * FROM OLYMPIA.WNW3XX.EMP_ACT“)

–Example of Pass Through SELECT using OPENROWSET with 2-part name:
SELECT *
FROM OPENROWSET
(‘DB2OLEDB’,‘Netlib=SNA;NetAddr=;NetPort=;RemoteLU=OLYMPIA;LocalLU=LOCAL;ModeName=QPCSUPP;User ID=WNW3XX;Password=WNW3XX;InitCat=OLYMPIA;Default Schema=WNW3XX;PkgCol=WNW3XX;TPName=;Commit=YES;IsoLvl=NC;AccMode=;CCSID=37;PCCodePage=1252;BinAsChar=NO;Data Source=Sample’,
‘SELECT * FROM WNW3XX.EMPLOYEE’)

–Example of an INSERT using 4-part name:
INSERT INTO WNW3XX.OLYMPIA.WNW3XX.DEPARTMENT VALUES (‘E21′,‘DUMMY’,NULL,‘E01′)

Ach ja, das Erstellen des Connection Strings von Hand kann manchmal schon echt zeitraubend sein; aber auch hierfür habe ich dir einen kleinen Tipp für deine Trickkiste. Erstelle auf deinem Desktop ein neues, leeres File und gib diesem die Endung UDL. Doppelklicke auf das File und Microsofts Data Link Wizard wird gestartet. Wähle dort deinen Provider, konfiguriere deine Data Source und beende anschliessend den Wizard wieder. Wenn du jetzt dieses File mit Notepad öffnest hat du deinen Connection String, den du so direkt weiter verwenden kannst. Viel Glück mit deinen AS400 Daten.

3. Juli 2007

Unzulänglicher FTP-Task im SSIS

Gespeichert unter: SSIS — Urs Gehrig @ 07:06

In einem meiner letzten Consulting Einsätze bin ich auf eine ganz besonders harte Nuss gestossen; dank den vielfältigen Tasks des SSIS habe ich dennoch einen valablen Workaround gefunden. Mit dem folgenden kleinen Beispiel möchte ich dir aufzeigen, wie vielfältig SSIS ist und dir gleichzeitig Mut machen nicht zu rasch den Pickel hinzuwerfen. Also los geht’s…

Die Aufgabe bestand im herunterladen eines Files von einem FTP-Server und der nachfolgenden Bearbeitung dieses Files – sofern überhaupt eines vorhanden ist. Leider ist der Filename unbekannt, da ein Verarbeitungsdatum die Extension des Filenamen bildet. Ein fehlen eines Files bedeutet keinen Fehler, ja ist sogar der Normalfall. Das ganze SSIS Package wird von einer Applikation via SMO angestossen. Die naheliegenste Lösung für das Packages beinhaltet wohl die Verwendung eines FTP Tasks. Im Kurzen würde dies in etwa wie folgt aussehen:

Das Propertie RemotePath des FTP-Tasks Download Ticket erhält den Wert myFile.* und das Propertie Operations den Wert Receive files. Der Foreach Loop Container ist ein Foreach File Enumerator. Auf den ersten Blick funktioniert dieser Ansatz gang ordentlich: das File wir heruntergeladen und vom Foreach File Enumerator im temporären Directory gefunden und verarbeitet. Das Problem kommt aber, wenn durch den FTP-Task kein File herunter geladen wird, weil keines vorhanden ist. In diesem Fall schlägt nämlich der FTP-Task fehl und das ganze Package wird mit einem Fehler abgebrochen – was ja in unserem Falle nicht passieren darf.

Erster Workaround: Das Propertie ForceExecutionResult des FTP-Tasks wird fix auf Success gesetzt. Damit wird der Control Flow des Packages auch weiter geführt, wenn kein File vom FTP Server kopiert wurde; also genau das was wir wollen. Pech gehabt: Der Foreach File Enumerator kommt zwar wie geplant zum Zuge und funktioniert auch einwandfrei (eine leere Collection bildet hier absolut kein Problem), das ganze Package hat nach der Verarbeitung aber dennoch einen DTSExecResult Wert von Failure.

Zweiter Workaround: In der Applikation, welche das Package startet, wird genau dieser eine Fehlerfall (kein File vom Server heruntergeladen) separat ausgewertet. Das kann in etwa wie folgt aussehen:

// start package
WSResult result = new WSResult(0, „“);

result.ResultCode = (Int32)myPackage.Execute();

// evaluate package result
// ignore FTP Task error in case any files are available!
if ((result.ResultCode == 0) || (myPackage.Errors.Count == 1 && myPackage.Errors[0].Source == „Download Ticket“ && myPackage.Errors[0].ErrorCode == -1073573501))
{
    // everything is OK!

    result.ResultCode == 0;
   

}
else
{
    // package failed
   

}

Fein, das funktioniert ganz gut; wenigstens in der Testumgebung wo der FTP-Server ein Microsoft Server ist (Bestandteil des IIS). Leider sah dies ganz anders aus, als ich diese Lösung in der realen Welt testete. Der Kunde hatte einen UNIX FTP Server und mit diesem kommt der FTP-Task nun ganz und gar nicht zu recht. Das File wird zwar runtergeladen (so sagt es jedenfalls der Task), aber das File ist nirgends zu finden. Ganz ähnlich verhält es sich beim Löschen des Files auf dem FTP Server; das File wird gelöscht (wiederum keine Fehlermeldung), aber das File ist weiterhin auf dem Server. Das kann doch nicht sein? Nochmals: Absolut keine Fehlermeldung vom FTP-Task, weder File nicht gefunden noch Keine Zugriffsberechtigung noch sonst was und trotzdem macht der Task nicht was er soll! Na ja, die Foren im Internet sind voll davon: So ziemlich jeder scheint dasselbe Problem mit dem FTP-Task und einem UNIX FTP Server zu haben L

Dritter Workaround: Wir wechseln den FTP Task durch einen Script Task aus und programmieren das runterladen halt selber in VB.net; schliesslich bietet uns das .NET Framework ja ein Webclient Objekt, welches als FTP Client genutzt werden kann. Soweit so gut; leider kann der Webclient nicht mit Wildcards in Fielnamen umgehen. War wohl wieder nichts.

Vierter Workaround: Wir wechseln den FTP Task durch einen Execute Process Task aus und rufen die Windows Konsolenapplikation FTP auf. Ja das war’s. Diese Lösung funktioniert. Egal ob ein File auf dem Server zum Download bereit liegt oder nicht. Egal ob der FTP Server ein UNIX oder Windows Server ist. Die Lösung funktioniert!

Du siehst also: Probleme gibt es immer und überall. SSIS bietet aber eine so grosse Vielfalt an Tasks, dass du immer einen Workaround für dein Problem finden kannst. Nur nie aufgeben!

Bloggen Sie auf WordPress.com.