Datenbanken: was ist gutes und was ist schlechtes Datenbankdesign? Überlegungen zu GUIDs als Index

Von Mario Meir-Huber Autor Feed 12. January 2011 10:37

Oft stellt sich die Frage, was ein gutes Datenbankdesign ist und was nicht. O/R-Mapper gehören quasi schon fast zum guten Ton einer jeglichen Anwendung. Die “Schmerzen” welche Joins und dergleichen verwenden werden uns durch O/R Mapper oftmals abgenommen. Was ich häufig sehe und auf den ersten Blick auch sehr gut aussieht ist ein GUID als Index zu verwenden. Dies sieht dann im Quellcode in etwa so aus:

[ActiveRecord]
public abstract class DomainObject : IDomainObject
{
    [PrimaryKey]
    public Guid Id
    {
        get;
        set;
    }
}

HINWEIS: Dieses Domänenobjekt ist – wie an [ActiveRecord] ersichtlich – mit ActiveRecord erstellt.

Einen GUID als Primary Key zu verwenden ist keine Seltenheit – ich mache es selbst sehr oft (wir müssen ja alle skalieren und bei unseren stark frequentierten Anwendungen kann es schon vorkommen das ein “long” oder “int” nicht ausreicht Smiley. Also ist es OK einen GUID zu verwenden?

Überlegen wir uns mal, wie es vom wirtschaftlichen Standpunkt betrachtet aussieht. Was ist am einfachsten zu skalieren, was kostet am wenigsten? Es dürfte klar sein das das günstigste der Plattenspeicher ist. Wo es wirklich teuer wird ist die CPU Last. Daher gilt es, eine Datenbank so zu erstellen dass die CPU-Last minimiert wird.

Ein kleiner Einblick, was GUIDs in der Datenbank bewirken gibt Kimberly L. Tripp in seinem Blog-Post “Disk space is cheap – that is not the Point”. Kimberly hat 3 unterschiedliche Testläufe durchgeführt, die Ergebnisse sind mit GUIDs fatal. Hier der Überblick bei 10.000 Inserts:

Dauer mit Integer als IDs: 17 Sekunden
Dauer mit GUIDs als IDs: 5:07 Minuten.

WOW, ein ziemlich heftiger Unterschied, oder? Befragt man den SQL-Server Experten Klaus Aschenbrenner, so sieht seine Antwort ähnlich aus:

*) Du generierst durch GUIDs random IO auf der Disk
*) Du produzierst unnötigen Speicherplatzverbrauch (GUID ist 16 Bytes lang - ist verdammt lang für einen PK)
*) JOINs sind 4x so teuer, als wenn du eine INT IDENTITY verwendest (16 Bytes vs. 4 Bytes)
*) Data Compression bringt mit GUIDs fast keinen Gewinn
*) Datenbank Backups werden größer
*) Die External Index Fragmentation ist standardmäßig auf ca. 99%, dh. jede Page ist out-of-order
*) Non-Clustered Indizes werden größer und unperformanter

Also spricht einiges gegen GUIDs in diesem Fall.

Nun frag an euch: könnt Ihr diese Behauptungen wiederlegen? Welche Erfahrungen habt Ihr zu diesem Thema gemacht? Postet eure Erfahrungen/Ideen zu diesem Thema!

Links:

Disk space is cheap von Kimberly L. Tripp: http://www.sqlskills.com/BLOGS/KIMBERLY/post/Disk-space-is-cheap.aspx

Blog von Klaus Aschenbrenner: www.csharp.at

Comments (14) -

>

1/12/2011 11:39:57 AM #

Hallo Mario,

Ich hatte auch so einiges an Performance Themen mit GUIDs, vor allem bei Inserts - wie auch oben angeführt.
Nach langem Suchen im Sourcecode und vielen Tests ist fast 99% der Zeit des Imports beim Insert vom Primary Key (Guids) verloren gegangen und das sind bei ein paar 100.000 Datensätze so einiges!
Warum?
Naja weil GUIDs zufällig sind. Daher immer an einer anderen Stelle eingefügt werden und der PK in der DB reorganisiert werden muss. Daher ist auch die angesprochene Page-Fragmentation sehr hoch.

Hierzu gibt es jedoch eine Lösung:
Und zwar indem man sequentielle GUIDs erzeugt. Und die Performance-Probleme (zumindest in diesem Bereich) sind weg. Diese könne im SQL Server mithilfe von NewSequentialID erzeugt werden, oder auch im Sourcecode: dotnet-snippets.de/.../...uential-guid-SID998.aspx

Der große Vorteil von GUIDs ist aus meiner Sicht, dass ich diese im C# Code erzeugen kann und mir keine weitern Gedanken oder Abfragen machen muss, wenn ich nach dem Insert den PK im Source brauche.

Grüße
Bernhard

Bernhard Wurm Österreich

>

1/12/2011 11:56:20 AM #

Hi Bernhard,

ja, im Blog von Kimberly wurden auch die Sequential GUIDs beschrieben - wobei auch hier die Performance schlecht war (im oben genannten Beispiel konnte dies zwar auf 1:13 gesenkt werden aber verglichen mit den 17 Sekunden noch immer VIEL zu langsam).

Mit dem Vorteil aus C# heraus ... das kann ich nachvollziehen. Eine GUID ist einfach viel 'schöner' für einen Softwareentwickler als eine int-ID ;)

lg Mario

Mario Österreich

>

1/12/2011 12:24:44 PM #

Ich hab gerade auf meiner Maschine einen kleinen Test geschrieben. Ich persönlich verwende auch immer GUIDs.

Die beiden Tabellen bestehen aus folgenden Feldern:

Id ist einmal GUID und einmal int.

[Id] [int] NOT NULL,
[FirstName] [nvarchar](255) NOT NULL,
[LastName] [nvarchar](255) NOT NULL,
[StreetNo] [nvarchar](50) NOT NULL,
[Zipcode] [nvarchar](10) NOT NULL,
[City] [nvarchar](255) NOT NULL,
[Mail] [nvarchar](255) NOT NULL

Auf meinem lokalem Notebook (HDD = SSD / CPU i7) habe ich folgende Zeiten erhalten:

Guid: 10248 Millisekunden
Int: 9997 Millisekundne

Auf einem Netzwerkserver/Kundenserver (HDD = SAS / CPU = XEON) habe ich folgende Zeiten erhalten:

Guid: 49662 Millisekunden
Int: 45779 Millisekunden

Aus diesem Grund kann ich die Zeiten aus den verschiedenen Blogs nicht verstehen. Klar verbraucht GUID mehr Platz usw. usf., aber man kann mit diesen Zeiten aus meiner Sicht auf jeden Fall Leben.

Timur Zanagar Germany

>

1/12/2011 12:25:18 PM #

Ich hab vergessen dazu zu schreiben das es auch 10.000 Inserts sind.

Timur Zanagar Germany

>

1/12/2011 1:00:44 PM #

Hallo Timur,

Bei 10.000 Initial-Inserts tut's auch noch nicht weh, füge aber mal in eine Tabelle 1 Mio Records ein, und probiere dann 10.000 zusätzliche Inserts durchzuführen - wird ewig dauern im Vergleich zu einer INT IDENTITY.

Auf einer SSD tut eine GUID nicht so weh, weil dort auch der Random IO nicht weh tut, es wird ja nichts mehr mechanisch bewegt. Aber auf einer normalen 7.2k oder 15k Festplatte schauts anders aus. Und der Overhead der 16 Bytes beschränkt sich nicht nur auf den Clustered Index, sondern auch auf die Non-Clustered Indexes. Page Splits sind ebenfalls die Folge, die ca. 10x so viel Platz im Transaction-Log benötigen, dh. wenn High-Availability Technologien wie Log-Shipping/Database Mirroring im Einsatz sind, hast du hier dann auch automatisch einen enormen Performance-Overhead, weil der Netzwerkverkehr viel größer ist.

Abhilfe schafft einerseits eine SSD, bzw. ein Non-Clustered Index auf der GUID Spalte, zB. "ID UNIQUEIDENTIFIER PRIMARY KEY NONCLUSTERED". Den Clustered Key kann man ja auch auf eine andere Spalte setzen. Und wenn man die GUID unbedingt haben möchte, weil's ja so schön sexy im Application Layer ist (...), muss man sich mit dem FILLFACTOR und dem Index Rebuild spielen, bis man eine optimale Fragmentierung des Clustered Index gefunden hat, die nicht zu viel Index Rebuilds kostet - Index Rebuild ist standardmäßig eine Offline-Operation, dadurch steht der Clustered Index - also die Tabelle - in der Zwischenzeit nicht zur Verfügung...

-Klaus

Klaus Aschenbrenner Österreich

>

1/12/2011 1:02:52 PM #

Wann könnte denn ein Long ( BigInt ) nicht mehr ausreichen?

Guid nimmt man nicht um nicht in diese Verlegenheit zu kommen, sondern um bei Multiuser zugriffen kein Problem mit FK's zu bekommen.

FZelle Germany

>

1/12/2011 1:21:49 PM #

Warum bekommt man bei Multiuser-Zugriffen Probleme mit den FKs, wenn ich keine GUIDs einsetze?

Klaus Aschenbrenner Österreich

>

1/12/2011 1:42:29 PM #

Ich stimme der Kernaussage durchaus zu. Allerdings wird in all diesen Beispieln immer nur mit einer einzigen Verbindung ein insert nach dem anderen gemacht. Wenn man auf einer Multi CPU / Multi Core (2 x 4, 2 x 8 ist heute nicht selten) arbeitet und Verbindungen von vielen Clients (zum Beispiel einer Farm von Webservern) hat und alle gleichzeitig inserts auf einer Tablle machen dann verhält es sich genau umgekehrt, Die einzelnen Verbindungen werden über ganzen Worker Threads aufgeteilt und diese kämpfen dann alle um die selben Data pages der Tablle. Wenn man dann mit Guids arbeitet wird die last gleichmässigverteilt und in summe ist es schneller.
Int basierende Clustered Index Tabllen haben auf aktueller Hardware ein limit von ca. 5.000 inserts pro Sekunde (zugegeben das ist relativ viel).
4 CPU/32 Core Server mit 2000 simulierten clients auf 10 Webservern
Gleich Hardware gleiches setup nur eine Guid als Clustered Index Key und wir konnten 220.000 inserts pro sekunde machen.

Thomas Grohser, SQL Server MVP Österreich

>

1/12/2011 6:11:27 PM #

Schöne Diskussion, nur weiter so Smile

Mario Meir-Huber Österreich

>

1/12/2011 6:53:41 PM #

Wichtig ist halt, dass einem die Fragmentierung bewusst ist, wenn man eine GUID als PK einsetzt...

Klaus Aschenbrenner Österreich

>

1/12/2011 7:27:15 PM #

ja Fragmentierung ist immer so ein Thema ...

Was mich interessiert seitens der Diskutierenden: was verwendet Windows Azure Table Storage im Hintergrund? Ist ja ein typischer BigTable Store. Da denke ich das nicht die SQL Server Engine verwendet wird? Oder hat jemand andere Informationen?

Mario Meir-Huber Österreich

>

1/13/2011 8:19:04 PM #

GUIDs haben vorallem bei Multiuser-Anwendungen ihre Vorteile. Und spätestens wenn es um Merge-Replikationen geht, kommt man eh nicht um GUIDs herum.

Carsten Germany

>

1/16/2011 12:13:25 PM #

@Carsten: ja, dafür eignen sich dann sequential GUIDs wohl eher ... sind von der Performance her noch etwas besser.+

lg Mario

Mario Meir-Huber Österreich

>

4/14/2011 10:00:43 AM #

Ich bin da schon bei euch: Die Verwendung hängt sicherlich auch vom Setting ab.

Meine Erfahrungswerte mit "kleinen" DBs sprechen auch für LONG od. INT als Index, da bin ich voll bei Klaus. Wie Thomas sagt, kann sich das aber natürlich in großen Szenarien mit Multi umdrehen.

Meine Erkenntnis á la Qualtinger: Es hängt davon ab. Wink

Toni Pohl Österreich

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

www.microsoft.com/austria | © 2009 Microsoft Corporation. Alle Rechte vorbehalten.
BlogEngine.NET 2.5.0.6 powered by atwork