Génération séquentielle de GUID en Java avec SQL Server uniqueidentifier

Le problème que j'essaie de résoudre est:

1 – Dans notre database, nous avons toutes les tables (également des tables avec des millions d'loggings) avec une colonne PK id déclarée comme VARCHAR (36). Il y a aussi un index clusterisé. Bien sûr, quand je lis en ligne, c'est une mauvaise chose pour la performance, aussi parce que la database a beaucoup de lectures, d'insertions, de mises à jour et de suppressions.

2 – Nous utilisons Hibernate pour notre application web java en tant qu'ORM pour cette db

Après une longue lecture en ligne, j'ai commencé à changer le type de données de ces colonnes en UNIQUEIDENTIFIER avec l'option par défaut newsequentialid () car cette option devrait atténuer les problèmes de fragmentation de nos index.

Ce que j'ai noté est que le problème de fragmentation a persisté, les tables sont devenues très fragmentées après les reconstructions (nous faisons une reconstruction complète de l'index tous les soirs).

Puis j'ai vu que tous nos mappages Hibernate pour les colonnes d'id consistaient en ceci:

<id name="id" column="id" type="ssortingng"> <generator class="guid"/> </id> 

Quand une insertion s'est produite dans notre système, le log a montré que l'insertion était faite après avoir appelé select newid() , donc comme cela returnne un guid random, l'insert serait placé dans un point random de l'index, provoquant ainsi la fragmentation changement de type de données de colonne j'ai fait aussi).

Donc, après une autre search en ligne, j'ai essayé d'implémenter un générateur de guid dans Hibernate, en implémentant l'interface IdentifierGenerator et en utilisant un générateur basé sur le time avec JUG ( http://wiki.fasterxml.com/JugHome ).

Le code qui génère l'identifiant (je pensais séquentiel) est le suivant:

 Ssortingng uuid = null; EthernetAddress nic = EthernetAddress.fromInterface(); TimeBasedGenerator uuidGenerator = Generators.timeBasedGenerator(nic); uuid = uuidGenerator.generate().toSsortingng(); 

Et j'ai changé en conséquence la cartographie à ceci:

 <id name="id" column="id" type="ssortingng"> <generator class="my_package.hibernate.CustomSequentialGuidGenerator"> </generator> </id> 

Puis j'ai essayé de générer des uuids de test pour tester leur séquentialité (séquentielle à la manière uniqueidentifier, donc binary), c'est une list courte (chaque élément est généré avant les successifs):

 314a9a1b-6295-11e5-8d2c-2c27d7e1614f 3d867801-6295-11e5-ae09-2c27d7e1614f 4434ac7d-6295-11e5-9ed1-2c27d7e1614f 491462c4-6295-11e5-af81-2c27d7e1614f 5389ff4c-6295-11e5-84cf-2c27d7e1614f 57098959-6295-11e5-b203-2c27d7e1614f 5b62d144-6295-11e5-9883-2c27d7e1614f 

Cela me regarde comme séquentiel alphabétique, mais pas séquentiel binary.

Le test ci-dessus a été fait en exécutant sept fois l'application de test, ce n'était pas une boucle.

J'ai essayé d'insert ces valeurs dans une colonne déclarée comme identifiant unique et après avoir émis un select sur cette colonne, ceci est la list des sorties du server sql:

 5389FF4C-6295-11E5-84CF-2C27D7E1614F 314A9A1B-6295-11E5-8D2C-2C27D7E1614F 5B62D144-6295-11E5-9883-2C27D7E1614F 4434AC7D-6295-11E5-9ED1-2C27D7E1614F 3D867801-6295-11E5-AE09-2C27D7E1614F 491462C4-6295-11E5-AF81-2C27D7E1614F 57098959-6295-11E5-B203-2C27D7E1614F 

Donc, je ne comprends vraiment pas ce que je devrais faire et si je peux utiliser JUG comme un générateur de guidage séquentiel pour éviter mes problèmes de fragmentation.

C'est un autre test JUG, j'ai essayé 3 runs à chaque fois en générant 10 guids avec une boucle:

Exécuter 1

 54bd156e-62a2-11e5-a1a7-2c27d7e1614f 54c3cc2f-62a2-11e5-a1a7-2c27d7e1614f 54caf820-62a2-11e5-a1a7-2c27d7e1614f 54d1aee1-62a2-11e5-a1a7-2c27d7e1614f 54d901e2-62a2-11e5-a1a7-2c27d7e1614f 54df9193-62a2-11e5-a1a7-2c27d7e1614f 54e64854-62a2-11e5-a1a7-2c27d7e1614f 54ecff15-62a2-11e5-a1a7-2c27d7e1614f 54f3b5d6-62a2-11e5-a1a7-2c27d7e1614f 54fa4587-62a2-11e5-a1a7-2c27d7e1614f 

Exécuter 2

 87c66bcc-62a2-11e5-8e7c-2c27d7e1614f 87ccd46d-62a2-11e5-8e7c-2c27d7e1614f 87d3641e-62a2-11e5-8e7c-2c27d7e1614f 87d97e9f-62a2-11e5-8e7c-2c27d7e1614f 87e05c70-62a2-11e5-8e7c-2c27d7e1614f 87e6ec21-62a2-11e5-8e7c-2c27d7e1614f 87ed7bd2-62a2-11e5-8e7c-2c27d7e1614f 87f40b83-62a2-11e5-8e7c-2c27d7e1614f 87fac244-62a2-11e5-8e7c-2c27d7e1614f 880103d5-62a2-11e5-8e7c-2c27d7e1614f 

Exécuter 3

 a4b690db-62a2-11e5-b667-2c27d7e1614f a4bcd26c-62a2-11e5-b667-2c27d7e1614f a4c2eced-62a2-11e5-b667-2c27d7e1614f a4c92e7e-62a2-11e5-b667-2c27d7e1614f a4cf48ff-62a2-11e5-b667-2c27d7e1614f a4d5d8b0-62a2-11e5-b667-2c27d7e1614f a4dc6861-62a2-11e5-b667-2c27d7e1614f a4e34632-62a2-11e5-b667-2c27d7e1614f a4e9d5e3-62a2-11e5-b667-2c27d7e1614f a4f101d4-62a2-11e5-b667-2c27d7e1614f 

Exécuter 4

 c2b872b2-62a2-11e5-b855-2c27d7e1614f c2c17363-62a2-11e5-b855-2c27d7e1614f c2c82a24-62a2-11e5-b855-2c27d7e1614f c2ce92c5-62a2-11e5-b855-2c27d7e1614f c2d57096-62a2-11e5-b855-2c27d7e1614f c2dc2757-62a2-11e5-b855-2c27d7e1614f c2e32c38-62a2-11e5-b855-2c27d7e1614f c2e9bbe9-62a2-11e5-b855-2c27d7e1614f c2f099ba-62a2-11e5-b855-2c27d7e1614f c2f7507b-62a2-11e5-b855-2c27d7e1614f 

Exécuter 5

 f0263d1b-62a2-11e5-8529-2c27d7e1614f f02d1aec-62a2-11e5-8529-2c27d7e1614f f033d1ad-62a2-11e5-8529-2c27d7e1614f f03a615e-62a2-11e5-8529-2c27d7e1614f f041181f-62a2-11e5-8529-2c27d7e1614f f047a7d0-62a2-11e5-8529-2c27d7e1614f f04dc251-62a2-11e5-8529-2c27d7e1614f f05403e2-62a2-11e5-8529-2c27d7e1614f f05a6c83-62a2-11e5-8529-2c27d7e1614f f0608704-62a2-11e5-8529-2c27d7e1614f 

Run 6 (Commencé à partir de 0 à nouveau)

 00fd4ec3-62a3-11e5-8ab8-2c27d7e1614f 01042c94-62a3-11e5-8ab8-2c27d7e1614f 010b3175-62a3-11e5-8ab8-2c27d7e1614f 0111e836-62a3-11e5-8ab8-2c27d7e1614f 0118ed17-62a3-11e5-8ab8-2c27d7e1614f 011fcae8-62a3-11e5-8ab8-2c27d7e1614f 0126a8b9-62a3-11e5-8ab8-2c27d7e1614f 012d115a-62a3-11e5-8ab8-2c27d7e1614f 0133c81b-62a3-11e5-8ab8-2c27d7e1614f 013a30bc-62a3-11e5-8ab8-2c27d7e1614f 

Les groupes individuels sont classés par ordre alphabétique (mais pas binary) et pris dans les différentes séries dans leur set, ils ne sont pas classés par ordre alphabétique (soupir).

Qu'est-ce que je rate?

************************* EDIT – Description de ma mise en œuvre ******************

Après les différents commentaires et réponses j'ai mis en place la stratégie suivante:

J'ai généré mon propre guids séquentiel (basé sur l'horodatage actuel) et c'est la class du générateur:

 package it.hibernate; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; import org.apache.commons.lang.RandomSsortingngUtils; import org.hibernate.HibernateException; import org.hibernate.engine.SessionImplementor; import org.hibernate.id.IdentifierGenerator; public class CustomSequentialGuidGenerator implements IdentifierGenerator{ @Override public Serializable generate(SessionImplementor session, Object object) throws HibernateException { Ssortingng uuid = null; try { Date data = new Date(); SimpleDateFormat sdf = new SimpleDateFormat(); Ssortingng rand = RandomSsortingngUtils.randomAlphanumeric(12); sdf.applyPattern("yyyy"); Ssortingng year = sdf.format(data); sdf.applyPattern("MM"); Ssortingng month = sdf.format(data); sdf.applyPattern("dd"); Ssortingng day = sdf.format(data); sdf.applyPattern("HH"); Ssortingng hour = sdf.format(data); sdf.applyPattern("mm"); Ssortingng mins = sdf.format(data); sdf.applyPattern("ss"); Ssortingng secs = sdf.format(data); sdf.applyPattern("SSS"); Ssortingng millis = sdf.format(data); //G carachter is used to insert the rows after uuid = "GG" + year + month + "-" + day + hour + "-" + mins + secs + "-" + "0" + millis + "-" + rand; } catch (Exception exception) { exception.printStackTrace(); } return uuid; } } 

Vous pouvez noter que toutes les lignes commencent par la string 'GG' parce que je devais m'assurer que toutes les nouvelles lignes seraient insérées après toutes les anciennes lignes générées via select newid() . Après cela, il y a l'horodatage actuel et 12 caractères randoms pour éviter la collision dans le cas d'une insertion de plusieurs lignes dans la même milliseconde.

Après un test de 2000, insert la fragmentation de l'indice de key primaire a chuté de 17,92% à 0,15%.

NB Le type de données que j'ai réintroduit est de nouveau un varchar (36) et non un identifiant unique, donc les lignes sont sortingées par ordre alphabétique.

L'option par défaut de newsequentialid() n'a bien sûr pas fonctionné, car Hibernate n'utilise pas la valeur par défaut, elle définit toujours une valeur émise par son générateur.

En jetant un coup d'œil à la bibliothèque JUG, il semble qu'elle n'offre aucun moyen de générer des GUID de manière séquentielle. Je ne sais pas pourquoi vous pensiez que la méthode generate() du générateur obtenue via Generators.timeBasedGenerator() vous donnerait des GUID séquentiels. Un générateur basé sur le time est simplement un générateur qui prend en count l' heure actuelle lors de la génération des GUID, mais il est libre de modifier la coordonnée temporelle actuelle comme il le souhaite lors de l'intégration dans le GUID, ce qui ne garantit pas être quelque chose de séquentiel sur les GUID résultant.

Généralement, les termes "GUID" et "séquentiel" sont incompatibles entre eux. Vous pouvez avoir des keys qui sont des GUID, ou des keys qui sont séquentielles, mais dans des circonstances normales, vous ne pouvez pas avoir les deux.

Alors, êtes-vous sûr que les keys doivent être des GUID? Personnellement, je trouve très difficile de travailler avec les GUID.

Mais si vous devez faire des hacks nécessaires pour avoir des GUID séquentiels, alors ma recommandation serait d'écrire votre propre fonction qui génère des strings de 36 caractères qui ressemblent à des GUID, mais qui sont séquentielles.

La partie séquentielle devrait provenir d'une SEQUENCE , qui émet simplement des entiers séquentiels. (Je crois que MS-SQL-Server les supporte.)

Vous pouvez lire la spécification UUID de l' IETF sur la façon de build des GUID correctement, mais vous n'avez pas à le suivre à la lettre. Pour la plupart, si cela ressemble simplement à un GUID, c'est assez bon.

Si vous pouvez avoir une seule séquence globale pour cela, c'est bien. Si vous ne pouvez pas avoir une seule séquence globale, vous devez d'une manière ou d'une autre identifier vos séquences, puis prendre en count l'identifiant de chaque séquence lors de la génération de vos GUID. (Ce serait "l'identifiant de noeud" mentionné dans la documentation de l'IETF.)

J'ai eu une fois l'exigence déraisonnable que les lignes que je devais transmettre à un certain service web devaient être identifiées par des GUID, et il y avait trop de paperasserie qui m'empêchait de les contacter pour leur requestr «êtes-vous sérieux»? " donc je viens de transmettre des GUID comme suit:

 |--- random part -----| |-- key ---| 314a9a1b-6295-11e5-8d2c-000000000001 314a9a1b-6295-11e5-8d2c-000000000002 314a9a1b-6295-11e5-8d2c-000000000003 314a9a1b-6295-11e5-8d2c-000000000004 314a9a1b-6295-11e5-8d2c-000000000005 ... 

Ils n'ont pas dit un mot.