Razões para evitar grandes valores de ID

17

Estamos trabalhando em um aplicativo Web, ainda não acessível aos usuários. Meu chefe percebeu que os registros recém-criados recebem um ID acima de 10.000, mesmo que tenhamos apenas menos de 100 registros na tabela. Ela assumiu que, por algum motivo, a interface da web cria mais de 100 vezes mais registros temporários do que os reais (e os exclui) e que isso pode nos levar a ficar fora do alcance alguns meses após o lançamento.

Não acho que ela esteja certa sobre a causa da inflação de identidade (a colega que pode responder a isso está de férias, então não sabemos ao certo), mas vamos supor que sim. Ela disse que odiaria usar uma coluna bigint e que gostaria que parássemos de aumentar automaticamente a coluna ID e escrevesse o código do servidor que escolhe o primeiro número inteiro "não utilizado" e o usa como ID.

Sou um estudante de ciências da computação com pouca experiência prática, desempenhando um papel de desenvolvedor júnior. Ela tem anos de experiência no gerenciamento de todos os bancos de dados de nossa organização e no design da maioria deles. Eu acho que ela está incorreta nesse caso, que um ID grande não é motivo para temer e que imitar a funcionalidade DBMS cheira a um antipadrão. Mas ainda não confio no meu julgamento.

Quais são os argumentos a favor e contra cada posição? Que coisas ruins podem acontecer se usarmos um bigint e quais são os perigos de reinventar a funcionalidade de incremento automático da roda ? Existe uma terceira solução que é melhor que uma? Quais poderiam ser as razões dela para evitar uma inflação dos valores de face do DI? Também estou interessado em ouvir sobre razões pragmáticas - talvez grandes identificações funcionem na teoria, mas causem dores de cabeça na prática?

Não é esperado que o aplicativo processe quantidades muito grandes de dados. Duvido que atingirá 10.000 registros reais nos próximos anos.

Se isso fizer alguma diferença, estamos usando o Microsoft SQL Server. O aplicativo é escrito em C # e usa Linq to SQL.

Atualizar

Obrigado, achei as respostas e comentários existentes interessantes. Mas receio que você tenha entendido mal a minha pergunta, então eles contêm o que eu queria saber.

Não estou realmente preocupado com o verdadeiro motivo das altas identificações. Se não conseguirmos encontrar por conta própria, eu poderia fazer uma pergunta diferente. O que me interessa é entender o processo de decisão neste caso. Para isso, suponha que o aplicativo esteja gravando 1000 registros por dia e excluindo 9999 deles . Tenho quase certeza de que não é esse o caso, mas é nisso que minha chefe acredita quando ela faz seu pedido. Portanto, nessas circunstâncias hipotéticas, quais seriam os prós e os contras de usar bigint ou escrever nosso próprio código que atribuirá IDs (de uma maneira que reutilize as IDs de registros já excluídos, para garantir que não haja lacunas)?

Quanto ao motivo real, suspeito fortemente que isso tenha acontecido porque uma vez escrevemos código para importar dados de outro banco de dados, como prova de conceito de que uma migração posterior pode ser realizada até certo ponto. Acho que meu colega realmente criou vários milhares de registros durante a importação e depois os excluiu. Preciso confirmar se esse foi realmente o caso, mas, se for, não há necessidade de ação.

rumtscho
fonte
Veja a publicação de SM Ahasan Habib em codeproject.com/Tips/668042/…
RLF
Você pode esclarecer? Os novos IDs simplesmente obtêm valores> 10000? Ou é que os novos IDs têm intervalos de 10000? E quantos IDs são estimados para serem necessários na vida futura do aplicativo?
user2338816
1
A respeito de encontrar o primeiro ID não utilizado, há um capítulo exatamente sobre isso no livro de Bill Karwin "SQL Antipatterns". Então, sim, certamente pode ser visto como um antipadrão!
Thomas Padron-McCarthy

Respostas:

24

Sem ver o código, é muito difícil dizer conclusivamente o que está acontecendo. Embora, provavelmente, o IDENTITYvalor esteja sendo armazenado em cache, causando lacunas no valor após a reinicialização do SQL Server. Consulte /programming/17587094/identity-column-value-suddenly-jumps-to-1001-in-sql-server para obter boas respostas e informações sobre isso.

Um INTcampo simples pode conter valores de até 2.147.483.647. Você pode realmente iniciar o valor da identidade em -2.147.483.648, fornecendo 32 bits de valores completos. 4 bilhões de valores distintos. Duvido muito que você fique sem valores para usar. Supondo que seu aplicativo esteja consumindo 1.000 valores para cada linha real adicionada, você precisará criar quase 12.000 linhas por dia todos os dias para ficar sem IDs em 6 meses, desde que você inicie o IDENTITYvalor em 0 e esteja usando uma INT. Se você estivesse usando um BIGINT, teria que esperar 21 milhões de séculos antes de ficar sem valores se escrevesse 12.000 linhas por dia, consumindo 1.000 "valores" por linha.

Dito tudo isso, se você quiser usar BIGINTcomo o tipo de dados do campo de identidade, certamente não há nada de errado nisso. Isso dará a você, para todos os efeitos, um suprimento ilimitado de valores a serem usados. A diferença de desempenho entre um INT e um BIGINT é praticamente inexistente no hardware moderno de 64 bits e é altamente preferível ao uso, por exemplo, NEWID()para gerar GUIDs, por exemplo .

Se você quiser gerenciar seus próprios valores para a coluna ID, poderá criar uma tabela de chaves e fornecer uma maneira bastante segura de fazer isso usando um dos métodos mostrados nas respostas nesta pergunta: Manipulando o acesso simultâneo a uma tabela de chaves sem bloqueios no SQL Server

A outra opção, supondo que você esteja usando o SQL Server 2012+, seria usar um SEQUENCEobjeto para obter valores de ID para a coluna. No entanto, você precisará configurar a sequência para não armazenar em cache valores. Por exemplo:

CREATE SEQUENCE dbo.MySequence AS INT START WITH -2147483648 INCREMENT BY 1 NO CACHE;

Em resposta à percepção negativa do seu chefe de números "altos", eu diria que diferença isso faz? Supondo que você use um INTcampo, com um IDENTITY, você poderia realmente iniciar o IDENTITYat 2147483647e "incrementar" o valor em -1. Isso não faria absolutamente nenhuma diferença no consumo de memória, desempenho ou espaço em disco usado, pois um número de 32 bits tem 4 bytes, não importa se é 0ou não 2147483647. 0em binário é 00000000000000000000000000000000quando armazenado em um INTcampo assinado de 32 bits . 2147483647é01111111111111111111111111111111- ambos os números ocupam exatamente a mesma quantidade de espaço, tanto na memória quanto no disco, e ambos exigem exatamente a mesma quantidade de operações da CPU para serem processadas. É muito mais importante que o código do aplicativo seja projetado corretamente do que ficar obcecado com o número real armazenado em um campo-chave.

Você perguntou sobre os prós e os contras de (a) usar uma coluna de ID de maior capacidade, como a BIGINT, ou (b) apresentar sua própria solução para evitar falhas de ID. Para responder a essas preocupações:

  1. BIGINTem vez de INTcomo o tipo de dados para a coluna em questão. O uso de a BIGINTrequer o dobro da quantidade de armazenamento, tanto em disco quanto em memória da própria coluna. Se a coluna for o índice de chave primária da tabela envolvida, todo e qualquer índice não clusterizado anexado à tabela também armazenará o BIGINTvalor, com o dobro do tamanho de um INT, novamente na memória e no disco. O SQL Server armazena dados em disco em páginas de 8 KB, onde o número de "linhas" por "página" depende da "largura" de cada linha. Portanto, por exemplo, se você tiver uma tabela com 10 colunas, cada uma INT, poderá armazenar aproximadamente 160 linhas por página. Se essas colunas em vez dissoBIGINTcolunas, você poderá armazenar apenas 80 linhas por página. Para uma tabela com um número muito grande de linhas, isso significa claramente que a E / S necessária para ler e gravar a tabela será o dobro neste exemplo para qualquer número determinado de linhas. Concedido, este é um exemplo bastante extremo - se você tivesse uma linha que consiste de um único INTou BIGINTde coluna e uma única NCHAR(4000)coluna, você ficaria (simplista) recebendo uma única linha por página, se você usou um INTou um BIGINT. Nesse cenário, não faria muita diferença apreciável.

  2. Rolar o seu próprio cenário para evitar lacunas na coluna ID. Você precisaria escrever seu código de forma que a determinação do "próximo" valor de ID a ser usado não entre em conflito com outras ações que estão acontecendo na tabela. Algo SELECT TOP(1) [ID] FROM [schema].[table]parecido com ingênuo vem à mente. E se houver vários atores tentando gravar novas linhas na tabela simultaneamente? Dois atores poderiam facilmente obter o mesmo valor, resultando em um conflito de gravação. Para solucionar esse problema, é necessário serializar o acesso à tabela, reduzindo o desempenho. Existem muitos artigos escritos sobre esse problema; Vou deixar para o leitor fazer uma pesquisa sobre esse tópico.

A conclusão aqui é: você precisa entender seus requisitos e estimar adequadamente o número de linhas e a largura da linha, juntamente com os requisitos de simultaneidade do seu aplicativo. Como sempre, depende.

Max Vernon
fonte
4
+1, mas eu não descartaria os requisitos de espaço do BIGINT. Não muito pelo espaço no disco, mas pela E / S e pelo espaço desperdiçado na memória. Você pode compensar muito disso usando a compactação de dados, para não sentir o peso do tipo BIGINT até superar os 2 bilhões. Idealmente, eles resolveriam o problema (hesito em chamá-lo de bug) - enquanto as pessoas não se importam com as lacunas e as pessoas não devem reiniciar seus servidores 15 vezes por dia, temos os dois cenários. bastante prevalente e frequentemente em conjunto.
Aaron Bertrand
3
Pontos muito válidos, Aaron, como sempre. Eu tenderia a usar uma INT de qualquer maneira, já que o BIGINT é um exagero total, a menos que eles esperem um grande número de linhas.
Max Vernon
Um tipo de dados BIGINT para uma coluna de ID não terá muito impacto na memória, a menos que você tenha centenas de milhares ou mais deles na memória ao mesmo tempo. Mesmo assim, é provável que seja uma pequena fração do tamanho total da linha.
user2338816
2
@ user2338816 esse é o ponto - se a tabela ficar grande, haverá muitas na memória. E como a coluna de identidade normalmente é a chave de cluster, são 4 bytes extras para cada linha em cada índice também. Será que isso importa em todos os casos? Não. Deve ser ignorado? Absolutamente não. Parece que ninguém se importa com a escalabilidade até que seja tarde demais.
Aaron Bertrand
3
Mas se você faz tem uma expectativa legítima de que você pode precisar de bigintvocê provavelmente vai agradecer a si mesmo para decidir que com antecedência ao invés de precisar adicionar este para uma tabela com bilhões de linhas.
Martin Smith
6

A principal tarefa a fazer é encontrar a causa raiz do motivo pelo qual o valor atual é tão alto.

A explicação mais razoável para as versões do SQL Server anteriores ao SQL2012 - supondo que você esteja falando de um banco de dados de teste - seria que houve um teste de carga seguido de uma limpeza.

A partir do SQL2012, o motivo mais provável deve-se a várias reinicializações do Mecanismo SQL (conforme explicado no primeiro link fornecido pelo Max).

Se a lacuna é causada por um cenário de teste, não há razão para me preocupar do meu ponto de vista. Mas, por segurança, eu verificaria os valores de identidade durante o uso normal do aplicativo, bem como antes e depois da reinicialização do mecanismo.

É "engraçado" que a MS afirme que ambas as alternativas (o sinalizador de rastreamento 272 ou o novo objeto SEQUENCE) podem afetar o desempenho.

Pode ser a melhor solução para usar o BIGINT em vez do INT, apenas para estar do lado seguro para cobrir os próximos "aprimoramentos" do MS ...

Lmu92
fonte
Provavelmente, formulei minha pergunta da maneira errada, mas não estou realmente interessada em encontrar a causa. Há uma alta probabilidade de que seja algo que não aparecerá novamente (resultados de uma execução de teste) ou uma má decisão de design no aplicativo, que pode ser resolvida fora do banco de dados. O objetivo era entender por que um DBA experiente consideraria os IDs altos ruins ou piores do que rolar nosso próprio gerenciamento de IDs.
rumtscho
2

Rumtscho, Se você estiver criando apenas 1000 linhas por dia, há pouco a decidir - use o tipo de dados INT com um campo Identity e pronto. A matemática simples diz que se você der ao seu aplicativo um ciclo de vida de 30 anos (improvável), poderá ter 200.000 linhas por dia e ainda estar dentro do intervalo positivo de números de um tipo de dados INT.

O uso do BigInt é um exagero no seu caso, mas também pode causar problemas se o aplicativo ou os dados forem acessados ​​via ODBC (como trazidos para o Excel ou MS Access, etc.), o Bigint não se traduz bem na maioria dos drivers ODBC para aplicativos de desktop.

Quanto aos GUIDS, além do espaço em disco extra e da E / S extra, há o enorme problema de que eles são projetados não sequenciais; portanto, se eles fazem parte de um índice classificado, você pode adivinhar que cada inserção será exigem que o índice seja utilizado. --Jim

jimo3
fonte
Bom argumento sobre os GUIDs, a menos que você use NEWSEQUENTIALID () - eu ainda concordo, não há grandes razões para usá-los aparentes nesta pergunta.
Max Vernon
1

Existe uma lacuna entre os valores usados? Ou os valores iniciais são 10.000 e a partir de então todos estão adicionando 1? Às vezes, se o número for fornecido aos clientes, o número inicial será maior que zero, digamos 1500 por exemplo, para que o cliente não perceba que o sistema é "novo".

A desvantagem de usar bigint em vez de smallint é que, como bigint usa "mais espaço em disco", ao ler um disco, você lê menos blocos de disco para cada disco. Se o espaço da sua linha for pequeno, isso pode ser uma desvantagem; caso contrário, não importa muito. Além disso, não importa muito se você não está consultando muitos recursos de uma só vez e se possui os índices adequados.

E, como dito em outra resposta, se você se preocupa com a falta de índices, não deve se preocupar, pois o smallint pode lidar a menos que tenha um negócio milionário. Inventar um mecanismo para "recuperar IDs" é caro e adiciona pontos de falha e complexidade ao software.

Saudações

ctutte
fonte
2
O OP está vendo lacunas na reinicialização do serviço. Isso ocorre por causa desse problema . Além disso, não acho que um smallint seja uma boa compensação a curto prazo pelo trabalho necessário para corrigi-lo mais tarde.
Aaron Bertrand
@AaronBertrand, na verdade, temo que outras pessoas não tenham entendido isso quando sugeriram essa possibilidade. Tenho certeza de que essa não é a causa dos números altos, mas, mesmo que fosse, não estava tentando encontrar a causa, mas para aprender quais argumentos podem ser a favor e contra as soluções propostas. Veja minha atualização para detalhes.
rumtscho
@rumtscho, na verdade, esta resposta destaca um bom ponto, mesmo que não atenda diretamente à sua pergunta: "Inventar um mecanismo para 'recuperar IDs' é caro e acrescenta pontos de falha e complexidade ao software".
Doktor J
@DoktorJ Eu concordo com você. Fui a pessoa que aprovou a resposta :) Só queria esclarecer o mal-entendido, por isso deixei meu primeiro comentário.
rumtscho
1

Se eu fosse seu chefe, eu ficaria mais interessado nos motivos dos valores inesperadamente altos de ID ... do jeito que eu o vejo, para cada um dos dois cenários descritos:

  1. Se os testes anteriores aumentassem os valores de identidade - seus outros comentários sobre o número esperado de registros também me levariam a sugerir um tipo de chave menor. Sinceramente, eu também consideraria se era possível redefinir a sequência e renumerar os registros existentes se o teste estivesse fora do caractere para o uso pretendido atual da tabela (a maioria consideraria esse exagero - 'depende').

  2. Se a maioria dos registros gravados na tabela for excluída logo depois, eu estaria inclinado a considerar o uso de duas tabelas; uma tabela temporária em que os registros não são mantidos a longo prazo e outra em que apenas os registros que criaremos permanentemente são mantidos. Novamente, suas expectativas em relação ao número de registros de longo prazo sugerem o uso de um tipo menor para sua coluna-chave, e alguns registros por dia dificilmente farão com que um problema de desempenho 'mova' um registro de uma tabela para outra similar. 1. Suspeito que não seja o seu cenário, mas imagine que um site de compras possa preferir manter um Basket / BasketItem e, quando um pedido é realmente feito, os dados são movidos para o conjunto Order / OrderItem.

Para resumir; na minha opinião, os BIGINTs não devem necessariamente ser temidos, mas são francamente desnecessariamente grandes para muitos cenários. Se a tabela nunca aumentar, você nunca perceberá que houve um exagero na sua escolha de tipo ... mas quando tiver tabelas com milhões de linhas e muitas colunas FK que são BIGINT quando poderiam ter sido menores - então você pode desejar o os tipos foram selecionados de maneira mais conservadora (considere não apenas as colunas-chave, mas todas as colunas-chave da frente e todos os backups que você mantém, e assim por diante!). O espaço em disco nem sempre é barato (considere o disco SAN em locais gerenciados - ou seja, o espaço em disco é alugado).

Em essência, estou argumentando por uma consideração cuidadosa de sua seleção de tipos de dados sempre e não às vezes . Você nem sempre prevê os padrões de uso corretamente, mas acho que você tomará melhores decisões como regra, sempre assumindo que "quanto maior, melhor". Em geral, seleciono o menor tipo que pode conter a faixa de valores exigida e razoável e considerarei felizmente INT, SMALLINT e até TINYINT se achar que o valor provavelmente se encaixará nesse tipo no futuro próximo. No entanto, é improvável que os tipos menores sejam usados ​​com colunas IDENTITY, mas podem ser usados ​​alegremente com tabelas de pesquisa nas quais os valores-chave são definidos manualmente.

Finalmente, as tecnologias que as pessoas usam podem influenciar consideravelmente suas expectativas e respostas. Algumas ferramentas têm maior probabilidade de causar lacunas nos intervalos, por exemplo, pré-agendar intervalos de identidades por processo. Em contraste, o @DocSalvager sugere uma sequência auditável completa que parece refletir o ponto de vista do seu chefe; Pessoalmente, nunca exigi esse nível de autoridade - embora a regra geral de que as identidades sejam seqüenciais e geralmente sem lacunas tenha sido incrivelmente útil para situações de suporte e análise de problemas.

Nij
fonte
1

quais seriam os prós e os contras de usar bigint ou escrever nosso próprio código que atribuirá IDs (de maneira a reutilizar os IDs de registros já excluídos, para garantir que não haja lacunas)?

Usando bigintcomo uma identidade e vivendo com as lacunas:

  • é tudo funcionalidade embutida
  • você pode ter certeza de que funcionará imediatamente
  • desperdiçará espaço, pois intainda forneceria dados de cerca de 2 milhões de dias; mais páginas terão que ser lidas e escritas; índices podem se tornar mais profundos. (Nesses volumes, isso não é uma preocupação significativa).
  • uma coluna de chave substituta deve ser sem sentido, portanto as lacunas são aceitáveis. Se isso for mostrado aos usuários e as lacunas forem interpretadas como significativas, você estará fazendo errado.

Role o seu:

  • sua equipe de desenvolvimento fará todo o trabalho de desenvolvimento e correção de bugs para sempre.
  • você também quer preencher lacunas na cauda ou no meio também? Decisões de design para discutir.
  • toda gravação terá que emitir bloqueios fortes para impedir que processos simultâneos adquiram o mesmo novo ID ou resolva conflitos post facto .
  • Na pior das hipóteses, você precisará atualizar todas as linhas da tabela para fechar as lacunas se rowid = 1 for excluído. Isso martelará a simultaneidade e o desempenho, com todas as atualizações em cascata de chaves estrangeiras etc.
  • preenchimento de lacunas preguiçoso ou ansioso? O que acontece com a simultaneidade enquanto isso está acontecendo?
  • você precisará ler o novo ID antes de qualquer gravação = carga adicional.
  • será necessário um índice na coluna id para encontrar uma lacuna eficiente.
Michael Green
fonte
0

Se você está realmente preocupado em atingir o limite superior de INT para suas PKs, considere usar GUIDs. Sim, eu sei que são 16 bytes vs 4 bytes, mas o disco é barato.

Aqui está uma boa descrição de prós e contras.

Tim Goyer
fonte
4
+1 porque esta é uma solução, mas veja o comentário de Aaron na resposta de Max por um motivo pelo qual "disco é barato" não é um motivo para usar GUIDs sem ponderar cuidadosamente as opções.
Jack Douglas
1
Aqui está uma melhor write-up de um especialista índice e arquitetura SQL Server em vez de um desenvolvedor: sqlskills.com/blogs/kimberly/disk-space-is-cheap
Aaron Bertrand
Oh, e, claro, tomar cuidado com divisões de página de NEWID ()
Max Vernon
1
Meu chefe parece se opor a valores altos apenas porque eles parecem altos. Espero que esta pergunta me mostre mais objeções possíveis, mas se esse for um dos seus principais argumentos, ela provavelmente reagirá ainda mais negativamente aos GUIDs.
rumtscho
1
@rumtscho Diga ao seu chefe que um número substituto é apenas um número sem sentido ("tamanho" do número é irrelevante) e que as lacunas em uma sequência são naturais e inevitáveis.
Aaron Bertrand
0

Chaves primárias do RDBMS (coluna geralmente denominada 'ID') As
lacunas não podem ser evitadas nas colunas (campos) de aumento automático do RDBMS. Eles são destinados principalmente à criação de PKs exclusivas. Para desempenho, os principais produtos os alocam em lotes, de modo que os mecanismos de recuperação automática para várias falhas de operação normal podem resultar em números não utilizados. Isto é normal.

Sequências Ininterruptas
Quando você precisa de um número de sequência ininterrupto, como é normalmente esperado pelos usuários, deve ser uma coluna separada atribuída programaticamente e não deve ser a PK. Portanto, todos esses 1000 registros podem ter o mesmo número nessa coluna.

Por que os usuários desejam sequências ininterruptas?
Os números de sequência ausentes são o sinal mais básico de erro descoberto em qualquer tipo de auditoria. Este princípio da "contabilidade-101" é onipresente. No entanto, o que funciona para um pequeno número de registros mantidos manualmente, tem um problema sério quando aplicado a um número muito grande de registros nos bancos de dados ...

Reutilização de valores-chave para registros não relacionados invalida o banco de dados O
uso do "primeiro número inteiro não utilizado" introduz a probabilidade de que, em algum momento no futuro, um número seja reutilizado para registros não relacionados ao original. Isso torna o banco de dados não confiável como uma representação precisa dos fatos. Essa é a principal razão pela qual os mecanismos de incremento automático são projetados propositadamente para nunca reutilizar um valor.

DocSalvager
fonte