Guid ca o cheie primară rapidă pentru diferite baze de date

Problema GUID

Din punct de vedere istoric, abordarea cea mai comună în proiectarea bazei de date pentru a identifica un rând specific a fost o secvență întregă. În mod obișnuit, o astfel de secvență este generată pe partea serverului când se introduce un rând nou. Această abordare simplă și clară este potrivită pentru multe aplicații.







Cu toate acestea, există o serie de situații în care această abordare nu va fi bună. Cu utilizarea extensivă a cadrelor ORM, cei mai mulți utilizatori încearcă să evite complexitatea inutilă din partea bazei de date, iar formarea cheii din partea bazei de date la astfel de complexități poate fi atribuită. Replicarea bazelor de date devine, de asemenea, dificilă, dacă vă bazați numai pe un singur serviciu intern pentru generarea cheilor. O sursă externă este necesară pentru a minimiza dependențele de modul în care sunt generate cheile.

Principalul avantaj al GUID este abilitatea de a genera în zbor, pe partea clientului, fără a fi nevoie să verificați unicitatea din baza de date. La prima vedere, aceasta este soluția ideală pentru problema cheilor unice.

Deci, care este problema?

Problema este performanța. Pentru cea mai bună performanță, majoritatea bazelor de date stochează rânduri în indexuri grupate, adică Rândurile din tabele sunt de fapt stocate pe disc în ordine ordonată. Acest lucru face căutarea șirului necesar este la fel de simplă ca și căutarea de index, însă același mecanism face ca inserarea unei noi valori să fie foarte lentă dacă noua valoare nu intră în capătul listei. De exemplu, luați în considerare acest exemplu:

Linile 7 și 8 ar trebui să fie deplasate pentru a face loc unei noi înregistrări. Nu este o problemă în acest caz, dar când vorbim despre această operațiune, când există milioane de rânduri în tabel, aceasta devine o problemă reală. Și când vrei să faci sute de inserții pe secundă, poate fi foarte dificil.

Aceasta este problema cu GUID: acestea pot sau nu pot fi cu adevărat aleatoare, dar majoritatea dintre ele arată aleatoriu, în sensul că acestea sunt de obicei create fără o anumită ordine. Din acest motiv, practica utilizării GUID ca cheie primară în bazele de date cheie importante este un mauveton. Introducerea de noi valori poate fi foarte lentă și poate conduce la o operare intensă a discului.

GuID-uri consecutive

Deci, ce soluție poate fi aplicată? Principala problemă este gradul ridicat de disparitate a datelor din GUID. În acest caz, putem încerca să facem GUID mai consistent și mai previzibil. Abordarea COMB (un compozit al marcajului de timp GUID COMBINAT) înlocuiește o parte a GUID cu o valoare care este garantată să crească sau cel puțin să nu scadă cu fiecare nouă valoare. După cum puteți ghici din definiția SOMV, în aceste scopuri se aplică valoarea generată de data și ora curente.

Pentru a ilustra acest lucru, să ne imaginăm un set de GUID-uri standard:

fda437b5-6edd-42dc-9bbd-c09d10460ad0
2cb56c59-ef3d-4d24-90e7-835ed5968cdc
6bce82f3-5bd2-4efc-8832-986227592f26
42af7078-4b9c-4664-ba01-0d492ba3bd83

Rețineți că valorile sunt în ordine aleatoare și sunt într-adevăr aleatorii. Introducerea unui milion de rânduri cu acest tip poate fi foarte lungă.

Acum, să ne imaginăm o listă ipotetică de GUID-uri speciale:

00000001-a411-491d-969a-77bf40f55175
00000002-d97d-4bb9-A493-cad277999363
00000003-916c-4986-a363-0a9b9c95ca52
00000004-f827-452b-a3be-b77a3a4c95aa

Primul bloc de cifre a fost înlocuit cu o succesiune ascendentă de cifre - să spunem numărul de milisecunde de la începutul programului. Introducerea unui milion de linii în acest format nu va fi atât de deprimantă, deoarece fiecare linie următoare va fi inserată la sfârșitul listei și nu va necesita re-sortarea datelor existente.

Acum, că avem un concept de bază, să analizăm detaliile despre cum să obțineți un GUID similar și cum funcționează în diferite baze de date.

GhID-ul pe 128 biți este alcătuit din 4 blocuri principale: Data1, Data2, Data3, Data4 - pe care le puteți vedea pe exemplu:

Majoritatea algoritmilor care sunt folosiți astăzi și în special folosiți .Net Framework, generatori în mod inerent de numere aleatorii. Aceasta este o veste bună pentru noi, deoarece aceasta înseamnă că experimentele cu diferite părți ale GUID nu trebuie să conducă la o încălcare a integrității unicității.

Din păcate, bazele de date funcționează diferit cu GUID-ul. Unele dintre ele (MS SQL, PostgreSQL) au un tip încorporat pentru a lucra cu GUID. DB-urile fără suport încorporat funcționează cu GUID ca și cu un câmp de text de lungime de 36 de caractere. Oracle utilizează, de obicei, un set brut de octeți într-o coloană brută (16).

O complicație suplimentară este faptul că MS SQL organizează GUID pe ultimele 6 octeți semnificativi (ultimii 6 octeți din blocul Data4). inclusiv dacă vrem să creăm un GUID secvențial pentru utilizarea în SQL Server, atunci trebuie să introducem partea consecutivă în final. Un număr mare de alte sisteme se așteaptă să vadă o parte secvențială la început.

Având în vedere faptul că bazele de date funcționează diferit cu GUID, nu poate exista un singur algoritm care să răspundă tuturor nevoilor. Va fi necesar să gestionați metoda de creare în funcție de modul în care baza de date funcționează cu GUID. După efectuarea unor experimente, am identificat trei abordări principale care ar trebui să acopere toate cazurile:

  • Crearea unui GUID secvențial ca șir
  • Crearea unui GUID secvențial sub formă de date binare
  • Crearea unui GUID secvențial, cu o parte secvențială la sfârșit pentru MS SQL

(De ce nu se poate același GUID ca un șir de caractere și un set de bytes? Pentru că modul în care .Net gestionează GUID pot fi diferite de reprezentările șir în sistemele little-endian, și de cele mai multe masini folosind .Net sunt little-endian. Detalii mai jos.)

Alegerea strategiei poate fi prezentată în felul următor:

vizualizați sursa de imprimare?

Dar cum să creăm un GUID consistent? La ce parte părăsim "aleatoriu" și care va fi înlocuită de timbrul temporal? Specificația inițială pentru COMB cu implementare pentru MS SQL înlocuiește ultimii 6 octeți cu valoarea de timp. Acest lucru este parțial dincolo de confort, deoarece cele 6 octeți sunt folosite pentru comandă, dar 6 octeți pentru timp vor fi suficienți. Restul de 10 octeți va fi suficient pentru componenta aleatoare.







Deci, așa cum am menționat deja, începeți prin a obține 10 octeți aleatorii:

vizualizați sursa de imprimare?

var rng = nou Sistem.Securitate.Cryptografie.RNGCryptoServiceProvider ();

octet [] randomBytes = nou octet [10];

Pentru a genera o componentă aleatoare GUID, utilizați clasa RNGCryptoServiceProvider. deoarece System.Random are câteva caracteristici care îl fac nepotrivite pentru sarcina noastră. Valorile generate de acesta corespund unui anumit tipar și încep să repete nu mai mult de 2 32 iterații. Deoarece ne bazăm pe întâmplare, vom încerca să obținem un număr cât mai cinstit posibil, iar clasa RNGCryptoServiceProvider oferă o astfel de oportunitate.

În primul rând, căpușele se întoarce pe 64 de biți număr întreg, și avem doar 48 de biți, iar dacă vom scăpa de cei doi octeți, 48 de biți rămase bazat pe 100 interval de nanosekndnogo va da cel puțin un an înainte de valori încep să se repete. Acest lucru va distruge ordinea pe care încercăm să o înființăm și va ucide performanța bazei de date pe care am sperat-o. Deoarece majoritatea aplicațiilor sunt concepute pentru mai mult de un an de utilizare, merită să folosiți o altă dimensiune a timpului.

Vestea bună este că două deficiențe se anulează într-un anumit sens: o rezoluție limitată înseamnă că nu putem folosi întreaga valoare a Ticks, dar o putem folosi indirect. Să împărțim valoarea câmpului cu 10000 pentru a obține valoarea stivuire în 48 de biți. Am de gând să utilizeze un milisecundă, deoarece acest lucru face posibilă utilizarea acestor valori la 5800 de ani înainte de valoarea va merge la al doilea tur, cred că acest lucru va fi suficient pentru multe aplicații moderne.

O notă mică înainte de a continua. Utilizarea unei rezoluții de 1 milisecundă este suficientă pentru multe sarcini. Am experimentat cu contoare suplimentare și rezoluție mai mică, dar nu prea avea sens deoarece diferențele erau minime. Bazele se descurcă perfect cu sute de GUID-uri cu o singură ștampilă de timp, deci nu este o problemă.

vizualizați sursa de imprimare?

lungime de timp lung = DateTime.Now.Ticks / 10000L;

octet [] timestampBytes = BitConverter.GetBytes (timestamp);

Acum avem o ștampilă de timp. Deoarece avem un set de octeți folosind BitConverter, va trebui să verificăm sistemul pentru o secvență de octeți.

vizualizați sursa de imprimare?

În general, totul este destul de bun, dar merită luate în considerare particularitățile .Net în modul în care reprezintă GUID-ul. Pentru un cadru, aceasta nu este o succesiune simplă de octeți. Acesta reprezintă GUID ca o structură care conține un număr întreg pe 32 de biți, 2 întregi de 16 biți și 8 octeți individuali.

Ce facem cu asta? Principala problemă din nou este ordinea octeților. Din nou, trebuie să rearanjați ordinea, dar numai pentru o reprezentare de șir pentru sistemele puțin-endian.

vizualizați sursa de imprimare?

dacă (guidType == SequentialGuidType.SequentialAsString

Array.Reverse (guidBytes, 0, 4);

Array.Reverse (guidBytes, 4, 2);

Rămâne acum cel mai simplu lucru să returnați rezultatul tuturor calculelor:

vizualizați sursa de imprimare?

returnați nou Guid (guidBytes);

utilizarea

Pentru a utiliza metoda, trebuie să determinați ce tip de bază de date utilizați și ce tip de GUID este cel mai potrivit. Iată câteva sfaturi pentru utilizarea:

Nu există niciun GUID nativ pentru baza de date SQLite, dar există extensii care să imite suportul. Într-un fel sau altul, GUID poate fi reprezentat fie de o matrice de 16 octeți, fie de un șir de caractere de 36 de caractere.

Iată câteva exemple de GUID-uri obținute folosind noua metodă NewSequentialGuid (SequentialGuidType.SequentialAsString):

39babcb4-e446-4ed5-4012-2e27653a9d13
39babcb4-e447-ae68-4a32-19eb8d91765d
39babcb4-e44a-6c41-0fb4-21edd4697f43
39babcb4-e44d-51d2-c4b0-7d8489691c70

Un alt exemplu este NewSequentialGuid (SequentialGuidType.SequentialAsBinary):

b4bcba39-58eb-47ce-8890-71e7867d67a5
b4bcba39-5aeb-42a0-0b11-db83dd3c635b
b4bcba39-6aeb-4129-a9a5-a500aac0c5cd
b4bcba39-6ceb-494d-A978-c29cef95d37f

Dacă te uiți la aceste date cu ToSting (). atunci puteți vedea ceva ciudat. Primele două blocuri vor fi scrise în ordine inversă. Acest lucru se datorează doar problemei discutate cu sistemele mari \ puțin-endian. Dacă aceste date sunt scrise ca șir, atunci vor apărea probleme de performanță. Soluția poate fi folosirea lui Guid.ToByteArray ():

39babcb4eb5847ce889071e7867d67a5
39babcb4eb5a42a00b11db83dd3c635b
39babcb4eb6a4129a9a5a500aac0c5cd
39babcb4eb6c494da978c29cef95d37f

Testele au fost executate utilizând aplicațiile de consolă furnizate împreună cu fiecare bază de date. A introdus 2 milioane de rânduri cu un GUID sub forma unei chei primare și cu o valoare de text de 100 de caractere. Au fost utilizate toate metodele descrise în articol. Pentru control, am folosit Guid.NewGuid (), precum și cheia primară a tipului întreg. Timpul a fost măsurat în câteva secunde după introducerea fiecărui milion. Iată ce sa întâmplat:

Guid ca o cheie primară rapidă pentru diferite baze de date

Pentru MS SQL, rezultatele au fost asteptate, intrucat SequentialAtEnd a fost facuta doar pentru aceasta baza de date. Diferențe cu o valoare intregă de numai 8,4%.

Guid ca o cheie primară rapidă pentru diferite baze de date

Guid ca o cheie primară rapidă pentru diferite baze de date

Era mai dificil să se gestioneze cu Oracle. Păstrând coloana GUID în brut (16) poate fi de așteptat ca metoda SequentialAsBinary este cel mai rapid, și este, dar chiar și la întâmplare GUID nu a fost prea lent, nu la fel de lent în comparație cu o cheie de număr întreg. Mai mult, GUID-urile succesive au fost mai rapide decât cheile întregi, ceea ce a fost greu de prezis și acceptat! Desigur, cred că a jucat rolul de incompetența mea în scris, cererea de Oracle, și dacă cineva respinge datele lasă-mă să știu.

Guid ca o cheie primară rapidă pentru diferite baze de date

Ca și în cazul Oracle, performanța cu GUID arbitrar nu a fost deprimantă. Așa cum era de așteptat, metoda cu SequentialAsString a fost cea mai rapidă, aproape de două ori mai rapidă decât un GUID arbitrar.

Considerații suplimentare

Există câteva lucruri care merită luate în considerare. În acest articol, am dat o mulțime de valori insert timp de timp în baza de date, dar a trecut cu vederea formarea GUID, în comparație cu Guid.NewGuid (). Desigur, timpul de formare este mai lung. Pot crea un GUID aleatoare de milioane de 140 ms, dar crearea de frunze succesive 2800 ms, care este de 20 de ori mai lent.

Testele rapide au arătat că partea leului din această încetinire este utilizarea serviciului RNGCryptoServiceProvider pentru a genera date arbitrare. Trecerea la System.Random a redus timpul de execuție la 400 ms. Încă nu recomand această metodă din cauza pericolelor descrise.

Este o astfel de încetinire o problemă? Personal pentru mine, am decis că nu. Atâta timp cât cererea dumneavoastră nu utilizează inserarea extensivă a datelor (și atunci este în valoare de vedere foarte oportunitatea de a utiliza GUID), costul de generare în concordanță cu timpul de funcționare a bazei de date în sine și activitatea sa viitoare rapid.

O altă posibilă problemă: vor fi 10 octeți suficienți pentru a garanta unicitatea? Dat fiind ștampila de timp, înseamnă că oricare dintre cele două GUID-uri create într-o perioadă de mai mult de câteva milisecunde va fi garantată diferită. Dar ceea ce se întâmplă cu GUID-urile create foarte repede într-o singură perioadă de timp. În acest caz, 10 octeți ne dau o estimare de 2 80 sau 1,208,925,819,614,629,174,706,176 combinații posibile. Ie probabilitatea va fi aceeași cu faptul că în acel moment baza dvs. de date și toate copiile de siguranță vor fi atacate și distruse simultan de o hoardă de porci sălbatici.

Ultima problemă este că s-ar putea fi interesat, este cea obținută GUID nu este în conformitate punct de vedere tehnic, cu RFC standardul 4122. Sincer, nu cred că este o mare problemă, eu nu știu de o singură bază de date, care verifică efectiv GUID dispozitivul intern și GUID versiunea omisiune ne dă octeți suplimentari pentru a crește unicitatea cheii.

Codul final

vizualizați sursa de imprimare?







Articole similare

Trimiteți-le prietenilor: