La mejor solución para arreglar el diseño de la base de datos con GUID como clave principal

18

Estoy después de una confirmación de esta idea para arreglar una base de datos de bajo rendimiento o una mejor sugerencia si alguien tiene una. Siempre abierto a mejores sugerencias.

Tengo una base de datos muy grande (más de 20 millones de registros que crecen aproximadamente 1/2 millón por día) que usan GUID como PK.

Un descuido de mi parte, pero el PK está agrupado en el servidor SQL y está causando problemas de rendimiento.

El motivo de un guid: esta base de datos está parcialmente sincronizada con otras 150 bases de datos, por lo que la PK debe ser única. La sincronización no es administrada por SQL Server, sino que hay un proceso personalizado creado que mantiene los datos sincronizados para los requisitos del sistema, todo basado en ese GUID.

Cada una de las 150 bases de datos remotas no almacena los datos completos como se almacenan en la base de datos central de SQL. solo almacenan un subconjunto de los datos que realmente requieren, y los datos que requieren no son exclusivos para ellos (10 de las 150 bases de datos pueden tener algunos de los mismos registros de las bases de datos de otros sitios, por ejemplo, comparten). Además, los datos se generan en los sitios remotos, no en el punto central, de ahí la necesidad de los GUID.

La base de datos central se usa no solo para mantener todo sincronizado, sino que las consultas de más de 3000 usuarios se ejecutarán en esa gran base de datos fragmentada. Este ya es un gran problema en las pruebas iniciales.

Afortunadamente, todavía no estamos en vivo, por lo que puedo hacer cambios y desconectar las cosas si es necesario, que es al menos algo.

El rendimiento de las bases de datos remotas no es un problema: los subconjuntos de datos son bastante pequeños y la base de datos generalmente nunca supera el tamaño de 1 GB en total. Los registros se retroalimentan al sistema principal con bastante regularidad y se eliminan de los BD más pequeños cuando ya no son necesarios.

El rendimiento de la base de datos central que es el guardián de todos los registros es lamentable, debido a un GUID agrupado como clave principal para esa cantidad de registros. La fragmentación del índice está fuera de los gráficos.

Entonces, mi intención de solucionar el problema de rendimiento es Crear una nueva columna: IDENTIDAD BIGINT sin firmar (1,1) y luego cambiar el PK agrupado de la columna BIGINT de la tabla.

Crearía un índice único no agrupado en el campo GUID, que era la clave principal.

Las 150 bases de datos remotas más pequeñas no necesitan saber acerca de la nueva PK en la base de datos del Servidor SQL Central: se utilizará exclusivamente para organizar los datos en la base de datos y detener el mal rendimiento y la fragmentación.

¿Funcionaría y mejoraría el rendimiento de la base de datos central de SQL y evitaría un futuro infierno de fragmentación del índice (hasta cierto punto)? ¿O me he perdido algo muy importante aquí que va a saltar y morderme y causar aún más dolor?

Roddles
fuente
2
@mattytommo Estoy de acuerdo.
Paul Fleming
2
¿Está ejecutando la desfragmentación de índice al menos una vez por semana?
Andomar
1
¿Tienes algo significativo para agrupar? Es decir, ¿qué consulta debería ser rápida? Definitivamente no va a ser un escaneo de rango en el guid, por lo que en lugar de simplemente elegir un aumento automático, considere si hay algún clúster óptimo en el tiempo de consulta que pueda elegir. Si no, entonces usa el bigint
2
@Borik No es una gran idea, basado en lo que tiene y su tasa de crecimiento, se agotaría inten 4255 días (11.5 años). Si lo hiciera, solo te culparía en 11.5 años;)
mattytommo
1
Una opinión contraria: ¿Por qué crees que el tipo de datos GUID es un problema? Es un entero de 128 bits. ¿Por qué cree que reemplazarlo con un número entero de 64 bits (bigint) o un número entero de 32 bits (int) va a hacer una diferencia notable en la velocidad? Creo que definitivamente debería cambiar la clave de agrupación a otra cosa, para evitar toda la división de páginas que conduzca a la fragmentación, pero no creo que deba cambiar el tipo de datos a menos que esté muy seguro de que el tipo de datos es el problema.
Greenstone Walker

Respuestas:

8

Ciertamente, NO necesita agruparse en el GUID. Si tiene algo que le permitiría identificar de manera única los registros que no sean ese GUID, le sugiero que busque construir un índice único en ese otro campo y agrupar ese índice. De lo contrario, puede agruparse en otros campos, incluso utilizando índices no únicos. Sin embargo, el enfoque sería agrupar, sin embargo, facilita la división de sus datos y las consultas, por lo que, si tiene un campo de "región", o algo así, podría ser un candidato para su esquema de agrupación.

El problema con el cambio a a BIGINTsería adiciones a los datos de otras bases de datos e integración de su base de datos en la tienda central. Si esto no es una consideración, y nunca será una consideración, entonces sí BIGINTresolvería el problema del reequilibrio del índice muy bien.

Detrás de escena, si no especifica un índice agrupado, SQL Server hace lo mismo: crea un campo de ID de fila y asigna todos los demás índices a ese. Entonces, al hacerlo usted mismo, lo está resolviendo tal como lo resolvería SQL.

David T. Macknet
fuente
El único campo genuinamente único en la tabla es el GUD: las otras columnas no son únicas y hay combinaciones de columnas juntas que pueden ser únicas para comenzar, pero con el tiempo hay una pequeña posibilidad de que generen un registro duplicado. Muy remoto pero es posible dada la naturaleza de los datos. He leído que todos los demás índices no agrupados hacen referencia al índice agrupado para mejorar el rendimiento de búsqueda, etc. ¿No tener un PK agrupado como GUID causaría un impacto en el rendimiento? Soy consciente del espacio y, aunque me preocupa, el rendimiento es primordial.
Roddles
El impacto en el rendimiento, si no especifica un índice agrupado, es que SQL creará uno detrás de escena y asignará todos los demás índices en ese. Entonces, en su caso, obtendría una mejora en el rendimiento al permitir que SQL lo haga, porque en este momento está barajando constantemente todos sus datos en el disco para preservar el orden cuando el orden no es importante. Necesitará más espacio de almacenamiento, pero verá una gran mejora en el almacenamiento y un impacto mínimo / nulo en la recuperación.
David T. Macknet
Entonces, la pregunta que supongo es que si no hago la PK agrupada de BIGINT y solo cambio la PK a un GUID no agrupado, ¿cuáles son las implicaciones de rendimiento? Hay otros índices no agrupados en la tabla que se buscarán con frecuencia. ¿Afectaría esto al rendimiento de esas búsquedas?
Roddles
+1 También sugeriría permanecer con los GUID. Es muy difícil reemplazarlos en sistemas distribuidos. Su índice agrupado de tabla grande debe ser evidente en función de cómo consulta los datos.
Remus Rusanu
1
Hola, muchachos: solo una actualización: realicé las modificaciones y convertí el PK en un No agrupado en GUID y el servidor SQL está ocupado insertando los más de 2 millones de registros en la base de datos. Al mismo tiempo que se insertaban los datos, pude consultar la base de datos para obtener información y las consultas que, en ocasiones, antes del cambio, expiraron a los 10 minutos, completadas en cuestión de 1-2 segundos. Entonces, hacer que el PK no esté agrupado y no preocuparse por el BIGINT parece haber funcionado bien. Muchas gracias por el aporte y asistencia de todos.
Roddles
1

Esa es una tarea difícil.

Permítanme sugerir un enfoque de intermediario.

Estaba teniendo problemas con System.Guid.NewGuid () generando guías aleatorias. (Estaba permitiendo que el cliente creara su propia guía, en lugar de confiar en la base de datos para crear una secuencial).

Una vez que me mudé a un UuidCreateSequential en el lado del cliente, mi rendimiento mejoró MUCHO, especialmente en INSERT.

Aquí está el código de cliente DotNet vudú. Estoy seguro de que empeñé desde algún lado:

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 ALTERNATIVA:

Si su base de datos principal y las bases de datos remotas están "vinculadas" (como en, sp_linkserver) ... entonces podría usar la base de datos principal como el "generador de uuid".

No quieres obtener el "uno por uno" de uuid, eso es demasiado charlatanería.

Pero podrías tomar un juego de uuid.

A continuación hay un código:

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
fuente
Interesante, y enfoque que no había considerado, lo examinaré más de cerca, ya que se ve bien y ejecutaré algunos proyectos de prueba. Si tuviéramos 150 bases de datos que generan guías secuenciales que se informan a la base de datos central, esto no causaría fragmentación, ya que las guías serían bastante aleatorias cuando se inserten en la base de datos central. ¿A menos que, por supuesto, se refiera a soltar la PK agrupada y tener la PK no agrupada?
Roddles
¿Se están insertando las 150 bases de datos "remotas" de una en una? ¿O están moviendo datos en conjuntos masivos por la noche o algo así? Entonces estás entre una roca y un lugar duro. El uso de bigint eventualmente se quedará sin espacio (tal vez) y aún tendrá que obtener un valor único en los muchos db's. Así que aquí está mi idea radical. ¿Pueden las 150 bases de datos remotas obtener sus UUID de un servicio central? Esa es una idea. ¿Las 150 bases de datos remotas están "vinculadas" (como en sp_addlinkedserver) a la base de datos principal? Entonces tengo un UDF que podría considerarse. Déjame ver si puedo encontrarlo.
granadaCoder
Aquí hay un artículo que habla sobre el codeproject.com/Articles/388157/… de
graderialid
0

Según su descripción, vaya con BIGINT. Sin embargo, el índice para GUID puede ser no único, ya que se supone que los GUID son globalmente únicos de todos modos.

Jimbo
fuente
-1

Si GUID se almacena correctamente como identificador único, no debería haber problemas de rendimiento ... y si puede usar GUID secuencial aún mejor ...

También @mattytommo tiene un buen punto de aproximadamente 11.5 años con el uso de INT ...

Borik
fuente
Sí, pero el guid se genera en las 150 bases de datos remotas, no en la base de datos de SQL Server, por lo que no puedo usar guia secuencial, pero gracias por la respuesta.
Roddles
En ese caso, su plan, en mi opinión, es sólido, he hecho algo similar en uno de los DB que administro, creé una INT DENTITY (1,1) y la configuré como PK en clúster, así como un identificador legible para los datos tire hacia arriba y mantuve GUID (Índice) como rastreador para poder rastrear dónde se originó. Pero mi motivación fue más por ahorrar espacio ...
Borik
Muchas gracias y muy apreciado por sus respuestas y puntos de vista. :)
Roddles