Usando um GUID como uma chave primária

32

Eu geralmente uso IDs de incremento automático como Chaves Primárias em bancos de dados. Estou tentando aprender os benefícios do uso de GUIDs. Eu li este artigo: https://betterexplained.com/articles/the-quick-guide-to-guids/

Percebo que esses GUIDs são usados ​​para identificar objetos no nível do aplicativo. Eles também são armazenados como a chave primária no nível do banco de dados. Por exemplo, digamos que eu tenha a seguinte classe:

public class Person
{
public GUID ID;
public string Name;
..

//Person Methods follow
}

Digamos que eu queira criar uma nova pessoa na memória e, em seguida, insira a Pessoa em um banco de dados. Posso apenas fazer isso:

Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

Digamos que eu tivesse um banco de dados contendo milhões e milhões de linhas com um GUID como Chave Primária. Isso sempre será único? Estou entendendo GUIDs corretamente?

Li este artigo anteriormente: http://enterprisecraftsmanship.com/2014/11/15/cqs-with-database-generated-ids/ . Isso me confunde um pouco, pois parece recomendar um meio termo entre GUIDs e números inteiros como Chaves Primárias.

Editar 11/06/18

Eu acredito que os Guids são mais adequados do que ints para minhas necessidades. Atualmente, estou usando mais o CQRS e os GUIDs se encaixam melhor.

Percebo que alguns desenvolvedores modelam os GUIDs como seqüências de caracteres no modelo de domínio, por exemplo, aqui: https://github.com/dotnet-architecture/eShopOnContainers/blob/dev/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/ Buyer.cs - neste caso: IdentityGuid é um GUID modelado como uma sequência. Existe algum motivo para fazer isso além do indicado aqui: Use um objeto de valor personalizado ou um Guid como identificador de entidade em um sistema distribuído? . É "normal" modelar o GUID como uma string ou devo modelá-lo como um GUID no modelo e no banco de dados?

w0051977
fonte
7
Não é garantido que seja único, embora seja improvável que você veja uma colisão. stackoverflow.com/questions/1155008/how-unique-is-uuid/…
icirellik
2
veja também: colisões UUID
gnat
2
Consulte também dba.stackexchange.com/questions/54690/… , bem como muitas outras perguntas - este tópico foi solicitado, respondido e discutido com frequência.
Greenstone Walker
1
O sistema com o qual estou trabalhando no momento usa UUIDs. Uma propriedade interessante é que um ID identifica exclusivamente um registro, em oposição a um ID seqüencial que identifica um registro nessa tabela.
Justin Justin

Respostas:

41

Os GUIDs são, por definição, "identificadores globalmente exclusivos". Existe um conceito semelhante, mas um pouco diferente em Java, chamado UUIDs "Universally Unique IDentifiers". Os nomes são intercambiáveis ​​para todos os usos práticos.

Os GUIDs são essenciais para o funcionamento da clusterização de bancos de dados pela Microsoft e, se você precisar incorporar dados de fontes às vezes conectadas, eles realmente ajudarão a evitar colisões de dados.

Alguns fatos do Pro-GUID:

  • GUIDs evitam colisões de chaves
  • Os GUIDs ajudam a mesclar dados entre redes, máquinas etc.
  • O SQL Server oferece suporte a GUIDS semi-seqüenciais para ajudar a minimizar a fragmentação do índice ( ref , algumas advertências)

Alguma feiura com GUIDs

  • Eles são grandes, 16 bytes cada
  • Eles estão com problemas, então você não pode classificar o código e esperar obter o pedido de inserção como em códigos de incremento automático
  • Eles são mais difíceis de trabalhar, principalmente em pequenos conjuntos de dados (como tabelas de consulta)
  • A nova implementação de GUID é mais robusta no SQL Server do que na biblioteca C # (você pode ter GUIDS sequenciais no SQL Server, em C # é aleatório)

Os GUIDs aumentarão seus índices; portanto, o custo do espaço em disco da indexação de uma coluna será maior. GUIDs aleatórios fragmentarão seus índices.

Se você sabe que não vai sincronizar dados de redes diferentes, os GUIDs podem ter mais sobrecarga do que valem.

Se você precisar ingerir dados de clientes às vezes conectados, eles podem ser muito mais robustos para evitar colisões de chaves do que depender da definição de intervalos de sequência para esses clientes.

Berin Loritsch
fonte
18
Meu entendimento é que os GUIDs são sinônimos de UUIDs. UUID é o nome padrão. GUID é o que a Microsoft os cunhou antes da RFC 4122 .
JimmyJames
13
"Eles estão fora de ordem, então você não pode classificar o código e esperar obter o pedido de inserção como pode aumentar os IDs de incremento automático" Francamente, também não me sinto confortável em contar isso com os IDs regulares. Embora seja possível, em um caso extremo, um ID mais baixo se comprometer com o disco posteriormente, prefiro confiar em dados úteis de classificação, como um carimbo de data / hora de inserção. Os IDs devem ser tratados como endereços de memória - tudo tem um, mas o valor em si não faz sentido. Use-os para desempate, no máximo. Especialmente porque, se você tiver uma carga em massa, o pedido de veiculação não será garantido.
Clockwork-Muse
8
@CortAmmon De acordo com a Wikipedia e a RFC 4122 , eles são sinônimos. P. Leach da Microsoft foi um dos criadores da RFC. Eu acho que desde que a RFC foi criada, os dois são iguais. No RFC: "UUIDs (identificador universalmente exclusivo), também conhecido como GUIDs (identificador exclusivo globalmente)." Eu acho que também é útil observar que os GUIDs não foram criados pelo MS. Eles acabaram de criar um novo nome para uma tecnologia adotada de outros lugares.
JimmyJames
6
"O SQL Server possui otimizações para lidar com GUIDs, portanto não deve afetar muito o desempenho da consulta." -1 Não é quase otimizado o suficiente. Estou trabalhando com um banco de dados em que todos os PKs são guias e é uma das principais causas de baixo desempenho.
Andy
7
"O SQL Server possui otimizações para lidar com GUIDs, portanto não deve afetar muito o desempenho da consulta. " Não é verdade. Essa declaração assume que outros tipos de dados não são otimizados. Os servidores de banco de dados também têm otimizações para lidar com valores int simples, por exemplo. GUIDs / UUIDs são muito mais lentos do que usar um valor int de 4 bytes. 16 bytes nunca serão tão rápidos quanto 4 bytes - especialmente em uma máquina que lida com no máximo 4 ou 8 bytes nativamente.
Andrew Henle
28

Isso sempre será único?

Sempre? nem sempre; é uma sequência finita de bits.

Digamos que eu tivesse um banco de dados contendo milhões e milhões de linhas com um GUID como Chave Primária.

Milhões e milhões, você provavelmente está seguro. Um milhão de milhões e a probabilidade de uma colisão se torna significativa. Porém, há boas notícias: você já ficou sem espaço em disco no momento em que isso acontece.

Posso apenas fazer isso?

Você pode; não é uma ideia totalmente boa. Seu modelo de domínio normalmente não deve gerar números aleatórios; eles devem ser entradas para o seu modelo.

Além disso, quando você estiver lidando com uma rede não confiável, onde poderá receber mensagens duplicadas, um UUID gerado deterministicamente o protegerá de ter entidades duplicadas. Mas se você atribuir um novo número aleatório a cada um, terá mais trabalho a fazer para identificar a duplicação.

Veja a descrição do uuid baseado em nome no RFC 4122

É "normal" modelar o GUID como uma string ou devo modelá-lo como um GUID no modelo e no banco de dados?

Eu não acho que isso importe muito. Para a maior parte do seu modelo de domínio, é um identificador ; a única consulta que você faz é se é ou não o mesmo que outro identificador. Seu modelo de domínio normalmente não estará olhando para a representação na memória de um identificador.

Se o GUID estiver disponível como um "tipo primitivo" na configuração independente de domínio, eu o usaria; permite que o contexto de suporte escolha otimizações apropriadas que possam estar disponíveis.

O que você deve reconhecer, no entanto, é que a representação do identificador, tanto na memória quanto no armazenamento, é uma decisão que você está tomando em sua implementação e, portanto, deve tomar medidas para garantir que a pegada do código seja acoplada àquela decisão é pequena - veja Parnas 1972 .

VoiceOfUnreason
fonte
20
+1 para "você já está sem espaço em disco no momento em que isso acontece".
W0051977
2
Eu sinto que o conceito de " UUID gerado deterministicamente " é essencial (consulte Data Vault 2)
alk
De fato, ser capaz de recalcular um UUID / GUID com base em outros dados é uma ajuda imensa, especialmente para detectar duplicatas. Certa vez, criei um sistema de processamento de mensagens que armazenava as mensagens e as fazia passar por um pipeline de processamento. Criei um hash da mensagem e usei isso como chave primária em todo o sistema. apenas isso, por si só, me resolveu MUITOS problemas para identificar a mensagem quando tínhamos que expandir.
Newtopian
Um milhão de milhões = 2 ^ 40. Isso faz 2 ^ 79 pares de possíveis colisões. O GUID possui 2 ^ 128 bits, então a chance é de um em 2 ^ 49. É muito mais provável que você tenha um bug que reutilize o mesmo GUID para dois registros ou que acredite erroneamente que há uma colisão onde não há nenhum.
gnasher729
Estou voltando às minhas perguntas históricas. Antes de eu aceitar; você poderia dar uma olhada na minha edição?
w0051977
11

O GUID ou o UUID provavelmente será único devido à forma como são gerados e eles fornecem uma maneira segura de garantir exclusividade sem precisar se comunicar com uma autoridade central.

Benefícios dos GUIDs como chave primária:

  • Você pode copiar dados entre diferentes fragmentos de um cluster e não precisa se preocupar com colisões de PK.
  • Ele permite que você conheça sua chave primária antes de inserir qualquer registro.
  • Simplifica a lógica da transação para inserir registros filhos.
  • Não pode ser adivinhado facilmente.

No exemplo que você forneceu:

Person p1 = new Person();
p1.ID = GUID.NewGUID();
PersonRepository.Insert(p1);

A especificação do GUID antes do tempo de inserção pode salvar uma viagem de ida e volta ao banco de dados ao inserir sucessivos registros filhos e permitir que você os confirme na mesma transação.

Person p2 = new Person();
p2.ParentID = p1.ID
PersonRepository.Insert(p2);

Distúrbios nos GUIDs como uma chave primária:

  • São 16 bytes grandes, o que significa que consumirão mais espaço à medida que índices e chaves estrangeiras são adicionados.
  • Eles não se classificam bem, pois são essencialmente números aleatórios.
  • O uso do índice é muito, muito, muito ruim.
  • Muita folha se movendo.
  • Eles são difíceis de lembrar.
  • Eles são difíceis de verbalizar.
  • Eles podem dificultar a leitura dos URLs.

Se o seu aplicativo não precisar de sharding ou clustering, seria melhor usar tipos de dados menores e mais simples, como int ou bigint.

Muitos bancos de dados têm suas próprias implementações internas que tentam atenuar os problemas de armazenamento causados ​​pelos GUIDs e pelo SQL Server, inclusive com uma função newsequentialid para ajudar na ordenação dos UUIDs, permitindo um melhor uso dos índices e geralmente possuem melhores características de desempenho.

Além disso, da perspectiva de um testador, usuário ou desenvolvedor que trabalha com o aplicativo, o uso de um ID em um GUID melhorará significativamente a comunicação. Imagine ter que ler um GUID por telefone.

No final, a menos que um cluster de grande escala ou URLs ofuscantes seja um requisito, é mais pragmático usar IDs de incremento automático.

icirellik
fonte
1
Uma coisa a considerar é que, dependendo do tipo de UUID , eles contêm informações que podem ser usadas para identificar a máquina na qual são gerados. A variante aleatória pura pode ter maior probabilidade de colidir sem entropia suficiente. Isso deve ser considerado antes do uso em um URI.
JimmyJames
Concordou, embora nunca se deva expor sua chave primária em um URL. Algum método mais apropriado deve ser usado para garantir que não haja dados seguros vazando para o sistema externo
#
1
Há mais um caso de uso: bancos de dados OLTP de inserção pesada, nos quais o bloqueio da sequência é um gargalo. De acordo com meu amigo do Oracle DBA, isso não é tão raro quanto parece, você nem precisa de grandes escalas ou clusters para isso. • No final, pesar os prós e contras (e não confundir os prós / contras dos UUIDs com os prós / contras que não são específicos dos UUIDs, como alguns pôsteres fazem) e medir .
mirabilos
1
Se você usar newsequentialid, precisará ir ao banco de dados para obter o ID (como com uma identidade int), não é? Qual é o benefício aqui.
W0051977
1
@mirabilos Para ficar claro, quando digo terrível, acabamos tendo inserções que estavam levando minutos por linha. Tudo começou bem, mas depois que havia milhares de linhas, foram rapidamente muito para os lados. Se não for óbvio, 10s de milhares de linhas é uma tabela muito pequena.
JimmyJames
4

Eu diria que não, não use GUIDs como chaves primárias. Atualmente, estou lidando com esse banco de dados e eles são uma das principais causas de problemas de desempenho.

Os 12 bytes extras são adicionados rapidamente; lembre-se, a maioria dos PKs serão FKs em outras tabelas, e apenas três FKs em uma tabela agora têm 48 bytes extras para cada linha. Isso se soma na tabela e nos índices. Ele também é adicionado à E / S do disco. Esses 12 bytes extras precisam ser lidos e gravados.

E se você não estiver usando guias seqüenciais e as PKs estiverem agrupadas (o que acontece por padrão), o SQL precisará, periodicamente, mover páginas inteiras de dados para espremer mais no "local" certo. Para um banco de dados de transações altamente com muitas inserções, atualizações e exclusões, as coisas atolam rapidamente.

Se você precisar de algum tipo de identificador exclusivo para sincronização ou algo assim, adicione uma coluna guid. Apenas não faça o PK.

Andy
fonte
4
Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

Esse é de longe o motivo mais importante para o uso de GUIDs.

O fato de você poder criar um ID exclusivo sem que seu código conheça ou se comunique com a camada de persistência é um grande benefício.

Você pode ter certeza de que o objeto Person que você acabou de gerar em seu servidor, telefone pc, laptop, dispositivo offline ou o que for único em todos os seus servidores em todo o mundo está distribuído.

Você pode colocá-lo em qualquer tipo de banco de dados rdb ou no-sql, arquivo, enviá-lo para qualquer serviço da web ou jogá-lo fora imediatamente como desnecessário

Não, você nunca terá uma colisão.

Sim, as inserções podem ser um pouco mais lentas, pois pode ser necessário mexer no índice.

Sim, é maior que um int.

  • editar. teve que disparar antes de terminar.

Sei que muitas pessoas se sentem fortemente com as incidentes automobilísticos e esse é um tópico controverso com os DBAs

Mas eu realmente não posso afirmar com força suficiente como guias superiores são. Você deve usar guias por padrão em qualquer aplicativo.

auto inc ints tem muitas falhas

  • Você usa um banco de dados distribuído No-Sql. Você simplesmente não pode conversar com todas as outras instâncias para descobrir qual é o próximo número.

  • Você usa um sistema de fila de mensagens. As coisas precisam de IDs antes de atingirem o banco de dados

  • Você está criando vários itens e editando-os antes de salvar. Cada um precisa de um ID antes de acessar o banco de dados

  • Você deseja excluir e reinserir linhas. Certifique-se de não contar seus IDs de auto inc e acabar!

  • Você deseja não expor quantos pedidos você recebeu este ano a todos os usuários

  • Você deseja mover dados anônimos da produção para testar e manter os relacionamentos intactos. Mas não exclua todos os dados de teste existentes.

  • Você deseja mesclar seu produto de inquilino único em um banco de dados multilocatário, mas todos têm um pedido 56.

  • Você cria objetos que são persistentes, mas efêmeros. (pedidos incompletos) novamente, não use todas as suas entradas com coisas que não existem mais.

A lista é interminável e todos são problemas reais que acontecem com as pessoas o tempo todo. diferente de ficar sem espaço em disco por causa de colunas FK um pouco maiores

Finalmente, o grande problema com as ints é que você as esgotou !!! ok, em teoria, você não tem, há um monte. Mas, na prática, você o faz porque as pessoas não os tratam como números aleatórios sem significado. eles fazem coisas como

  • ah, não quero que os clientes pensem que somos novos. começar às 10.000

  • Eu tive que importar uma carga de dados, então eu apenas subi a semente para 1m para sabermos o que é importado

  • precisamos de categorias de dados. todo período começa no próximo milhão, para que possamos usar os primeiros dígitos como um número mágico

  • Excluí e reimportei todos os dados novamente com novos IDs. Sim, mesmo os logs de auditoria.

  • use esse número, que é uma chave composta, como o ID dessa outra coisa

Ewan
fonte
1
Não há nada de errado com essa resposta, mas eu (para evitar mais votos negativos) talvez explique a ressalva de que, embora os aplicativos da vida real não encontrem colisões, é teoricamente possível. (Ou talvez mais de 45 bancos de dados exabyte sejam mais prevalentes do que eu pensava ...). Embora eu ache a linguagem "a razão mais importante" um pouco forte, é isso que acho mais útil.
BurnsBA
2
o mais provável que um int auto inc irá colidir do que um guid
Ewan
4
-1 para "Você deve usar guias por padrão em qualquer aplicativo". Depende ™. E, como outros demonstraram, GUIDs / UUIDs, não garantem absolutamente que sejam únicos.
Max Vernon
3
As respostas "depende" são inúteis, com certeza haverá algumas aplicações estranhas nas quais um int é melhor. Mas é provável que seu aplicativo não seja um deles. GUIDs são a coisa mais original você pode obter
Ewan
2
Eu acho que haverá algumas aplicações estranhas onde os guias são melhores. Único não é a coisa mais importante a considerar. Suas "falhas" de ints são massivamente exageradas e você não considera nenhuma das muitas desvantagens dos guias.
Andy Andy
2

Percebo que esses GUIDs são usados ​​para identificar objetos no nível do aplicativo. Eles também são armazenados como a chave primária no nível do banco de dados.

É aí que você deve parar, ali mesmo, e repensar.

A chave primária do banco de dados NUNCA deve ter significado comercial. Deve ser sem sentido por definição.

Portanto, adicione o GUID como sua chave comercial e uma chave primária normal (geralmente uma longa int) como chave primária do banco de dados. Você sempre pode colocar um índice exclusivo no GUID para garantir a exclusividade.

Isso está falando da teoria do banco de dados, é claro, mas também é uma boa prática. Eu lidei com bancos de dados em que as chaves primárias tinham significado comercial (um cliente pensou em economizar alguns recursos do banco de dados usando-os como números de funcionários, números de clientes, etc. etc., por exemplo) e isso sempre gera problemas.

jwenting
fonte
1
Como isso é diferente de consultar a camada de aplicativo usando uma chave primária inteira? Nesse ponto, ele também está sendo usado para identificar objetos na camada de aplicativo. Você meio que precisa de uma maneira de identificar objetos em um banco de dados a partir da camada do aplicativo.
Icirellik
@icirellik, a chave primária é para uso interno do banco de dados, para vincular registros pai e filho, etc. NÃO é para ser usado pela lógica do aplicativo, você usa IDs comerciais para isso, como um número ou nome de produto.
jwenting
2

Sempre use chaves primárias (PKs) com incremento automático de banco de dados.

Por que usar o incremento automático em vez do GUID / UUID?

  • GUID (UUID) s não impedem colisões de chaves, pois não são únicas e não há como torná-las únicas, pois são geradas a partir de várias fontes.
  • Os GUIDs não ajudam na mesclagem, pois aumentam bastante o processo de mesclagem que já consome tempo, com colunas PK e FK extremamente longas e não inteiras que levam muito tempo para serem processadas. Lembre-se de que para a maioria das PKs, haverá pelo menos uma outra tabela com pelo menos 2 chaves do mesmo tamanho: é PK própria e uma FK de volta à primeira tabela. Tudo precisa ser resolvido em uma mesclagem.

Mas como lidar com cacos, cachos, etc.?

  • Crie PKs de várias colunas compostas de colunas separadas, identificando cada fragmento / cluster / banco de dados / o que quer que gere suas próprias chaves de incremento automático. Por exemplo...

Uma PK de 3 colunas para uma tabela em cluster pode ser ...

 DB | SH | KEY     |
----|----|---------|
 01 | 01 | 1234567 |

Mas e quanto ...?

  • Várias viagens ao banco de dados - a maioria dos aplicativos não precisa identificar exclusivamente um registro que está sendo criado até que ele seja inserido no banco de dados desde o encadeamento / sessão / o que estiver funcionando apenas um por vez. Se o aplicativo realmente precisar dessa capacidade, use um PK temporário gerado pelo aplicativo que não seja enviado ao banco de dados . Deixe o banco de dados colocar seu próprio PK de incremento automático na linha quando for inserido. As inserções usarão a PK temporária, enquanto as atualizações e exclusões usarão a PK permanente atribuída pelo banco de dados.

  • Desempenho - Os computadores podem processar números inteiros simples muito mais rapidamente do que qualquer outra coisa, devido ao valor muito maior do domínio, se possível, por elemento em um GUID (37) versus um número inteiro (10). Lembre-se também de que cada caractere em um GUID deve primeiro ser convertido em um número a ser manipulado pela CPU.

Os usos indevidos comuns de chaves primárias PKs têm apenas um objetivo ... identificar absolutamente exclusivamente uma linha em uma tabela. Qualquer outra coisa é um mau uso muito comum.

Detectando registros ausentes

  • Os registros ausentes não podem ser detectados observando as PKs. Abençoe o controle de qualidade por pelo menos tentar garantir a qualidade dos dados. No entanto, eles e a falta de compreensão do programador sobre como as chaves nos sistemas modernos de banco de dados são atribuídos muitas vezes os levam a acreditar que um número ausente em um PK com incremento automático significa dados ausentes. Ele faz não porque ...
  • Para desempenho, os sistemas de banco de dados alocam blocos de números em 'sequências' (lotes, intervalos) para minimizar as viagens ao banco de dados real no armazenamento. O tamanho dessas seqüências de números geralmente está sob o controle do DBA, mas pode não ser ajustado em uma base por tabela.
  • O principal argumento é ... números não utilizados dessas seqüências nunca são retornados ao banco de dados, portanto sempre existem lacunas nos números PK.
  • Por que haveria números não utilizados que você pergunta? Porque uma variedade de ações de manutenção do banco de dados pode fazer com que as seqüências sejam abandonadas. São coisas como reinicializações, recarregamentos em massa de tabelas, alguns tipos de restauração de backups e outras operações.

Classificação

  • A classificação por PK é muito suscetível a erros, pois a maioria das pessoas pensa que lista as linhas na ordem em que foram criadas e que corresponde à hora do relógio. Principalmente, mas não necessariamente.
  • Os mecanismos de banco de dados são otimizados para obter o desempenho máximo e isso pode significar atrasar a inserção dos resultados de uma transação complicada de longa duração para inserir pequenas e simples, "fora de curva", por assim dizer.
DocSalvager
fonte
O que você pensa do esquema da tabela, de modo que a única coluna exclusiva seja uma chave primária de incremento automático criada pelo banco de dados? Especialmente para tabelas que não têm chave estrangeira, mas cuja chave primária é a chave estrangeira para várias tabelas relacionadas?
precisa saber é o seguinte
Eu adicionei muito mais à resposta nesse sentido. A resposta original estava incompleta devido ao aplicativo Android SE em que estou pendurado. Eu acho que uma grande reescrita do aplicativo está em desenvolvimento.
DocSalvager 6/08/17
Então, na sua opinião, seria bom que uma tabela contivesse qualquer número de linhas idênticas, exceto pela chave primária de incremento automático?
precisa saber é o seguinte
@RibaldEddie - Tanto quanto o que o DB é projetado para permitir ... absolutamente. Exclusões são fáceis. Quando o seu cenário ocorrer, consideraria um bug corrigido no software e excluiria qualquer linha. O caso muito mais comum, porém, são dois registros para a mesma coisa com dados ligeiramente diferentes, portanto eles devem ser mesclados. Se uma coluna estiver vazia em um registro e tiver um valor no outro, a escolha é óbvia e pode ser automatizada. Geralmente, o carimbo de data / hora pode ser usado para arbitrar uma mesclagem automatizada. Algumas duplicatas exigem que uma pessoa conclua e verifique a mesclagem com base nas regras de negócios.
DocSalvager
1

Como qualquer coisa, há vantagens e desvantagens em fazer isso:

O bom:

  1. Suas chaves têm sempre o mesmo comprimento (bancos de dados muito grandes podem ter chaves muito grandes)

  2. A exclusividade é praticamente garantida - mesmo quando você as está gerando em um sistema separado e / ou não leu o último ID do banco de dados

O mal:

  1. Como mencionado muito acima - índices maiores e armazenamento de dados.

  2. Você não pode solicitar por ID, mas por outra coisa. Mais índices, provavelmente menos eficientes.

  3. Eles são menos legíveis por humanos. Inteiros geralmente são mais fáceis de analisar, lembrar e digitar para as pessoas. Usar GUIDs como IDs nas cláusulas WHERE em várias tabelas unidas pode fazer sua cabeça derreter.

Como tudo, use-os quando apropriado, não seja dogmático - em muitas situações, números inteiros com auto-incremento são melhores, às vezes os GUIDs são ótimos.

Phil S
fonte
0

Sim, você pode usar o GUID como chave primária. O lado negativo é o tamanho e a rápida fragmentação do índice.

A menos que você precise de exclusividade nos bancos de dados (por exemplo, um cluster), é preferível um número inteiro.

paparazzo
fonte
Os geradores de GUID podem produzir o mesmo GUID mais de uma vez; aí está uma falha. Se eles vão ou não, depende de sua granularidade, principalmente do intervalo entre os tiques do relógio. Por exemplo, um gerador baseado em relógio pode funcionar apenas a cada 100ms, levando a 2 GUIDs solicitados dentro desses 100ms na máquina sendo idênticos. Existem maneiras de evitar isso, principalmente, mas muitos geradores de GUID funcionam inteiramente fora do endereço IP e / ou endereço MAC e um carimbo de data e hora.
Jwenting 07/08
0

Aqui está minha opinião sobre esse problema - a solução é uma casa intermediária entre os valores GUID e int, aproveitando o melhor de ambos.

A classe gera um valor de ID pseudo-aleatório (mas aumentando ao longo do tempo), que é semelhante a um GUID Comb .

A principal vantagem é que ele permite que os valores de ID sejam gerados no cliente, em vez de usar valores de incremento automático gerados no servidor (o que requer uma ida e volta) com risco quase zero de valores duplicados.

Os valores gerados usam apenas 8 bytes em vez de 16 para um GUID e não dependem de uma ordem de classificação de banco de dados específica (por exemplo, Sql Server for GUIDs ). Os valores podem ser expandidos para usar todo o longo período não assinado, mas isso causaria problemas em qualquer banco de dados ou outro repositório de dados que possuísse apenas tipos de números inteiros assinados.

public static class LongIdGenerator
{
    // set the start date to an appropriate value for your implementation 
    // DO NOT change this once any application that uses this functionality is live, otherwise existing Id values will lose their implied date
    private static readonly DateTime PeriodStartDate = new DateTime(2017, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    private static readonly DateTime PeriodEndDate = PeriodStartDate.AddYears(100);
    private static readonly long PeriodStartTicks = PeriodStartDate.Ticks;
    private static readonly long PeriodEndTicks = PeriodEndDate.Ticks;
    private static readonly long TotalPeriodTicks = PeriodEndTicks - PeriodStartTicks;

    // ensures that generated Ids are always positve
    private const long SEQUENCE_PART_PERMUTATIONS = 0x7FFFFFFFFFFF; 

    private static readonly Random Random = new Random();

    private static readonly object Lock = new object();
    private static long _lastSequencePart;

    public static long GetNewId()
    {
        var sequencePart = GetSequenceValueForDateTime(DateTime.UtcNow);

        // extra check, just in case we manage to call GetNewId() twice before enough ticks have passed to increment the sequence 
        lock (Lock)
        {
            if (sequencePart <= _lastSequencePart)
                sequencePart = _lastSequencePart + 1;

            _lastSequencePart = sequencePart;
        }

        // shift so that the sequence part fills the most significant 6 bytes of the result value
        sequencePart = (sequencePart << 16);

        // randomize the lowest 2 bytes of the result, just in case two different client PCs call GetNewId() at exactly the same time
        var randomPart = Random.Next() & 0xFFFF;

        return sequencePart + randomPart;
    }

    // used if you want to generate an Id value for a historic time point (within the start and end dates)
    // there are no checks, compared to calls to GetNewId(), but the chances of colliding values are still almost zero
    public static long GetIdForDateTime(DateTime dt)
    {
        if (dt < PeriodStartDate || dt > PeriodStartDate)
            throw new ArgumentException($"value must be in the range {PeriodStartDate:dd MMM yyyy} - {PeriodEndDate:dd MMM yyyy}");

        var sequencePart = GetSequenceValueForDateTime(dt.ToUniversalTime());
        var randomPart = Random.Next() & 0xFFFF;
        return ( sequencePart << 16 ) + randomPart;
    }

    // Get a 6 byte sequence value from the specified date time - startDate => 0 --> endDate => 0x7FFFFFFFFFFF
    // For a 100 year time period, 1 unit of the sequence corresponds to about 0.022 ms
    private static long GetSequenceValueForDateTime(DateTime dt)
    {
        var ticksFromStart = dt.ToUniversalTime().Ticks - PeriodStartTicks;
        var proportionOfPeriod = (decimal)ticksFromStart / TotalPeriodTicks;
        var result = proportionOfPeriod * SEQUENCE_PART_PERMUTATIONS;
        return (long)result;
    }

    public static DateTime GetDateTimeForId(long value)
    {
        // strip off the random part - the two lowest bytes
        var timePart = value >> 16;
        var proportionOfTotalPeriod = (decimal) timePart / SEQUENCE_PART_PERMUTATIONS;
        var ticks = (long)(proportionOfTotalPeriod * TotalPeriodTicks);
        var result = PeriodStartDate.AddTicks(ticks);
        return result;
    }
}
Peregrino
fonte