Primary keys in transactional processing: int vs GUID
Le type "guid" (uniqueidentifier) a été introduit dans la version 7/2000 de SQL Server. Etant utilisé pour les clés primaires ce type avait apporté plusieurs avantages (et inconvénients, bien sur) in implémentation de BDD. En 2002, j'ai fait un essais de performance en comparaison les différents types des données (ancienne article en russe, traduction google) qui montre la différence non-significative entre l'approche "guid" et celle "classique" des valeurs entières. En 2012 il est temps de rappeler le sujet et refaire ce test.
Environment
Infra
J'ai pris le poste de travail en x86.
CPU Intel Xeon 6 cœurs (avec HT) 2.8 GHz RAM 4 Go Windows 7 x86 SQL Server 2012 x86 Enterprise (limité par 2 Go de mémoire à utiliser)
BDD
Nous avons un schéma de 3 tables:
Documents --- 1:M ---< Lignes de documents >--- M:1 --- Produits
Il y a donc 3 tables dont les clés primaires/étrangères ont le type "int" et 3 tables dont les clés primaires/étrangères ont le type "uniqueidentifier".
Le script "00-createdb.sql" crée la BDD et ces tables.
Données
Comme nous sommes dans OLTP (transactionnel), le volume ne devrait être gros. 1 millions de document dont le nombre de lignes est 20 environ (soit 20M lignes) sera suffisant.
Le script "01-init-data.sql" fait l'initialisation des données.
Les données sont identiques sauf les valeurs de clés.
Les requêtes
J'ai retenu 3 type de requêtes :
Récupérer la clé primaire d'une ligne aléatoire de la table de documents (requêtes Q1.1 et Q2.1) Récupérer une ligne de la table de documents par la clé primaire (requêtes Q1.2 et Q2.2) Calculer le montant de ventes d'un document groupé par les produits (requêtes Q1.3 et Q2.3) Chaque requête se tourne 1000 fois, puis on calcul les valeurs moyennes des indicatrices importantes (temps, CPU, I/O).
Le batch "start-queries.cmd" (à customiser) lance le script "02-queries.sql" qui tourne les requêtes.
Résultats
Pas de nouvelles par rapport des résultat de 2002 : en termes de performance l'utilisation des guid ne dégrade pas significativement toujours. C'est évident, puisque le stockage de "guid" proche de Int128 (binary(16)) vs Int32 pour le type "int".
On peut sembler, que la différence de 10% est grande, mais il ne s'agit que de la différence entre 30 et 33 micro(!)secondes ou entre 6 et 8 millisecondes en cas de 21%.
Query | Q1.1 | Q2.1 | Diff, % | Q1.2 | Q2.2 | Diff, % | Q1.3 | Q2.3 | Diff, % |
---|---|---|---|---|---|---|---|---|---|
avg_cpu_time | 484504 | 488416 | 0,8 | 30 | 33 | 10 | 314 | 351 | 11,8 |
avg_elapsed_time | 421849 | 422883 | 0,2 | 30 | 33 | 10 | 6903 | 8408 | 21,8 |
avg_physical_reads | 4 | 5 | 25,0 | 0 | 0 | 0 | 10 | 12 | 20,0 |
avg_logical_reads | 8444 | 11535 | 36,6 | 3 | 3 | 0 | 46 | 48 | 4,4 |
Par contre, le stockage est plus affecté puisqu'on ordonne la table en cluster par sa clé primaire. Ensuite, tous les index non-cluster seront plus volumineuse en 2-3 fois. D autre part, l'optimisation du stockage des guids en SQL Server permet éviter l'augmentation de 4 fois (128 vs 32 bits).
table_name | row_count | reserved_size_kb | data_size_kb | index_size_kb | unused_size_kb |
---|---|---|---|---|---|
doc_line2 | 20503463 | 1941384 | 1115840 | 824872 | 672 |
doc_line1 | 20503463 | 956672 | 628464 | 327848 | 360 |
doc2 | 1000000 | 111120 | 45248 | 65776 | 96 |
doc1 | 1000000 | 79888 | 33408 | 46368 | 112 |
product1 | 100 | 48 | 16 | 32 | 0 |
product2 | 100 | 48 | 16 | 32 | 0 |
Mais n'oublie pas la compression. Après avoir compressé les données des tables "doc_line2" et "doc_line1" les tailles ont devenus 430136 et 338712 Ko au lieu de 1115840 et 628464. Voyez la différence entre 20% et 90% ? :)
Également, pour les index.
Conclusions
En OLTP (transactionnel) le choix de type "guid" peut être une solution. Au point de vue d’architecture du système cette solution supprime les problèmes récurrentes tels que génération des ID des objets, conflits des clés dans les BDD en réplication/consolidation, unicité des ID dans une système distribué sans avoir le service d'identification supplémentaire (remplace les sacrés ID pseudo-composites = ID node + ID ligne).
Sachant, qu'en OLTP le temps moyen de réponse de BDD ne dépasse pas 20% du celui total, il faut étudier cette option au moins.
Par contre, en OLAP (décisionnel) nous avons les contraintes de grosses volumes des données en mode "lecture seul" et donc le choix des types numériques correspondantes (tinyint/smallint/int/bigint) est évident.
blog comments powered by Disqus