Meilleure solution pour fixer la conception de la base de données avec GUID comme clé primaire

18

Je suis après une confirmation de cette idée pour corriger une base de données mal performante ou une meilleure suggestion si quelqu'un en a une. Toujours ouvert à de meilleures suggestions.

J'ai une très grande base de données (plus de 20 millions d'enregistrements augmentant d'environ 1/2 million par jour) qui utilise le GUID comme PK.

Un oubli de ma part mais le PK est clusterisé sur le serveur SQL et cause des problèmes de performances.

La raison d'un GUID - cette base de données est partiellement synchronisée avec 150 autres bases de données, de sorte que le PK devait être unique. La synchronisation n'est pas gérée par SQL Server, il existe plutôt un processus personnalisé construit qui maintient les données synchronisées pour les exigences du système - toutes basées sur ce GUID.

Chacune des 150 bases de données distantes ne stocke pas les données complètes telles qu'elles sont stockées dans la base de données SQL centrale. ils ne stockent qu'un sous-ensemble des données dont ils ont réellement besoin, et les données dont ils ont besoin ne leur sont pas uniques (10 des 150 bases de données peuvent avoir certains des mêmes enregistrements des bases de données d'autres sites par exemple - ils partagent). De plus - les données sont en fait générées sur les sites distants - pas au point central - d'où la nécessité des GUID.

La base de données centrale est utilisée non seulement pour tout synchroniser, mais les requêtes de plus de 3000 utilisateurs seront exécutées sur cette très grande base de données fragmentée. C'est déjà un gros problème lors des tests initiaux.

Heureusement, nous ne sommes pas encore en direct - je peux donc apporter des modifications et mettre les choses hors ligne si nécessaire, ce qui est au moins quelque chose.

Les performances des bases de données distantes ne sont pas un problème - les sous-ensembles de données sont assez petits et la base de données ne dépasse généralement jamais 1 Go au total. Les enregistrements sont renvoyés au système principal assez régulièrement et supprimés des BD plus petits lorsqu'ils ne sont plus nécessaires.

Les performances de la base de données centrale qui est la gardienne de tous les enregistrements sont lamentables - en raison d'un GUID en cluster comme clé primaire pour autant d'enregistrements. La fragmentation de l'indice est hors des graphiques.

Donc - mes réflexions pour résoudre le problème de performances sont de créer une nouvelle colonne - Unsigned BIGINT IDENTITY (1,1), puis de changer le cluster cluster de la colonne BIGINT de la table.

Je créerais un index unique non groupé sur le champ GUID qui était la clé primaire.

Les 150 bases de données distantes plus petites n'ont pas besoin de connaître le nouveau PK sur la base de données Central SQL Server - il sera purement utilisé pour organiser les données dans la base de données et arrêter les mauvaises performances et la fragmentation.

Est-ce que cela fonctionnerait et améliorerait les performances de la base de données SQL centrale et empêcherait un futur enfer de fragmentation d'index (dans une certaine mesure)? ou ai-je raté quelque chose de très important ici qui va me sauter et me mordre et causer encore plus de chagrin?

Roddles
la source
2
@mattytommo Je suis d'accord.
Paul Fleming
2
Exécutez-vous une défragmentation d'index au moins une fois par semaine?
Andomar
1
Avez-vous quelque chose de significatif à regrouper? C'est-à-dire, quelle requête devrait être rapide? Ce ne sera certainement pas une analyse de plage sur le guid, donc au lieu de simplement choisir un auto-incrémentation, demandez-vous s'il y a un clustering optimal au moment de la requête que vous pouvez choisir. Sinon, allez-y et utilisez le bigint
2
@Borik Ce n'est pas une bonne idée, d'après ce qu'il a et son taux de croissance, il s'épuiserait inten 4255 jours (11,5 ans). S'il l'a fait, il ne vous en voudrait que dans 11,5 ans;)
mattytommo
1
Un avis contraire: Pourquoi pensez-vous que le type de données GUID est un problème? Il s'agit d'un entier de 128 bits. Pourquoi pensez-vous que le remplacer par un entier 64 bits (bigint) ou un entier 32 bits (int) va faire une différence notable de vitesse? Je pense que vous devriez définitivement changer la clé de clustering pour autre chose, pour éviter tout fractionnement de page qui mène à la fragmentation, mais je ne pense pas que vous devriez changer le type de données à moins que vous ne soyez très sûr que le type de données est le problème.
Greenstone Walker

Réponses:

8

Vous n'avez certainement pas besoin de cluster sur le GUID. Si vous avez quelque chose qui vous permettrait d'identifier de manière unique des enregistrements autres que ce GUID, je vous suggère de chercher à créer un index unique sur cet autre champ et à rendre cet index en cluster. Sinon, vous êtes libre de regrouper sur d'autres champs, même en utilisant des index non uniques. L'approche qu'il y aurait à regrouper facilite cependant le fractionnement de vos données et l'interrogation - donc, si vous avez un champ "région", ou quelque chose, cela pourrait être un candidat pour votre schéma de clustering.

Le problème avec le passage à a BIGINTserait des ajouts aux données d'autres bases de données et l'intégration de leur base de données dans le magasin central. Si ce n'est pas une considération - et ne le sera jamais - alors, oui, BIGINTcela résoudrait bien le problème de rééquilibrage des indices.

En arrière-plan, si vous ne spécifiez pas d'index clusterisé, SQL Server fait à peu près la même chose: il crée un champ d'ID de ligne et mappe tous les autres index dans celui-ci. Donc, en le faisant vous-même, vous le résolvez comme SQL le résoudrait.

David T. Macknet
la source
Le seul champ véritablement unique dans le tableau est le GUD - les autres colonnes ne sont pas uniques et il y a des combinaisons de colonnes ensemble qui peuvent être uniques pour commencer - mais avec le temps il y a une légère chance qu'elles génèrent un enregistrement en double. Très éloigné mais c'est possible compte tenu de la nature des données. J'ai lu que tous les autres index non clusterisés font référence à l'index cluster pour améliorer les performances de recherche, etc. Un PK en cluster comme GUID n'aurait-il pas un impact sur les performances? Je suis conscient de l'espace et même si une préoccupation - la performance est primordiale.
Roddles
Le problème de performances, si vous ne spécifiez pas d'index clusterisé, est que SQL en créera un en arrière-plan pour vous et mappera tous les autres index dans celui-ci. Donc, dans votre cas, vous obtiendrez une amélioration des performances en laissant SQL faire cela, car en ce moment, vous mélangez constamment toutes vos données sur le disque pour conserver l'ordre de tri lorsque l'ordre de tri n'est pas important. Vous aurez besoin de plus d'espace de stockage, mais vous constaterez une amélioration considérable du stockage et un impact minimal / nul sur la récupération.
David T.Macknet
Donc, la question est, je suppose, si je ne fais pas le BIGINT Clustered PK, et que je change simplement le PK en un GUID non clusterisé, quelles sont les implications en termes de performances? Il existe d'autres index non cluster sur la table qui seront recherchés fréquemment. Cela aurait-il un impact sur les performances de ces recherches?
Roddles
+1 Je suggère également de rester avec les GUID. Il est très difficile de les remplacer dans des systèmes distribués. Votre index cluster de grande table doit être évident en fonction de la façon dont vous interrogez les données.
Remus Rusanu
1
Salut les gars - Juste une mise à jour - J'ai apporté les modifications et fait du PK un GUID non clusterisé et SQL Server est en train d'insérer les 2 millions et plus d'enregistrements dans la base de données. En même temps que les données étaient insérées, j'ai pu interroger la base de données pour des informations et des requêtes qui, parfois avant le changement, expiraient à 10 minutes, terminées en une à deux secondes. Donc, le fait de rendre le PK non cluster et de ne pas se soucier du BIGINT semble avoir bien fonctionné. Merci beaucoup pour la contribution et l'assistance de chacun.
Roddles
1

C'est un défi de taille.

Permettez-moi de suggérer une approche intermédiaire.

J'avais des problèmes avec System.Guid.NewGuid () générant des guides aléatoires. (Je permettais au client de créer son propre guide, au lieu de s'appuyer sur la base de données pour créer un séquentiel).

Une fois que je suis passé à un UuidCreateSequential côté client, mes performances sont devenues BEAUCOUP meilleures, en particulier sur INSERT.

Voici le code client vaudou de DotNet. Je suis sûr d'avoir mis en gage quelque part:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;


namespace MyCompany.MyTechnology
{
  public static class Guid
  {


    [DllImport("rpcrt4.dll", SetLastError = true)]
    static extern int UuidCreateSequential(out System.Guid guid);


    public static System.Guid NewGuid()
    {
      return CreateSequentialUUID();
    }


    public static System.Guid CreateSequentialUUID()
    {
      const int RPC_S_OK = 0;
      System.Guid g;
      int hr = UuidCreateSequential(out g);
      if (hr != RPC_S_OK)
        throw new ApplicationException("UuidCreateSequential failed: " + hr);
      return g;
    }


  }
}














    /*

Original Reference for Code:
http://www.pinvoke.net/default.aspx/rpcrt4/UuidCreateSequential.html


*/

/*



Text From URL above:

UuidCreateSequential (rpcrt4)

Type a page name and press Enter. You'll jump to the page if it exists, or you can create it if it doesn't.
To create a page in a module other than rpcrt4, prefix the name with the module name and a period.
. Summary
Creates a new UUID 
C# Signature:
[DllImport("rpcrt4.dll", SetLastError=true)]
static extern int UuidCreateSequential(out Guid guid);


VB Signature:
Declare Function UuidCreateSequential Lib "rpcrt4.dll" (ByRef id As Guid) As Integer


User-Defined Types:
None.

Notes:
Microsoft changed the UuidCreate function so it no longer uses the machine's MAC address as part of the UUID. Since CoCreateGuid calls UuidCreate to get its GUID, its output also changed. If you still like the GUIDs to be generated in sequential order (helpful for keeping a related group of GUIDs together in the system registry), you can use the UuidCreateSequential function.

CoCreateGuid generates random-looking GUIDs like these:

92E60A8A-2A99-4F53-9A71-AC69BD7E4D75
BB88FD63-DAC2-4B15-8ADF-1D502E64B92F
28F8800C-C804-4F0F-B6F1-24BFC4D4EE80
EBD133A6-6CF3-4ADA-B723-A8177B70D268
B10A35C0-F012-4EC1-9D24-3CC91D2B7122



UuidCreateSequential generates sequential GUIDs like these:

19F287B4-8830-11D9-8BFC-000CF1ADC5B7
19F287B5-8830-11D9-8BFC-000CF1ADC5B7
19F287B6-8830-11D9-8BFC-000CF1ADC5B7
19F287B7-8830-11D9-8BFC-000CF1ADC5B7
19F287B8-8830-11D9-8BFC-000CF1ADC5B7



Here is a summary of the differences in the output of UuidCreateSequential:

The last six bytes reveal your MAC address 
Several GUIDs generated in a row are sequential 
Tips & Tricks:
Please add some!

Sample Code in C#:
static Guid UuidCreateSequential()
{
   const int RPC_S_OK = 0;
   Guid g;
   int hr = UuidCreateSequential(out g);
   if (hr != RPC_S_OK)
     throw new ApplicationException
       ("UuidCreateSequential failed: " + hr);
   return g;
}



Sample Code in VB:
Sub Main()
   Dim myId As Guid
   Dim code As Integer
   code = UuidCreateSequential(myId)
   If code <> 0 Then
     Console.WriteLine("UuidCreateSequential failed: {0}", code)
   Else
     Console.WriteLine(myId)
   End If
End Sub




*/

IDÉE ALTERNATIVE:

Si votre base de données principale et base de données distante sont "liées" (comme dans sp_linkserver) ...... alors vous pouvez utiliser la base de données principale comme "générateur uuid".

Vous ne voulez pas obtenir "un par un" uuid, c'est trop de bavardage.

Mais vous pourriez attraper un ensemble d'uuid.

Ci-dessous est un code:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id =
 OBJECT_ID(N'[dbo].[uspNewSequentialUUIDCreateRange]') AND type in (N'P',
 N'PC'))

 DROP PROCEDURE [dbo].[uspNewSequentialUUIDCreateRange]

 GO



 CREATE PROCEDURE [dbo].[uspNewSequentialUUIDCreateRange] (

 @newUUIDCount int --return

 )

 AS

 SET NOCOUNT ON

 declare @t table ( dummyid int , entryid int identity(1,1) , uuid
 uniqueidentifier default newsequentialid() )

 insert into @t ( dummyid ) select top (@newUUIDCount) 0 from dbo.sysobjects
 so with (nolock)

 select entryid , uuid from @t

 SET NOCOUNT OFF

 GO

/ *

--START TEST

 set nocount ON

 Create Table #HolderTable (entryid int , uuid uniqueidentifier )

 declare @NewUUIDCount int

 select @NewUUIDCount = 20

 INSERT INTO #HolderTable EXEC dbo.uspNewSequentialUUIDCreateRange
 @NewUUIDCount

 select * from #HolderTable

 DROP Table #HolderTable

 --END TEST CODE

* /

granadaCoder
la source
Intéressant - et approche que je n'avais pas envisagée - j'examinerai cela de plus près car cela a l'air bien et exécutera quelques projets de test. Si nous avions 150 bases de données générant des guides séquentiels qui sont renvoyés à la base de données centrale, cela ne provoquerait-il pas encore une fragmentation car les guides seraient toujours assez aléatoires lorsqu'ils seraient insérés dans la base de données centrale. À moins bien sûr que vous vouliez supprimer le PK en cluster et avoir le PK non en cluster?
Roddles
Les 150 bases de données «distantes» en insèrent-elles une à la fois? Ou déplacent-ils des données en vrac la nuit ou quelque chose? Vous êtes donc un peu entre un rocher et un endroit dur. L'utilisation de bigint finira par manquer de place (peut-être) et vous devrez toujours obtenir une valeur unique sur les nombreuses bases de données. Voici donc mon idée radicale. Les 150 bases de données distantes peuvent-elles obtenir leurs UUID à partir d'un service central? Voilà une idée. Les 150 bases de données distantes sont-elles "liées" (comme dans sp_addlinkedserver) à la base de données principale? Ensuite, j'ai un UDF qui pourrait être envisagé. Laissez-moi voir si je peux le trouver.
granadaCoder
Voici un article qui parle de sequentialid (non liés à ce que je l' ai déjà écrit, je pense que son intéressant) codeproject.com/Articles/388157/...
granadaCoder
0

En fonction de votre description, optez pour BIGINT. Cependant, l'index du GUID peut être non unique, car les GUID sont censés être globalement uniques de toute façon.

Jimbo
la source
-1

Si le GUID est stocké correctement en tant qu'identificateur unique, il ne devrait pas y avoir de problèmes de performances ... et si vous pouvez utiliser le GUID séquentiel encore mieux ...

@Mattytommo a également un bon point sur 11,5 ans avec l'utilisation de INT ...

Borik
la source
Oui - mais le guide est généré dans les 150 bases de données distantes, pas sur la base de données SQL Server - donc je ne peux pas utiliser sequentialguid - mais merci pour la réponse.
Roddles
Dans ce cas, votre plan est à mon avis solide, j'ai fait la même chose sur l'une des bases de données que je gère, j'ai créé une INT DENTITY (1,1) et je l'ai définie comme Clustered PK ainsi qu'un identifiant humainement lisible pour les données tirer vers le haut et j'ai gardé GUID (Index) comme tracker pour pouvoir retracer d'où il provient. Mais ma motivation était plus de gagner de l'espace ...
Borik
Merci beaucoup et très apprécié pour vos réponses et vos idées. :)
Roddles