2009-03-20 12 views
20

Ho appena letto un blog post sulla capacità di NHibernate di creare un GUID dall'ora di sistema (Guid.Comb), evitando così una buona quantità di frammentazione del database. È possibile chiamarlo equivalente sul lato client all'ID sequenziale di SQL Server.GUID sequenziale in Linq-to-Sql?

C'è un modo per utilizzare una strategia simile nel mio progetto Linq-to-Sql (generando il codice Guid nel codice)?

+0

Ho provato tutti questi esempi di guida COMB e tutti generano un'elevata frammentazione con righe da 10.000. la versione di arul - frammentazione del 98%.
NHibernate - frammentazione del 53%.
rpcrt4.dll - 98% frammentazione
bigint - 6% frammentazione
Quanto bene ci si aspetta che le merci della COMB si esibiscano? – Shaun

+0

@Shaun dipende dalla velocità di inserimento, se hai solo pochi inserti al secondo non ci dovrebbe essere frammentazione. – Peter

risposta

9

pettini vengono generati nel seguente modo:

DECLARE @aGuid UNIQUEIDENTIFIER 

SET @aGuid = CAST(CAST(NEWID() AS BINARY(10)) + CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER) 

Quali trascritto in C# sarebbe simile a questa:

public static unsafe Guid CombGuid() 
    { 
     Guid guid = Guid.NewGuid(); 
     byte[] bytes = guid.ToByteArray(); 
     long ticks = DateTime.Now.Ticks; 
     fixed(byte* pByte = bytes) 
     { 
      int* pFirst = (int *)(pByte + 10); 
      short* pNext = (short*)(pByte + 14); 
      *pFirst = (int)(ticks & 0xFFFFFF00); 
      *pNext = (short)ticks; 
     } 

     return new Guid(bytes); 
    } 
+0

Questo sarebbe molto utile anche a me, ma dovrei compilare il mio intero programma con/non sicuro o potrei inserirlo nella propria Libreria di classi e compilare solo quello con la bandiera non sicura? –

+0

@Paladin: puoi farlo senza ricorrere a codice non sicuro. Dai un'occhiata alla classe BitConvert –

+1

Vedere la mia risposta qui sotto per una versione sicura della generazione Guid.Comb. – Doug

3

Bene, è possibile generare il Guid a mano. Tuttavia, uno dei vantaggi di un Guid è che non è da indovinare - cioè determinato record 0000-...-0005, di solito c'è poco senso (da un attaccante) controllando per registrare 0000-....-0004 ecc

Inoltre - re la frammentazione? Finché si dispone di un indice non cluster su questi dati, non sono sicuro che questo sia un problema. Normalmente non si inserisce un indice cluster su un Guid, quindi la tabella sarà un heap (a meno che non si disponga di un indice cluster separato, ad esempio un IDENTITY int). In tal caso, verrà aggiunto alla fine e inserito il nuovo Guid nell'indice non cluster. Nessun vero dolore.

(modifica) Un problema di utilizzare il tempo direttamente è che si introduce un rischio molto maggiore di collisioni; dovresti preoccuparti della creazione a ciclo stretto Guid (ovvero evitare la ripetizione durante la creazione di alcuni in sequenza), il che significa sincronizzazione, ecc. e diventa ancora più problematico se più macchine lavorano in modo intensivo in parallelo - è probabile che otterrai duplicati.

+0

La soluzione che ho suggerito come risposta corretta combina un Guid arbitrario con una parte generata dal tempo, che rimuove il rischio di duplicati. Non so se risolverà qualsiasi problema di frammentazione ... – JacobE

51

di codice C# (sicuro) (complimenti del NHibernate Guid Comb Generator)

Guid GenerateComb() 
{ 
    byte[] destinationArray = Guid.NewGuid().ToByteArray(); 
    DateTime time = new DateTime(0x76c, 1, 1); 
    DateTime now = DateTime.Now; 
    TimeSpan span = new TimeSpan(now.Ticks - time.Ticks); 
    TimeSpan timeOfDay = now.TimeOfDay; 
    byte[] bytes = BitConverter.GetBytes(span.Days); 
    byte[] array = BitConverter.GetBytes((long) (timeOfDay.TotalMilliseconds/3.333333)); 
    Array.Reverse(bytes); 
    Array.Reverse(array); 
    Array.Copy(bytes, bytes.Length - 2, destinationArray, destinationArray.Length - 6, 2); 
    Array.Copy(array, array.Length - 4, destinationArray, destinationArray.Length - 4, 4); 
    return new Guid(destinationArray); 
} 

Un collegamento all'origine su github: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs

+1

Appena aggiunto al mio progetto! –

+1

Felice di sentirlo. Ho trovato questo piccolo gioiello nella fonte di NHibernate e ho dovuto solo condividerlo :) – Doug

3

È sempre possibile chiamare UuidCreateSequential; questo è il "vecchio" generatore di guida (pre-2000-ish quando MSFT lo ha modificato per i guai di stile più casuali a cui siamo abituati oggi). Hanno rinominato il vecchio UuidCreate in UuidCreateSequential e hanno inserito il loro nuovo generatore di guida in una nuova implementazione di UuidCreate. UuidCreateSequential è anche ciò che SQL Server utilizza in NewSequentialID(), ed è unico come i normali guids, ma con il vantaggio che sono sequenziali se ne si crea una pila in fila nello stesso processo.

using System; 
using System.Runtime.InteropServices; 

namespace System 
{ 
    public static class GuidEx 
    { 
     [DllImport("rpcrt4.dll", SetLastError = true)] 
     private static extern int UuidCreateSequential(out Guid guid); 
     private const int RPC_S_OK = 0; 

     /// <summary> 
     /// Generate a new sequential GUID. If UuidCreateSequential fails, it will fall back on standard random guids. 
     /// </summary> 
     /// <returns>A GUID</returns> 
     public static Guid NewSeqGuid() 
     { 
      Guid sequentialGuid; 
      int hResult = UuidCreateSequential(out sequentialGuid); 
      if (hResult == RPC_S_OK) 
      { 
       return sequentialGuid; 
      } 
      else 
      { 
       //couldn't create sequential guid, fall back on random guid 
       return Guid.NewGuid(); 
      } 
     } 
    } 
} 
+2

FYI, se stai usando Mono, quindi rpcrt4.dll non esiste, e questo non funzionerà. – Doug

+0

Buon punto Doug. Io uso UuidCreateSequential, ma ho dimenticato questo scatto. – granadaCoder

+0

Cosa fare se la macchina si riavvia ?? poi perderai il sequenziale –

2

@arul, @Doug

Perché hai messo la parte di tempo alla fine del GUID?

Ho pensato che i byte iniziali sono più significativi per l'ordine, e l'ordine è il motivo per cui la parte temporale è stata introdotta in primo luogo per impedire la frammentazione dell'indice.

Ok, ho trovato il answer e questo answer from Bernhard Kircher e il sito Comparing GUID and uniqueidentifier Values (ADO.NET) a cui fa riferimento.

I GUID generati in questo modo non avrebbero quindi funzionato allo stesso modo su altri database rispetto a MS SQL-Server ma questo non è correlato a LINQ-to-SQL.

Ci scusiamo per gli URL deformati ma non ho abbastanza reputazione per pubblicare più collegamenti.

0

Abbiamo utilizzato un metodo simile a quello che Doug ha pubblicato in precedenza nel modello Entity Framework, quindi è necessario essere in grado di farlo anche da Linq a SQL.

Nel fare questo abbiamo bisogno di un generatore di guid a pettine per il test, e ha finito per la costruzione di questo piccolo strumento per generare GUID pettine in linea

http://www.webdesigncompany.co.uk/comb-guid/

Speriamo che vi aiuterà anche voi.