Qual pode ser a desvantagem de sempre ter uma única coluna inteira como chave primária?

18

Em um aplicativo Web em que estou trabalhando, todas as operações do banco de dados são abstraídas usando alguns repositórios genéricos definidos no ORM do Entity Framework.

No entanto, para ter um design simples para os repositórios genéricos, todas as tabelas envolvidas devem definir um número inteiro exclusivo ( Int32em C #, intem SQL). Até agora, este sempre foi o PK da tabela e também o IDENTITY.

Chaves estrangeiras são muito usadas e fazem referência a essas colunas inteiras. Eles são necessários para consistência e para gerar propriedades de navegação pelo ORM.

A camada de aplicativo geralmente executa as seguintes operações:

  • carregamento inicial de dados da tabela (*) -SELECT * FROM table
  • Atualização -UPDATE table SET Col1 = Val1 WHERE Id = IdVal
  • Excluir -DELETE FROM table WHERE Id = IdVal
  • Inserir -INSERT INTO table (cols) VALUES (...)

Operações menos frequentes:

  • Inserção em massa - BULK INSERT ... into tableseguida (*) por toda a carga de dados (para recuperar identificadores gerados)
  • Exclusão em massa - esta é uma operação de exclusão normal, mas "volumosa" da perspectiva do ORM:DELETE FROM table where OtherThanIdCol = SomeValue
  • Atualização em massa - esta é uma operação de atualização normal, mas "volumosa" da perspectiva do ORM:UPDATE table SET SomeCol = SomeVal WHERE OtherThanIdCol = OtherValue

* todas as tabelas pequenas são armazenadas em cache no nível do aplicativo e quase todas SELECTsnão atingem o banco de dados. Um padrão típico é carga inicial e muita INSERTs, UPDATEs e DELETEs.

Com base no uso atual do aplicativo, há uma chance muito pequena de atingir 100 milhões de registros em qualquer uma das tabelas.

Pergunta: Na perspectiva do DBA, existem problemas significativos com os quais posso ter essa limitação de design da tabela?

[EDITAR]

Depois de ler as respostas (obrigado pelo ótimo feedback) e os artigos referenciados, sinto que preciso adicionar mais detalhes:

  1. Especificações atuais do aplicativo - não mencionei o aplicativo da Web atual, porque quero entender se o modelo também pode ser reutilizado para outros aplicativos. No entanto, meu caso particular é um aplicativo que extrai muitos metadados de um DWH. Os dados de origem são bastante confusos (desnormalizados de uma maneira estranha, com algumas inconsistências, sem identificador natural em muitos casos etc.) e meu aplicativo está gerando entidades separadas e claras. Além disso, muitos dos identificadores gerados ( IDENTITY) são exibidos, para que o usuário possa usá-los como chaves comerciais. Além de uma grande refatoração de código, isso exclui o uso de GUIDs .

  2. "eles não devem ser a única maneira de identificar uma linha de maneira única" (Aaron Bertrand ♦) - esse é um conselho muito bom. Todas as minhas tabelas também definem uma restrição exclusiva para garantir que duplicatas de negócios não sejam permitidas.

  3. Design orientado a aplicativos front-end vs. Design orientado a banco de dados - a escolha do design é causada por esses fatores

    1. Limitações do Entity Framework - PKs de várias colunas são permitidas, mas seus valores não podem ser atualizados

    2. Limitações personalizadas - ter uma única chave inteira simplifica muito as estruturas de dados e o código não-SQL. Por exemplo: todas as listas de valores têm uma chave inteira e valores exibidos. Mais importante, garante que qualquer tabela marcada para armazenamento em cache poderá colocar em um Unique int key -> valuemapa.

  4. Consultas de seleção complexas - isso quase nunca acontece porque todos os dados de tabelas pequenas (<20-30K registros) são armazenados em cache no nível do aplicativo. Isso torna a vida um pouco mais difícil ao escrever o código do aplicativo (mais difícil de escrever o LINQ), mas o banco de dados é muito melhor:

    1. Exibições de lista - não gerará SELECTconsultas no carregamento (tudo está armazenado em cache) ou consultas com esta aparência:

      SELECT allcolumns FROM BigTable WHERE filter1 IN (val1, val2) AND filter2 IN (val11, val12)

      Todos os outros valores necessários são buscados por meio de pesquisas em cache (O (1)), portanto, nenhuma consulta complexa será gerada.

    2. Editar visualizações - gerará SELECTinstruções como esta:

      SELECT allcolumns FROM BigTable WHERE PKId = value1

(todos os filtros e valores são ints)

Alexei
fonte
Você pode encontrar essas postagens relevantes, pois alguns aspectos lógicos, físicos e práticos são discutidos com relação ao uso de colunas com valores substitutos gerados pelo sistema.
MDCCL

Respostas:

19

Além de espaço em disco adicional (e, por sua vez, uso de memória e E / S), não há nenhum mal em adicionar uma coluna IDENTITY mesmo a tabelas que não precisam de um (exemplo de uma tabela que não precisa de uma coluna IDENTITY é uma tabela de junção simples, como mapear um usuário para suas permissões).

Eu me oponho a adicioná-los às cegas em todas as tabelas de um post de 2010:

Porém, as chaves substitutas têm casos de uso válidos - apenas tome cuidado para não supor que eles garantam exclusividade (e às vezes é por isso que são adicionados - eles não devem ser a única maneira de identificar exclusivamente uma linha). Se você precisar usar uma estrutura ORM, e sua estrutura ORM exigir chaves inteiras de coluna única, mesmo nos casos em que sua chave real não for um número inteiro, ou uma coluna única, ou nenhuma, verifique se você definiu restrições / índices exclusivos para suas chaves reais também.

Aaron Bertrand
fonte
Obrigado pela resposta rápida. Sim, o aplicativo usa um ORM (EF). Ele não requer chaves de coluna inteiras, mas introduzi essa restrição para facilitar algumas operações genéricas (em termos de design). Além disso, todos os caches de aplicativos armazenam tudo em mapas (dicionários) para recuperações rápidas por chave e a chave deve ser exclusiva. Desde que escolhi ints em vez de guias, sou forçado a usar IDENTITY em qualquer tabela que inserir. Para tabelas de valores fixos, IDENTITY não é necessário.
Alexei
Eu acho que existem alguns casos que exigem evitar a verificação de exclusividade nas chaves naturais. Como alguém que trabalha com dados GIS, aquele que vem à mente imediatamente é onde a chave natural é apenas a própria geometria ou a geometria mais alguma chave estrangeira. Procurar as coisas por uma geometria exata sempre será impraticável, portanto, uma restrição de exclusividade dificilmente ajudará muito e pode ter desvantagens de desempenho. O mesmo pode acontecer se parte da chave natural for uma coluna de texto longo. Mas eu concordo: sempre que possível, sim, uma restrição exclusiva à chave natural deve ser aplicada.
Jpmc26
13

Pela minha experiência, o principal e principal motivo para usar um ID separado para cada tabela é o seguinte:

Em quase todos os casos, meu cliente fez um juramento de sangue na fase de concepção de que algum campo externo "natural" XYZBLARGH_IDpermanecerá único para sempre, nunca mudará para uma determinada entidade e nunca será reutilizado, eventualmente surgiram casos em que o As propriedades da Chave Primária foram quebradas. Simplesmente não funciona dessa maneira.

Então, do ponto de vista do DBA, as coisas que tornam um banco de dados lento ou inchado certamente não são 4 bytes (ou o que for) por linha, mas coisas como índices errados ou ausentes, reorganizações esquecidas de tabela / índice, parâmetros de ajuste de RAM / espaço de tabela incorretos , deixando de usar variáveis ​​de ligação e assim por diante. Aqueles podem diminuir a velocidade do banco de dados por fatores de 10, 100, 10000 ... não uma coluna de ID adicional.

Portanto, mesmo que houvesse uma desvantagem técnica e mensurável de ter 32 bits adicionais por linha, não se trata de otimizar o ID, mas se o ID será essencial em algum momento, o que será mais provável que não. E não vou contar todos os benefícios "flexíveis" de uma postura de desenvolvimento de software (como o exemplo do ORM, ou o fato de facilitar para os desenvolvedores de software quando todos os IDs projetados tiverem o mesmo tipo de dados e assim por diante) .

NB: observe que você não precisa de um ID separado para n:mtabelas de associação, pois para essas tabelas os IDs das entidades associadas devem formar uma chave primária. Um contraexemplo seria uma n:massociação estranha que permite várias associações entre as mesmas duas entidades por qualquer motivo bizarro - elas precisariam de sua própria coluna de ID para criar uma PK. Porém, existem bibliotecas ORM que não podem manipular PKs de várias colunas, portanto, esse seria um motivo para ser indulgente com os desenvolvedores, se eles precisassem trabalhar com essa biblioteca.

AnoE
fonte
2
"associação n: m estranha que permite múltiplas associações entre as mesmas duas entidades" MUITO comum na vida real. Por exemplo, uma pessoa é dona de um carro, então os requisitos mudam para recuperados quando a propriedade começou e terminou. (Uma pessoa pode vender um carro e comprá-lo mais tarde, além de travar o software ...)
Ian Ringrose
Sim, algo assim, @IanRingrose.
AnoE
6

Se você invariavelmente adicionar uma coluna extra sem sentido a todas as tabelas e fizer referência apenas a essas colunas como chaves estrangeiras, quase inevitavelmente tornará o banco de dados mais complexo e difícil de usar. Efetivamente, você removerá os dados de interesse dos usuários dos atributos da chave estrangeira e forçará o usuário / aplicativo a fazer uma associação extra para recuperar as mesmas informações. As consultas se tornam mais complexas, o trabalho do otimizador se torna mais difícil e o desempenho pode sofrer.

Suas tabelas serão mais escassamente preenchidas com dados "reais" do que seriam de outra forma. O banco de dados será, portanto, mais difícil de compreender e verificar. Você também pode achar difícil ou impossível impor certas restrições úteis (onde as restrições envolvem vários atributos que não estão mais na mesma tabela).

Eu sugiro que você escolha suas chaves com mais cuidado e faça-as inteiras somente se / quando você tiver boas razões. Baseie seus projetos de banco de dados em boas análises, integridade de dados, praticidade e resultados verificáveis, em vez de confiar em regras dogmáticas.

nvogel
fonte
11
E, no entanto, muitos sistemas têm chaves primárias inteiras sintéticas em todas as tabelas (quase todos os aplicativos Ruby on Rails já escritos, por exemplo), sem sofrer esses problemas. Eles também nunca sofrem com o problema de precisar fazer alterações nas chaves primárias (que nunca deveriam acontecer) em todas as tabelas de chaves estrangeiras.
David Aldridge
2
A pergunta pedia possíveis desvantagens, daí a minha resposta. Não nego que chaves substitutas possam fazer sentido se usadas com sabedoria. Mas vi tabelas com 3,4,5 (ou mais) chaves estrangeiras sem sentido que, portanto, exigiam 3,4,5 ou mais junções para obter resultados úteis delas. Um design mais pragmático pode não ter exigido nenhuma junção.
Nvogel
11
Não estou convencido de que a execução de tais consultas seja o principal problema que as pessoas têm com esse design - é a escrita da consulta à qual elas frequentemente se opõem.
David Aldridge
5

Na minha experiência com vários bancos de dados, uma chave primária Inteira é sempre melhor do que os aplicativos que não possuem chaves definidas. Ou que têm chaves que juntam meia dúzia de colunas varchar de maneiras estranhas que não são lógicas ... (suspiro)

Já vi aplicativos que alternavam de PKs inteiros para GUIDs. A razão para isso foi porque havia a necessidade de mesclar dados de vários bancos de dados de origem em certos casos. Os desenvolvedores mudaram todas as chaves para GUIDs para que as mesclagens pudessem ocorrer sem medo de colisões de dados, mesmo em tabelas que não faziam parte da mesclagem (apenas no caso de essas tabelas se tornarem parte de uma mesclagem futura).

Eu diria que um PK inteiro não vai morder você, a menos que você planeje mesclar dados de fontes separadas ou você possa ter dados que vão além dos limites de tamanho inteiro - é tudo divertido e jogos até você ficar sem espaço para inserções .

Eu direi, no entanto, que pode fazer sentido definir seu índice clusterizado em uma coluna diferente da sua PK, se a tabela for consultada com mais frequência dessa maneira. Mas esse é um caso externo, especialmente se a maior parte das atualizações e seleções forem baseadas nos valores de PK.

CaM
fonte
2
Parece uma justificativa terrível para mudar todas as chaves para guias. Atualmente, trabalho com um banco de dados que usa guias para todas as chaves substitutas ... não é divertido.
217 Andy Andy
2
Não. Usar GUIDs não é divertido. Não gosto deles, mas respeito o valor deles em certos casos de uso.
CaM
2

Pondo de lado:

  • As guerras religiosas (google substituto vs chave natural)
  • A questão separada de quais índices em cluster definir em suas tabelas
  • A viabilidade de armazenar em cache todos os seus dados

Desde que você esteja usando a exclusão / atualização em massa, quando apropriado, e possua índices para dar suporte a essas operações, acho que você não terá problemas devido ao padrão PK usado.
É possível que, se você tiver o EF mais tarde, gerar consultas com junções, etc., que elas não sejam tão eficientes quanto seriam em um repositório baseado em chave natural, mas eu não sei o suficiente sobre essa área para dizer com certeza de qualquer maneira.

º
fonte
4
Não consigo pensar em um único caso em que uma junção em uma chave natural seria mais eficiente do que uma junção em um número inteiro - poucas chaves naturais podem ser menores que 4 bytes e, se houver, não pode haver apenas o suficiente. linhas para fazer a diferença material.
Aaron Bertrand
Para SQL competente e otimizado, eu concordo, mas estava me referindo a possíveis limitações dos geradores de SQL. Minha única experiência nesta área está sendo solicitada a criar visualizações extensas com as quais a EF poderia ser alimentada com colher - embora seja possível que os desenvolvedores de .net não soubessem o suficiente sobre a EF ou que houvesse outros motivos.
TH
@AaronBertrand, eu diria que a única maneira pela qual eles podem ser mais eficientes é se uma junção não for necessária. Os únicos lugares em que considero o uso de chaves naturais são as listas de códigos padrão, como os códigos de moeda ISO4127 (reconhecíveis pelo homem), e posso usar GBP, EUR etc. como chave estrangeira para uma chave primária ou alternativa no código da moeda mesa.
David Aldridge
@ David Claro, eu estava falando sobre casos em que as junções são necessárias. Existem muitos casos em que eu não quero que a chave natural prolifere em todas as tabelas relacionadas, porque as chaves naturais podem mudar, e isso é algo doloroso.
Aaron Bertrand
Hmmm, vejo como minha resposta poderia ser mal interpretada ao promover chaves estrangeiras naturais em vez de substitutas. Para ser claro, na verdade só os mencionei porque a) li a pergunta de Alexei como "é um problema que não usamos chaves naturais?", B) a pergunta de finalização de Alexei começou com "da perspectiva de um DBA" e eu achava que deveria reconhecer que há mais de uma perspectiva ec) porque acho que os recursos do ORM a serem usados ​​determinam amplamente a escolha (se realmente pode fazer a diferença). Estou firmemente no campo substituto de chaves estrangeiras.
TH
2

Você tem alguns fatores para ajudá-lo,

  1. Definição e especificações.

    Se algo é definido como único pela tarefa ou pelas leis da física, você está perdendo seu tempo com uma chave substituta.

  2. Singularidade.

    Para sanidade pessoal, junções e funcionalidade de banco de dados de nível superior, você precisará de: (a) coluna exclusiva, (b) série exclusiva de colunas

    Todos os esquemas suficientemente normalizados (1NF) fornecem um dos seguintes. Caso contrário, você deve sempre criar um. Se você tem uma lista de pessoas como voluntária no domingo, e inclui sobrenome e nome, convém saber quando tiver dois Joe Bobs.

  3. Implementação e otimização.

    Um int tende a ser um pequeno formulário de dados que é rápido para comparação e igualdade. Compare isso com uma cadeia Unicode cujo agrupamento pode depender da localidade (local e idioma). Armazenar um 4242 em uma sequência ASCII / UTF8 é de 4 bytes. Armazená-lo como um número inteiro cabe em 2 bytes.

Então, quando se trata de desvantagens, você tem alguns fatores.

  1. Confusão e ambiguidade.

    1. A entrada do blog @Aaron Bertrand resume bem isso. Não é auto-documentado ter um OrderID pela especificação e tarefa e, em seguida, impor um " OrderID " por meio da implementação do banco de dados. Às vezes, você precisa esclarecer isso ou criar uma convenção, mas é provável que isso adicione confusão.
  2. Espaço.

    Os números inteiros ainda adicionam espaço à linha. E, se você não os estiver usando, não há propósito.

  3. Clustering.

    Você só pode solicitar seus dados de uma maneira. Se você impõe uma chave substituta desnecessária, você agrupa dessa maneira ou da maneira da chave natural?

Evan Carroll
fonte
Prós e contras agradáveis ​​e curtos.
Alexei
@Alexei obrigado, considere marcá-lo como escolhido se ele atender ao que você está procurando. Ou, pedindo esclarecimentos.
Evan Carroll