Sekvenční GUID
Při návrhu sloupců primárních klíčů se u MS SQL poměrně často používá datový typ GUID. Jednou ze zajímavých výhod tohoto datového typu pro aplikačního vývojáře je fakt, že hodnoty primárních klíčů je možné generovat i v klientské aplikaci (standardně metodou Guid.NewGuid()). Odpadá tak určitá pracnost navíc při použití databází generovaných sekvenčních hodnot klíče (INT s IDENTITY apod.), kdy je nutné po vložení záznamu hodnoty z databáze do aplikace zpětně načítat. Z pohledu vývojáře je tedy GUID většinou méně pracné řešení a proto bývá také často jako primární klíč používán.
Pro databázový server nicméně primární klíče s datovým type GUID není úplně ideální volba. Datový typ GUID zabírá jednak o něco víc místa než například zmiňovaný INT (16 byte GUID oproti 4 byte INT). Díky tomu budou například databázové indexy s použitím GUID vždy větší, než indexy s použitím INT.
Možná ještě větším problémem může být skutečnost, že generovaná hodnota je z pohledu řazení náhodná. Ve spojení s primárním klíčem a navíc clustered indexem (pokud není explicitně řečeno jinak, je vytvářen s primárním klíčem “by default”) dochází tedy většinou k tomu, že nově vkládaný záznam není ukládán jako u sekvenčních hodnot v tabulce “na konec”, ale do různých datových stránek podle vygenerované hodnoty. Toto vede k častější nutnosti rozdělování stránek a reorganizaci dat v nich (blíže viz popis u Fill Factor). Výsledkem je obvykle daleko vyšší počet použitých stránek a také jejich vyšší fragmentace, než při použití klíčů se sekvenčně generovanými hodnotami.
Jedním z řešení, které dokáže tuto nevýhodu eliminovat, je generování sekvenčních GUID. Jedná se o způsob generování GUID, který zajišťuje, že hodnoty generované na jednom počítači budou jednak unikátní a také každá vytvořená hodnota bude větší než předchozí.
V databázi pro tento způsob vytváření lze využít funkci NEWSEQUENTIALID (bohužel lze využít pouze jako default hodnota sloupce), možnost generování takovéto hodnoty v aplikaci je ukázáno dále.
Vytváření sekvenčního GUID v .NET
Pro vytvoření sekvenčního GUID existuje API funkce UuidCreateSequential. Protože managed třída Guid metody pro její využití neobsahuje, je nutné toto implementovat s pomocí P/Invoke voláním této API funkce. Pomocnou třídu pro jejich vytváření nazveme například GuidCreator a vypadat by mohla například takto:
Code Snippet
- public static class GuidCreator
- {
- private enum RpcUuidCodes : int
- {
- RPC_S_OK = 0,
- RPC_S_UUID_LOCAL_ONLY = 1824,
- RPC_S_UUID_NO_ADDRESS = 1739
- }
- [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
- static extern int UuidCreateSequential(out Guid guid);
-
- public static Guid NewGuid()
- {
- return Guid.NewGuid();
- }
- public static Guid NewSequentialGuid()
- {
- return CreateSequentialGuid();
- }
- public static Guid NewSQLSequentialGuid()
- {
- return SQLLikeReverseBytes(CreateSequentialGuid());
- }
-
- private static Guid CreateSequentialGuid()
- {
- Guid guid = Guid.Empty;
- var resultCode = UuidCreateSequential(out guid);
- switch (resultCode)
- {
- case (int)RpcUuidCodes.RPC_S_OK:
- break;
- case (int)RpcUuidCodes.RPC_S_UUID_LOCAL_ONLY:
- throw new Exception(@"NewGuid failed - UuidCreateSequential returned RPC_S_UUID_LOCAL_ONLY");
- case (int)RpcUuidCodes.RPC_S_UUID_NO_ADDRESS:
- throw new Exception(@"NewGuid failed - UuidCreateSequential returned RPC_S_UUID_NO_ADDRESS");
- default:
- throw new Exception(String.Format(@"NewGuid failed - UuidCreateSequential returned {0}", resultCode));
- }
- return guid;
- }
- private static Guid SQLLikeReverseBytes(Guid value)
- {
- var bytes = value.ToByteArray();
- Array.Reverse(bytes, 0, 4);
- Array.Reverse(bytes, 4, 2);
- Array.Reverse(bytes, 6, 2);
- return new Guid(bytes); ;
- }
- }
Metoda NewGuid je zde uvedena jen pro úplnost a reprezentuje klasické vytváření náhodného GUID pomocí volání standardní .NET metody Guid.NewGuid(). Hodnoty jsou generovány náhodně:
| 965bb964-6eca-4607-a037-d2550b51aefa |
| 6f7d260a-67b3-444b-a7e7-dba8fddcd7a6 |
| 1fecc9c7-50a7-4f78-99cc-469a8cf65fc4 |
| 7fbde154-be15-469a-8d91-c337b9d1c37a |
Metoda NewSequentialGuid reprezentuje vytváření sekvenčního GUID pomocí přímého volání API funkce UuidCreateSequential:
| b5aea086-aecc-11df-940d-001fe2e4f119 |
| b5aea087-aecc-11df-940d-001fe2e4f119 |
| bd77004c-aecc-11df-940d-001fe2e4f119 |
| bd77004d-aecc-11df-940d-001fe2e4f119 |
Pro kompatibilitu s SQL metodou NEWSEQUENTIALID je potřeba ještě ve vygenerovaném GUID provést přehození některých bytů. Dle http://blogs.msdn.com/b/sqlprogrammability/archive/2006/03/23/559061.aspx se přehození provádí proto, aby výsledné hodnoty lépe vyhovovaly interním algoritmům SQL serveru pro porovnávání GUID hodnot. Tento postup implementuje metoda NewSQLSequentialGuid a generované hodnoty tak odpovídají hodnotám vraceným uvedenou SQL funkcí:
| 6cd9ef0e-cdae-df11-940d-001fe2e4f119 |
| 6dd9ef0e-cdae-df11-940d-001fe2e4f119 |
| 4ab28917-cdae-df11-940d-001fe2e4f119 |
| 4bb28917-cdae-df11-940d-001fe2e4f119 |
V případě použití takto generovaných hodnot je samozřejmě dobré si uvědomit, že sekvenčně generované budou hodnoty vždy pouze na jednom počítači. Lze to tedy použít u aplikací, u nichž lze toto zajistit (ASP.NET, vícevrstvé aplikace s aplikačním serverem apod.). V klientských aplikacích bez aplikačního serveru postrádá většinou použití sekvenčních ID smysl.
Testovací aplikace s příkladem generování hodnot pomocí výše uvedené třídy GuidCreator je k dispozici ke stažení zde.
Na závěr ještě pár odkazů na další informace:
http://msdn.microsoft.com/en-us/library/ms190215.aspx
http://www.mssqltips.com/tip.asp?tip=1600
http://www.jorriss.net/blog/jorriss/archive/2008/04/24/unraveling-the-mysteries-of-newsequentialid.aspx
http://www.shirmanov.com/2010/05/generating-newsequentialid-compatible.html