SQL Server 2005 fornisce una nuova funzione chiamata NEWSEQUENTIALID()
simile a quella già implementata in SQL Server 2000 NewID()
(ancora supportata dalla versione 2005). Entrambe restituiscono un valore univoco a 16-byte di tipo uniqueidentifier
. Scopo di questo articolo è illustrare le modalità e i limiti di utilizzo di NEWSEQUENTIALID()
rispetto a NEWID()
.
La funzione NEWID()
Molti sviluppatori utilizzano questa funzione per produrre valori unici da inserire in una colonna che funge da chiave primaria con indice cluster per una generica tabella. Questa pratica, formalmente corretta, fornisce però dei risultati scadenti in termini di performance se comparata all'uso di colonne di tipo IDENTITY
con tipo dati INTEGER. Il listato 1 illustra il codice necessario per creare una tabella con i valori di chiave primaria e indice cluster generati mediante la funzione NEWID()
o il tipo colonna IDENTITY
.
Listato 1. Generare valori con NEWID() e IDENTITY per colonne con chiave primaria e indice cluster
--Tabella con chiave primaria generata con NEWID()
CREATE TABLE test1 ( [uniqueid] UNIQUEIDENTIFIER default(NEWID()) PRIMARY KEY )
--Tabella con chiave primaria generata tramite colonna identity
CREATE TABLE test2 ( [uniqueid] INT IDENTITY(1,1) PRIMARY KEY )
L'algoritmo implementato dalla funzione NEWID()
crea dei valori unici e casuali (ideali per identificare univocamente una riga all'interno della tabella) che seguono un pattern di crescita non-sequenziale. In parole povere non è garantito che l'ultimo valore generato sia maggiore del precedente.
In virtù di questo ogni inserimento effettuato su una tabella con valori di chiave primaria e indice cluster ottenuti con NEWID()
comporta sempre una riallocazione della posizione dei dati dell'indice e della riga all'interno delle strutture di memorizzazione di SQL Server (cioè le pagine). Questa operazione causa un'elevata frammentazione/suddivisione (pensiamo a tabelle con migliaia o milioni di righe) delle pagine dati in memoria con conseguente aumento delle operazioni Input/Output fatte sul server (ricordiamo che i valori degli indici cluster sono duplicati per riferimento anche negli indici non cluster).
Diversamente, utilizzando IDENTITY
nella creazione dei valori per le colonne con chiave primaria e indice cluster, oltre ad avere la garanzia che i valori generati siano univoci otterremo anche la loro sequenzialità (dal più basso al più alto: 1,2,3,4,5,6...10000). Per questi motivi, fino ad ora, il tipo colonna IDENTITY
è un canditato migliore, rispetto a NEWID()
, a diventare un indice cluster (chiave primaria lo possono essere entrambi visto che i valori generati sono univoci).
L'uso di NEWID()
ha comunque un innegabile vantaggio: i valori GUIDs generati sono garantiti come globalmente univoci, quindi spendibili tra server, macchine e istanze differenti. Ciò significa che possono rappresentare universalmente l'univocità una riga di dati (non troveremo mai un duplicato), l'ideale per lo scambio e la condivisione di dati tra macchine e server differenti.
La funzione NEWSEQUENTIALID()
NEWSEQUENTIALID()
genera valori univoci a 16 byte di tipo uniqueidentifier
. Questi seguono un pattern creazionale di tipo incrementale/sequenziale (l'opposto di NEWID()
) il quale assicura che l'ultimo valore generato sia più alto del precedente. Per questo motivo NEWSEQUENTIALID()
è un buon candidato a sostituire la funzione NEWID()
e le colonne di tipo IDENTITY
nella creazione di valori da inserire nelle colonne con chiave primaria ed indice cluster. Vediamo ora un semplice esempio di utlizzo della funzione:
Listato 2. Generare valori con NEWSEQUENTIALID() per colonne con chiave primaria e indice cluster
CREATE TABLE Prodotti ( [ProdottoId] uniqueidentifier DEFAULT NEWSEQUENTIALID() PRIMARY KEY )
INSERT INTO Prodotti Values (Default)
GO 50
Select * from Prodotti
Altra cosa interessante è che l'ordinamento crescente dei valori GUIDs è effettuato sulla loro rappresentazione binaria e non su quella testuale. L'esempio del listato 3 chiarisce le ide a riguardo.
Listato 3. L'ordinamento dei valori generati con NEWSEQUENTIALID() è effettuato sulla loro rappresntazione binaria
create table TestGUID
(
[uguid] uniqueidentifier default (newsequentialid()),
[iguid] int identity(1,1)
)
go
insert into TestGUID default values
go 30
select [uguid],
cast([uguid] as binary(16)),
row_number() over (order by [uguid]) as ordinaPerUGuid,
row_number() over (order by cast([uguid] as varchar(40))) as ordinaPerUGuidTesto,
[iguid], row_number() over (order by testId) as ordinaPerIGuid
from test
Internamente NEWSEQUENTIALID()
chiama una API documentata di Windows: UuidCreateSequential()
, in grado di generare un valore univoco sulla base del "MAC Address della scheda di rete" e dell'hardware del pc (questa implementazione è condivisa anche da NEWID()
).
Ovviamente se il pc in questione non è dotato di una scheda di rete (improbabile al giorno d'oggi ma non impossibile in certi scenari) il valore restiuito da NEWSEQUENTIALID()
non può essere garantito come univoco tra server differenti, ma solo sulla macchina in questione.
Il team del SQL Server Engine allo scopo di ottenere valori sequenziali (e non casuali) da NEWSEQUENTIALID()
ha manipolato alcuni byte dei valori restituiti dall'API UuidCreateSequential()
.
Pregi e limiti di NEWSEQUENTIALID()
Un elenco dei pregi della funzione:
NEWSEQUENTIALID()
può essere utilizzata per creare valori GUIDs capaci di ridurre i livelli foglia degli indici (vedi strutture dati btree). I valori generati seguono un pattern incrementale e sequenziale (al contrario dei valori casuali generati daNEWID()
) questo comporta una minor frammentazione delle pagine dati degli indici e un minor numero di operazioni input/output. Di riflesso otteniamo un aumento delle performance di accesso ai dati (durante gli inserimenti le pagine dati rimangono in memoria, non c'è riallocazione, i valori aggiunti sono semplicemente accodati alle ultime pagine).- Usata in abbinamento con ROWGUIDCOL produce un miglioramento nella scalabilità e nelle performance della "Merge Replication".
Un elenco dei limiti della funzione:
NEWSEQUENTIALID()
può essere usata solo come "DEFAULT constraints" su colonne con tipo dati uniqueidentifier. Non è possibile richiamareNEWSEQUENTIALID()
all'interno diSELECT
oSET
. La funzione è marcata come "non-deterministica"- È possibile prevedere il valore successivo generato da
NEWSEQUENTIALID()
e questo ne limita l'uso in applicazioni "sicure by default" (questo perchè segue un pattern creazionale predefinito basato sull'indirizzo della scheda di rete del pc). - I valori generati da
NEWSEQUENTIALID()
potrebbero essere non-univoci (il caso di un pc senza scheda di rete). Attenzione quindi ad eventuali collisioni tra dati nel caso questi vengano condivisi e scambiati tra servers differenti.
Conclusione
La generazione di ID è sempre stato un "grosso cruccio" per gli sviluppatori di applicazioni. La nuova funzione NEWSEQUENTIALID()
ci viene in aiuto semplificando le scelte applicative sotto molti punti di vista. Tenendo conto dei limiti illustrati in precedenza credo che d'ora in poi sarà molto più ovvio scrivere DEFAULT NEWSEQUENTIALID() PRIMARY KEY
anzichè INT IDENTITY(1,1) PRIMARY KEY
durante la creazione di una generica tabella.