Melhor solução para corrigir o design do banco de dados com GUID como chave primária

18

Estou depois de alguma confirmação dessa idéia para corrigir um banco de dados com desempenho ruim ou uma sugestão melhor, se alguém tiver um. Sempre aberto a melhores sugestões.

Eu tenho um banco de dados muito grande (mais de 20 milhões de registros crescendo cerca de 1/2 milhão por dia) que estão usando GUID como PK.

Uma supervisão da minha parte, mas o PK está agrupado no servidor SQL e está causando problemas de desempenho.

O motivo de um guia - esse banco de dados é parcialmente sincronizado com outros 150 bancos de dados, portanto a PK precisava ser única. A sincronização não é gerenciada pelo SQL Server, mas há um processo personalizado criado que mantém os dados sincronizados para os requisitos do sistema - todos baseados nesse GUID.

Cada um dos 150 bancos de dados remotos não armazena os dados completos armazenados no banco de dados SQL central. eles armazenam apenas um subconjunto dos dados que eles realmente precisam e os dados que eles exigem não são exclusivos deles (10 dos 150 bancos de dados podem ter alguns dos mesmos registros dos bancos de dados de outros sites, por exemplo - eles compartilham). Além disso - os dados são realmente gerados nos sites remotos - não no ponto central - daí a necessidade dos GUIDs.

O banco de dados central é usado não apenas para manter tudo sincronizado, mas as consultas de mais de 3000 usuários serão executadas nesse banco de dados fragmentado muito grande. Já é um grande problema nos testes iniciais.

Felizmente, ainda não estamos no ar - para que eu possa fazer alterações e colocar offline, se necessário, o que é pelo menos algo.

O desempenho dos bancos de dados remotos não é um problema - os subconjuntos de dados são bem pequenos e o banco de dados geralmente nunca ultrapassa 1 GB no total. Os registros são retornados ao sistema principal com bastante regularidade e removidos dos BDs menores quando não são mais necessários.

O desempenho do banco de dados central, que é o guardião de todos os registros, é lamentável - devido a um GUID em cluster como uma chave primária para muitos registros. A fragmentação do índice está fora dos gráficos.

Então - o meu pensamento para corrigir o problema de desempenho é criar uma nova coluna - IDENTIDADE BIGINT não assinada (1,1) e depois alterar o PK em cluster da coluna BIGINT da tabela.

Eu criaria um índice não clusterizado exclusivo no campo GUID, que era a chave primária.

Os 150 bancos de dados remotos menores não precisam saber sobre a nova PK no banco de dados do SQL Server Central - será puramente usada para organizar os dados no banco de dados e impedir o mau desempenho e a fragmentação.

Isso funcionaria e melhoraria o desempenho do banco de dados SQL central e impediria a fragmentação futura do índice (até certo ponto)? ou eu perdi algo muito importante aqui que vai pular e me morder e causar ainda mais sofrimento?

Roddles
fonte
2
@mattytommo Concordo.
Paul Fleming
2
Você está executando a desfragmentação do índice pelo menos uma vez por semana?
Andomar 21/03
1
Você tem algo significativo para agrupar? Ou seja, qual consulta deve ser rápida? Definitivamente, não haverá varredura de intervalo no guia, portanto, em vez de escolher apenas um incremento automático, considere se existe algum cluster ideal no momento da consulta que você possa escolher. Se não, então vá em frente e usar o bigint
2
@Borik Não é uma ótima idéia, com base no que ele tem e em sua taxa de crescimento, ele se esgotará intem 4255 dias (11,5 anos). Se ele fez isso, ele só culpá-lo em 11,5 anos;)
mattytommo
1
Uma visão contrária: por que você acha que o tipo de dados GUID é um problema? É um número inteiro de 128 bits. Por que você acha que substituí-lo por um inteiro de 64 bits (bigint) ou inteiro de 32 bits (int) fará uma diferença notável na velocidade? Eu acho que você definitivamente deve alterar a chave de cluster para outra coisa, para evitar toda a página dividida que leva à fragmentação, mas não acho que você deva alterar o tipo de dados, a menos que tenha muita certeza de que esse é o problema.
precisa

Respostas:

8

Você certamente NÃO precisa se agrupar no GUID. Se você tiver algo que permita identificar exclusivamente os registros que não sejam esse GUID, sugiro que você crie um índice exclusivo nesse outro campo e torne esse índice agrupado. Caso contrário, você poderá agrupar em outros campos, mesmo usando índices não exclusivos. A abordagem que existe para agrupar, no entanto, facilita a divisão e a consulta de dados - portanto, se você tiver um campo "região", ou algo assim, que possa ser candidato ao seu esquema de agrupamento.

O problema de mudar para a BIGINTseria acrescentar dados de outros bancos de dados e integrar o banco de dados ao armazenamento central. Se isso não for uma consideração - e nunca será uma consideração -, sim, BIGINTisso resolveria bem o problema de reequilíbrio do índice.

Nos bastidores, se você não especificar um índice em cluster, o SQL Server fará a mesma coisa: ele cria um campo de ID de linha e mapeia todos os outros índices para ele. Então, fazendo você mesmo, você está resolvendo da mesma maneira que o SQL resolveria.

David T. Macknet
fonte
O único campo genuinamente único na tabela é o GUD - as outras colunas não são únicas e existem combinações de colunas que podem ser únicas para começar - mas, com o tempo, há uma pequena chance de que eles gerem um registro duplicado. Muito remoto, mas é possível, dada a natureza dos dados. Li que todos os outros índices não agrupados fazem referência ao índice agrupado para melhorar o desempenho da pesquisa, etc. Não ter uma PK agrupada como GUID causaria um impacto no desempenho? Estou ciente do espaço e, apesar de uma preocupação - o desempenho é fundamental.
Roddles
O impacto no desempenho, se você não especificar um índice em cluster, é que o SQL criará um nos bastidores para você e mapeará todos os outros índices para esse. Portanto, no seu caso, você obteria uma melhoria no desempenho deixando o SQL fazer isso, porque agora você está constantemente embaralhando todos os seus dados no disco para preservar a ordem de classificação quando a ordem de classificação não é importante. Você precisará de mais espaço de armazenamento, mas verá uma grande melhoria no armazenamento e um impacto mínimo / nenhum na recuperação.
David T. Macknet
Portanto, a pergunta que eu acho é que se eu não fizer o PK BIGINT Clustered e apenas alterar o PK para um GUID não clusterizado, quais são as implicações de desempenho? Existem outros índices não agrupados na tabela que serão pesquisados ​​com freqüência. Isso afetaria o desempenho dessas pesquisas?
precisa saber é o seguinte
+1 Eu também sugeriria ficar com os GUIDs. É muito difícil substituí-los em sistemas distribuídos. Seu índice de cluster de tabela grande deve ser evidente com base em como você consulta os dados.
Remus Rusanu 21/03
1
Oi pessoal - Apenas uma atualização - fiz as modificações e tornei o PK um não clusterizado no GUID e o SQL Server está ocupado inserindo os 2 milhões de registros no banco de dados. Ao mesmo tempo em que os dados estavam sendo inseridos, eu era capaz de consultar o banco de dados em busca de informações e as consultas que às vezes antes da alteração atingiam o tempo limite de 10 minutos, concluídas em questão de 1-2 segundos. Portanto, tornar o PK não agrupado e não se preocupar com o BIGINT parece ter funcionado muito bem. Muito obrigado pela contribuição e assistência de todos.
Roddles
1

Essa é uma tarefa difícil.

Deixe-me sugerir uma abordagem intermediária.

Eu estava tendo problemas com System.Guid.NewGuid () gerando guias aleatórios. (Eu estava permitindo que o cliente criasse seu próprio guia, em vez de confiar no banco de dados para criar um sequencial).

Depois que mudei para um UuidCreateSequential no lado do cliente, meu desempenho ficou MUITO melhor, principalmente nos INSERTs.

Aqui está o código do cliente DotNet vodu. Tenho certeza de que penhorizei de algum lugar:

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

Se o seu banco de dados principal e o banco de dados remoto estiverem "vinculados" (como em, sp_linkserver) ...... você poderá usar o banco de dados principal como o "gerador de uuid".

Você não quer ser "um por um" do uuid, isso é muita chats.

Mas você pode pegar um conjunto de uuids.

Abaixo está algum 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
fonte
Interessante - e abordagem que eu não havia considerado - examinarei isso mais de perto, pois isso parece bom e executarei alguns projetos de teste. Se tivéssemos 150 bancos de dados gerando guias seqüenciais que são relatados de volta ao banco de dados central, isso ainda não causaria fragmentação, pois os guias ainda seriam bastante aleatórios quando inseridos no banco de dados central. A menos, claro, que você queira dizer solte a PK em cluster e tenha a PK não em cluster?
precisa saber é o seguinte
Os 150 bancos de dados "remotos" estão inserindo um de cada vez? Ou eles estão movendo dados em grandes quantidades à noite ou algo assim? Então você está entre uma rocha e um lugar difícil. O uso do bigint acabará ficando sem espaço (talvez) e você ainda precisará obter um valor único entre os muitos bancos de dados. Então aqui está a minha ideia radical. Os 150 bancos de dados remotos podem obter seus UUIDs de um serviço central? Essa é uma ideia. Os 150 bancos de dados remotos estão "vinculados" (como em sp_addlinkedserver) ao banco de dados principal? Então eu tenho uma UDF que pode ser considerada. Deixe-me ver se consigo encontrá-lo.
granadaCoder
Aqui está um artigo que fala sobre o sequentialid (não relacionadas com o que eu já escrevi, eu acho que é interessante) codeproject.com/Articles/388157/...
granadaCoder
0

Com base na sua descrição, vá com BIGINT. No entanto, o índice para GUID pode não ser exclusivo, pois os GUIDs devem ser globalmente exclusivos de qualquer maneira.

Jimbo
fonte
-1

Se o GUID for armazenado corretamente como identificador exclusivo, não deverá ter nenhum problema de desempenho ... e se você pode usar o GUID seqüencial ainda melhor ...

Também @mattytommo tem um bom ponto cerca de 11,5 anos com o uso de INT ...

Borik
fonte
Sim - mas o guid é gerado nos 150 bancos de dados remotos, não no banco de dados do SQL Server - por isso não posso usar o sequentialguid -, mas obrigado pela resposta.
Roddles
Nesse caso, em minha opinião, seu plano é sólido, fiz algo semelhante em um dos bancos de dados que gerencio, criei uma INT DENTITY (1,1) e o defini como PK em cluster e como identificador legível para dados. puxe para cima e eu mantive GUID (Index) como rastreador para poder rastrear onde ele se originou. Mas minha motivação era mais de economia de espaço ...
Borik
Muito obrigado e muito apreciado por suas respostas e idéias. :)
Roddles