La migliore soluzione per correggere la progettazione del database con GUID come chiave primaria


18

Sto cercando una conferma di questa idea per riparare un database mal funzionante o un suggerimento migliore se qualcuno ne ha uno. Sempre aperto a suggerimenti migliori.

Ho un database molto grande (20+ milioni di record in crescita di circa 1/2 milioni al giorno) che usano GUID come PK.

Una supervisione da parte mia, ma il PK è cluster su server SQL e sta causando problemi di prestazioni.

Il motivo di una guida: questo database è parzialmente sincronizzato con altri 150 database, quindi il PK doveva essere unico. La sincronizzazione non è gestita da SQL Server, ma esiste un processo personalizzato che mantiene i dati sincronizzati per i requisiti del sistema, il tutto basato su quel GUID.

Ognuno dei 150 database remoti non memorizza i dati completi come archiviati nel database SQL centrale. memorizzano solo un sottoinsieme dei dati effettivamente richiesti e i dati richiesti non sono univoci per loro (10 database su 150 possono avere alcuni degli stessi record di database di altri siti, ad esempio, condividono). Inoltre - i dati vengono effettivamente generati nei siti remoti - non nel punto centrale - da qui la necessità dei GUID.

Il database centrale viene utilizzato non solo per mantenere tutto sincronizzato, ma le query di oltre 3000 utenti verranno eseguite su quel database frammentato molto grande. Già questo è un grosso problema nei test iniziali.

Fortunatamente non siamo ancora in vita - quindi posso apportare modifiche e portare le cose offline se necessario, che è almeno qualcosa.

Le prestazioni dei database remoti non sono un problema: i sottoinsiemi di dati sono piuttosto piccoli e il database di solito non supera mai le dimensioni di 1 GB in totale. I record vengono inviati al sistema principale abbastanza regolarmente e rimossi dai BD più piccoli quando non sono più necessari.

Le prestazioni del DB centrale, che è il custode di tutti i record, sono deplorevoli, a causa di un GUID cluster come chiave primaria per quel numero di record. La frammentazione dell'indice è fuori dai grafici.

Quindi - il mio pensiero per risolvere il problema delle prestazioni è di creare una nuova colonna - Unsigned BIGINT IDENTITY (1,1) e quindi modificare il PK cluster della colonna BIGINT della tabella.

Vorrei creare un indice univoco non cluster sul campo GUID che era la chiave primaria.

I 150 database remoti più piccoli non hanno bisogno di conoscere il nuovo PK sul database Central SQL Server: verranno semplicemente utilizzati per organizzare i dati nel database e arrestare le prestazioni e la frammentazione non valide.

Funzionerebbe e migliorerebbe le prestazioni del database SQL centrale e impedirebbe in futuro l'inferno della frammentazione dell'indice (fino a un certo punto)? o ho perso qualcosa di molto importante qui che sta per saltare e mordermi e causare ancora più dolore?


2
@mattytommo Sono d'accordo.
Paul Fleming,

2
Stai eseguendo la deframmentazione dell'indice almeno una volta alla settimana?
Andomar,

1
Hai qualcosa di significativo su cui concentrarti? Cioè, quale query dovrebbe essere veloce? Sicuramente non ci sarà la scansione del range sul guid, quindi invece di scegliere solo un incremento automatico, considera se c'è un clustering ottimale per il tempo di query che puoi scegliere. In caso contrario, vai avanti e usa il bigint

2
@Borik Non un'ottima idea, in base a ciò che ha e al suo tasso di crescita, si esaurirebbe intin 4255 giorni (11,5 anni). Se lo facesse, ti
darebbe la

1
Una visione contraria: perché pensi che il tipo di dati GUID sia un problema? È un numero intero a 128 bit. Perché pensi che sostituirlo con un numero intero a 64 bit (bigint) o un numero intero a 32 bit (int) farà una notevole differenza di velocità? Penso che dovresti assolutamente cambiare la chiave di clustering in qualcos'altro, per evitare tutta la divisione della pagina che porta alla frammentazione, ma non penso che dovresti cambiare il tipo di dati a meno che tu non sia molto sicuro che il tipo di dati sia il problema.
Greenstone Walker,

Risposte:


8

Certamente non è necessario raggrupparsi nel GUID. Se hai qualcosa che ti consentirebbe di identificare in modo univoco record diversi da quel GUID, ti suggerirei di cercare di costruire un indice univoco su quell'altro campo e di raggruppare tale indice. Altrimenti, sei libero di raggrupparti su altri campi, anche usando indici non unici. L'approccio ci sarebbe di raggruppare, tuttavia facilita la suddivisione dei dati e l'interrogazione, quindi, se si dispone di un campo "regione" o qualcosa del genere, potrebbe essere un candidato per il proprio schema di raggruppamento.

Il problema con il passaggio a a BIGINTsarebbe l'aggiunta ai dati di altri database e l'integrazione del loro database nell'archivio centrale. Se questa non è una considerazione - e non sarà mai una considerazione - allora sì, BIGINTrisolverebbe bene il problema del ribilanciamento dell'indice.

Dietro le quinte, se non si specifica un indice cluster, SQL Server fa la stessa cosa: crea un campo ID riga e mappa tutti gli altri indici in quello. Quindi, facendolo da soli, lo risolvi proprio come lo risolverebbe SQL.


L'unico campo veramente unico nella tabella è il GUD - le altre colonne non sono uniche e ci sono combinazioni di colonne insieme che potrebbero essere uniche per cominciare - ma nel tempo c'è una leggera possibilità che generino un record duplicato. Molto remoto ma è possibile data la natura dei dati. Ho letto che tutti gli altri indici non cluster fanno riferimento all'indice cluster per migliorare le prestazioni di ricerca, ecc. Non avere un PK cluster come GUID causerebbe un impatto sulle prestazioni? Sono consapevole dello spazio e, sebbene sia preoccupante, le prestazioni sono di primaria importanza.
Roddles,

Il colpo di prestazione, se non si specifica un indice cluster, è che SQL ne creerà uno dietro le quinte e mapperà tutti gli altri indici in quello. Quindi, nel tuo caso, otterrai un miglioramento delle prestazioni lasciando che SQL lo faccia, perché in questo momento stai costantemente mescolando tutti i tuoi dati su disco per preservare l'ordinamento quando l'ordinamento non è importante. Avrai bisogno di più spazio di archiviazione, ma vedrai un notevole miglioramento nella memoria e un impatto minimo / nullo sul recupero.
David T. Macknet,

Quindi la domanda che suppongo sia se non eseguo il BIGINT Clustered PK e cambi semplicemente il PK in un GUID non cluster, quali sono le implicazioni sulle prestazioni? Ci sono altri indici non cluster sulla tabella che verranno cercati frequentemente. Ciò inciderebbe sulle prestazioni di tali ricerche?
Roddles,

+1 Vorrei anche suggerire di rimanere con i GUID. È molto difficile sostituirli nei sistemi distribuiti. L'indice cluster di tabelle di grandi dimensioni dovrebbe essere evidente in base alla modalità di query dei dati.
Remus Rusanu,

1
Salve ragazzi - Solo un aggiornamento - Ho apportato le modifiche e reso PK un non cluster su GUID e SQL Server è impegnato a inserire 2+ milioni di record nel database. Contemporaneamente all'inserimento dei dati, sono stato in grado di interrogare il database per informazioni e domande che a volte prima della modifica scadevano a 10 minuti, completate in 1-2 secondi. Quindi, rendere il PK non cluster e non preoccuparsi di BIGINT sembra aver funzionato bene. Mille grazie per il contributo e l'assistenza di tutti.
Roddles,

1

Questo è un ordine elevato.

Permettimi di suggerire un approccio intermedio.

Ho avuto problemi con System.Guid.NewGuid () generando guide casuali. (Stavo permettendo al client di creare il proprio guid, invece di fare affidamento sul database per creare un sequentialid).

Una volta passato a un UuidCreateSequential sul lato client, le mie prestazioni sono diventate MOLTO migliori, specialmente negli INSERTI.

Ecco il voodoo del codice client DotNet. Sono sicuro di essere stato impegnato da qualche parte:

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




*/

IDEA ALTERNATA:

Se il tuo db principale e il tuo db remoto sono "collegati" (come in, sp_linkserver) ...... allora potresti usare il db principale come "generatore di uuid".

Non vuoi ottenere "uno per uno" di uuid, è troppo chiacchiericcio.

Ma potresti prendere una serie di uuidi.

Di seguito è riportato un codice:

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

* /


Interessante - e un approccio che non avevo preso in considerazione - lo esaminerò più da vicino poiché sembra bello e realizzerò alcuni progetti di test. Se avessimo 150 database che generano guide sequenziali che vengono riportate al database centrale, ciò non causerebbe comunque la frammentazione poiché le guide sarebbero comunque casuali se inserite nel database centrale. A meno che, naturalmente, non intenda eliminare il PK cluster e avere il PK non cluster?
Roddles,

I 150 database "remoti" ne stanno inserendo uno alla volta? Oppure spostano i dati in serie durante la notte o qualcosa del genere? Quindi sei un po 'tra una roccia e un luogo difficile. L'uso di bigint alla fine esaurirà lo spazio (forse) e dovrai comunque ottenere un valore unico tra i molti db. Quindi ecco la mia idea radicale. I 150 database remoti possono ottenere i loro UUID da un servizio centrale? Questa è un'idea. I 150 database remoti sono "collegati" (come in sp_addlinkedserver) al database principale? Quindi ho un UDF che potrebbe essere considerato. Fammi vedere se riesco a trovarlo.
granadaCoder

Ecco un articolo che parla di sequentialid (non correlato a quello che ho già scritto, penso sia interessante) codeproject.com/Articles/388157/…
granadaCoder

0

Sulla base della tua descrizione, scegli BIGINT. Tuttavia, l'indice per GUID può essere non univoco, poiché i GUID dovrebbero essere comunque globalmente univoci.


-1

Se il GUID è memorizzato correttamente come uniqueidentifier non dovrebbe avere problemi di performace ... e se puoi usare il GUID sequenziale ancora meglio ...

Anche @mattytommo ha un buon punto circa 11,5 anni con l'uso di INT ...


Sì, ma il guid viene generato nei 150 database remoti, non nel database SQL Server, quindi non posso usare la guida sequenziale, ma grazie per la risposta.
Roddles,

In tal caso, secondo me il tuo piano è valido, ho fatto una cosa simile su uno dei DB che gestisco, ho creato un INT DENTITY (1,1) e l'ho impostato come Clustered PK e identificatore umano leggibile per i dati pull up e ho mantenuto GUID (Index) come tracker per essere in grado di rintracciare dove ha avuto origine. Ma la mia motivazione era più dal risparmio di spazio ...
Borik

Mille grazie e molto apprezzato per le tue risposte e approfondimenti. :)
Roddles,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.