Executando verificação de segurança...
75

CPF/CNPJ: estudo de performance em banco de dados, devemos armazenar um inteiro mesmo?

Com a nova mudança no formato do CNPJ, temos uma discussão que voltou em alta. Lá vem o coitado que armazena documento em formato numérico. Porém, essa é uma guerra muito antiga e todo mundo já conhece.

Curiosamente esse movimento me lembrou de quando fiz engenharia de software, há mais de 15 anos atrás. Uma das primeiras matérias foi "Introdução a Banco de Dados". Naquela época, já havia a mesma discussão.

Um dos trabalhos do período, inclusive, foi montar uma tese em relação a performance de diferentes dados. E eu acabei fazendo a exploração de vários tipos de jeitos de armazenar um CPF em uma base de dados. Não achei a tese original, contudo uso a solução dela até hoje e consegui montar uma linha de raciocínio parecida.

Bora descobrir finalmente qual é o melhor jeito de armazenar esse tipo de dado!

Metodologia

Antes de qualquer coisa, preciso criar uma hipótese. Uma das coisas mais benéficas do ambiente acadêmico é o aprendizado sobre construção de testes a partir de uma hipótese e a importância de utilizar alguma metodologia para conduzir seu estudo. Não basta que eu diga "VARCHAR é melhor", preciso provar.

A minha hipótese é “não há nada que justifique armazenar CPFs como inteiro e não é a melhor opção disponível”. A metodologia que vou adotar para tentar sustentar essa hipótese consiste nas seguintes etapas:

  1. Gerar uma amostragem com 2 milhões de CPFs aleatórios que não se repetem;
  2. Coletar alguns CPFs de controle (aleatórios) dentro da amostragem;
  3. Montar diferentes esquemas de tabela e inserir os mesmos CPFs gerados em cada uma dessas tabelas;
  4. Executar uma query de seleção para cada um dos CPFs de controle e extrair alguns indicadores de performance.

Os esquemas de tabela adotados:

  • table_a: Uma única coluna cpf do tipo BIGINT (inteiro sem índice);
  • table_b: Uma única coluna cpf do tipo BIGINT INDEX (inteiro com índice);
  • table_c: Uma única coluna cpf do tipo CHAR(11) (CHAR sem índice);
  • table_d: Uma única coluna cpf do tipo CHAR(11) + INDEX (CHAR com índice);
  • table_e: Uma única coluna cpf do tipo VARCHAR(11) (VARCHAR sem índice);
  • table_f: Uma única coluna cpf do tipo VARCHAR(11) + INDEX (VARCHAR com índice);
  • table_g: Uma única coluna cpf do tipo CHAR(11) e uma coluna crc32 do tipo BIGINT GENERATED ALWAYS AS (CRC32(cpf)) STORED (CRC32 sem índice);
  • table_h: Uma única coluna cpf do tipo CHAR(11) e uma coluna crc32 do tipo BIGINT GENERATED ALWAYS AS (CRC32(cpf)) STORED + INDEX (CHAR com índice);

Pela forma como banco de dados funcionam com e sem índice, os CPFs de controle precisam estar bem distribuídos para favorecer as melhores condições de cada tabela, mas também forçar as piores condições. Dessa forma:

  • 10 CPFs aleatórios foram coletados em um intervalo de 1000 no início da inserção (após 1000 registros);
  • 10 CPFs aleatórios foram coletados em um intervalo de 1000 no meio da inserção (após 1 milhão de registros);
  • 10 CPFs aleatórios foram coletados em um intervalo de 1000 no final da inserção (antes de 2 milhões de registros);

Os indicadores de performance são:

  • Tempo execução das instruções INSERT;
  • Tempo médio de resposta das instruções SELECT;
  • Total em megabytes acumulado por cada tabela + índice;
  • Cálculo de operação realizado pelo banco de dados.

⚠️ Aviso: Os testes foram realizados em uma máquina Ubuntu 20.04/Linux com Intel i7-12700K, 64 GB de RAM e MySQL v8.

Primeira Etapa: Geração de CPFs

A primeira etapa é bem simples, gerar 2 milhões de CPFs aleatórios e únicos. A princípio pouco importa para a gente a autenticidade do CPF, uma vez que isso é irrelevante para fins de busca. Logo, é necessário gerar uma string que possua:

  • 11 caracteres, onde cada um desses caracteres deve variar entre o valor 0 e 9;
  • E uma regra de integridade para evitar a repetição de muitos zeros em sequência (ignorando resultados como 000.001.453-56, mas aceitando resultados como 012.145.002-12).

Outro ponto necessário é o uso de multithreading. Desse modo, conseguimos otimizar o tempo que irá levar para gerar os 2 milhões de CPFs necessários (do contrário, teria que esperar algum tempinho dependendo da CPU).

  • A função generate_cpf apenas aplica as regras necessárias para que sejamos capaz de gerar uma string válida para os testes;
  • A função generate_unique_cpfs_process gera uma quantidade finita de CPFs para aquele processo, sendo essa quantidade uma parte do todo (total/processos);
  • A função generate_unique_cpfs inicia todos os processos, aguarda os CPFs gerados e faz a união desses conjuntos removendo as colisões. Por fim, adiciona mais alguns CPFs após a remoção caso não tenha completado os 2 milhões necessários.
  • Encerramos armazenando todos esses CPFs em um CSV.

Ver código-fonte

Segunda Etapa: CPFs de Controle

Com os CPFs em mãos, chegou a hora da coleta. Nessa parte, efetuei um processo bem manual. Naveguei até as linhas 1000, depois 1000000 e por fim 2000000; então selecionei 1 CPF aleatório iniciado com cada um dos dígitos de 0 a 9, totalizando 10 CPFs por cada localização. Essa lista foi salva em um arquivo TXT (reserved_cpfs.txt) um por linha.

Terceira Etapa: Preparação da Base de Dados

Para montagem da base de dados foi utilizado o MySQL, certamente outras bases de dados terão respostas diferentes. A intenção aqui é criar todas as tabelas e depois inserir, antes é preciso que o banco de dados já tenha sido criado com CREATE DATABASE.

Da mesma forma que anteriormente, temos um problema ao lidar com 2 milhões de registros. A velocidade de inserção para tantos esquemas diferentes pode demorar horas, por tanto, é necessário utilizar novamente multithreading. Esses processos entretanto não afetam o tempo de resposta do banco de dados (que será coletado), uma vez que são interpretados como novas conexões simultâneas.

  • Na função create_tables estão todos os esquemas de tabelas que serão utilizadas, para cada um deles foi criado uma versão com índice e sem índice (como mencionado anteriormente);
  • As funções insert_cpfs_process e insert_cpfs tem o objetivo de deixar o processo mais eficiente além de fazer as devidas inserções.

(Ver código-fonte)[https://gist.github.com/caiquearaujo/a7599da5801f1163776d274e46e012d1]

Quarta Etapa: Teste de Performance

Depois de preparar todo o setup é hora de executar as queries. Os dados que são coletados aqui serão salvos em um arquivo JSON. Para cada uma das tabelas serão executadas 30 queries com os CPFs de controle, mais uma de uma instrução para obter o tamanho da tabela e outra para entender detalhes da lógica utilizada para obter um CPF.

Ver código-fonte

Resultados

Depois de coletar todos esses dados finalmente chegamos aos resultados e a análise deles. Primeiro, vamos destrinchar cada uma das tabelas. Para fins de comparação, todas pegaram o mesmo CPF da última faixa de controle (os últimos 1000).

Os parâmetros importantes a serem considerados nos resultados a seguir são:

  • query_cost: Esse valor é uma estimativa do custo total da query em termos de recursos, como I/O e CPU. Um custo mais alto sugere que a query é "cara", o que pode afetar a performance se usada frequentemente;

  • access_type: O tipo de varredura que foi escolhida pelo MySQL para acessar a tabela. Aqui conseguimos identificar se o índice foi utilizado ou não.

  • rows_examined_per_scan: O número estimado de linhas que o MySQL precisará examinar durante a varredura completa antes de aplicar a filtragem WHERE;

  • read_cost: O custo estimado de leitura ao fazer a varredura completa da tabela;

  • eval_cost: O custo estimado de avaliar o filtro WHERE em cada linha;

  • prefix_cost: A soma de read_cost e eval_cost, o custo total para a execução da query;

  • data_read_per_join: A quantidade de dados esperada para ser lida e processada por esse join.

table_a, BIGINT, sem índice

Para localizar o CPF na table_a, o MySQL está realizando uma varredura quase completa (1.465.034 de registros lidos antes da filtragem). Nem é necessário comentar que o custo dessa query é absurdo e provavelmente irá degradar bastante o tempo de resposta.

Ver JSON

table_b, BIGINT, com índice

Em comparação com a anterior, deu para notar que a table_b é bem mais eficiente. O uso de índice em números inteiros faz a total diferença. Apenas 16 registros precisaram ser lidos para encontrar o CPF desejado. Ademais, nenhum problema de performance é indicado nem de longe.

Ver JSON

table_c, CHAR, sem índice

Como esperado para a table_c usar o CHAR sem índice é 33% pior do que usar inteiro sem índice. Totalmente descartado como opção.

Ver JSON

table_d, CHAR, com índice

Em relação a complexidade da query, tivemos o mesmo custo de uma query de inteiros com índice. Porém, tivemos um aumento de 200% de dados lidos para encontrar o resultado. O que pode indicar que alguns CPFs, podem custar um pouquinho mais do que outros. Embora o custo não preocupe, quanto mais queries forem sendo executadas, a quantidade de dados lidos poderá afetar mais o desempenho do que em comparação com a table_b.

Ver JSON

table_e, VARCHAR, sem índice

É o mesmo cenário da table_c, o VARCHAR sem índice consegue perder do CHAR em dados lidos. Um desempenho 11% pior, porque ele precisou ler 1 milhão de registros a mais antes de encontrar o resultado.

Ver JSON

table_f, VARCHAR, com índice

O VARCHAR com índice tem o mesmo desempenho do que a table_d (CHAR com índice). Algo já esperado, pois, a estrutura de busca indexada deles é muito parecida. Nesse caso, a escolha entre esses dois tipos de dados tem exatamente haver com o tipo de dado que você está armazenando, sendo que com VARCHAR você vai gastar alguns bytes a mais.

Ver JSON

table_g, CHAR + CRC32, sem índice

Dentre os dados preliminares, está eleita a pior estrutura de todas. Porém, aqui tem algumas coisas bem curiosas sobre a forma de operação das queries. Veja:

  • Embora a instrução SQL tenha sido montada como SELECT * FROM {table} WHERE crc32 = CRC32(%s) AND cpf = %s, o MySQL ignorou a ordem descrita na query (executou ((cpfs_data.table_g.cpf = '99786051285') and (cpfs_data.table_g.crc32 = 946011781))). Isso aconteceu pois como nenhuma otimização foi calculada, ele seguiu a ordem das colunas na tabela. PS: É importante definir bem a ordenação das colunas ou uma delas (CHAR, no caso) pode desacelerar toda a query;
  • Outro ponto interessante é que, embora o desempenho seja horrível, a inclusão da coluna crc32 reduziu consideravelmente a quantidade de registros lidos (para 1 milhão) comparado as demais queries sem índices. Será que existe uma oportunidade aqui para encontrar uma solução adequada?

Ver JSON

table_h, CHAR + CRC32, com índice

A análise dessa tabela é complicada. Se levarmos em consideração o custo da query, em comparação as demais com índices, essa estrutura foi 0,92% pior. Não que seja um número absurdo, mas dado o esforço de gerenciar duas colunas… o suficiente para deixar muitos com pé atrás. Antes de desistir, vamos nos atentar para alguns detalhes adicionais:

  • Dessa vez o crc32 foi aplicado primeiro (e ainda seria independente da ordem da query), afinal ele é um índice disponível para consulta;
  • Além disso, o check de redundância cíclica de 32 bits tem algumas vantagens: (1) a geração dele não custa tanto esforço; (1) quando gerado, é um inteiro que varia entre 8 e 10 dígitos, isso é ainda menos que os 11 dígitos do CPF e seria menos ainda que os 14 dígitos do CNPJ. O que acaba reduzindo consideravelmente a quantidade de registros lidos (dessa vez, menos de 5);
  • Por fim, aplicamos o filtro no CHAR para sanar colisões que vão acontecer. Concluímos que, por conta disso, essa estratégia exige mais cuidado ao escrever as queries. Sempre as duas colunas devem ser chamadas para pesquisar por um "único" dado.

Ver JSON

Tamanho do Armazenamento

Os tempos mudam. Antigamente, um dos grandes objetivos era tornar o espaço de armazenamento eficiente. Em alguns casos, até acima da performance. Armazenar era caro e cada gigabyte mais poderia custar milhares de dólares a uma empresa. Hoje em dia não é assim.

Hoje competimos pelo custo de processamento, não pelo custo de armazenamento. Você consegue ter um grande volume de dados armazenado por quase nada. Ainda sim, temos que apresentar a realidade. Dê uma olhada no gráfico a seguir:

Gráfico

Em uma observação inicial vemos uma escada nos dois grupos (sem e com índice). Mas, observando os micro dados, olhem que interessante:

  • A versão de inteiro com índice é 60.87% maior do que a versão sem;
  • A versão de CHAR com índice é 57.81% maior;
  • A versão de VARCHAR com índice é 55.81% maior;
  • E, a versão CRC32 com índice é apenas 44.30% maior.

Ainda sim é importante considerar que a versão de inteiro é a que ocupa menos espaço. Mas, ao colocar a versão de inteiro com índice em comparação com a versão de CRC32 com índice só existe uma diferença de 23.24%. Quando somado os dois crescimentos, a razão entre essas versão é a mesma 1.6x em relação ao dado original. Na prática, em termos de armazenamento, CRC32 com índice tem o maior peso, mas não é tão substancialmente maior do que a versão mais econômica.

Tempo Médio de Resposta

Enquanto armazenamento está mais acessível, a capacidade de processamento é bem mais disputada. Além disso, o tempo de resposta é um dos fatores que mais impacta o usuário final. O risco de perder receita sempre está na ponta final. Ninguém quer passar segundos esperando a resolução do seu CPF.

Novamente, dê uma olhada no gráfico a seguir:

Gráfico

Nesse caso, o gráfico é alto explicativo... e a certeza absoluta é NÃO deixe de usar índices sempre para consultas filtradas. O impacto na performance já era evidente nas análises da query. Um tipo de query assim pode travar todo o sistema, afinal aqui ainda estamos em um ambiente controlado.

Outro ponto curioso é a performance do CHAR com índice, ele consegue ser melhor que inteiros e VARCHAR indexados. A delimitação de bytes (11) aqui ajuda bastante a ganhar alguma performance. Mas impressionante mesmo é a velocidade o CRC32 com índice, a quantidade de dados que ele precisa ler para encontrar uma resposta é tão insignificante que o retorno é quase imediato.

Tempo Médio de Inserção

Gráfico

Em relação as inserções, todas aquelas que tem índices sofreram mais o impacto. O fato da mais eficiente ser o VARCHAR sem índice é um benefício da sua alocação dinâmica, enquanto o CHAR sem índice é o oposto. Considerando que devemos ignorar a partir de agora as tabelas sem índice, pouco importa.

Agora o que deve ter chamado a sua atenção, despertado o seu olhar, foi o custo de inserção de um CRC32 com índice. Confesso que utilizei uma estratégia "preguiçosa", o tal BIGINT GENERATED ALWAYS AS (crc32(cpf)) STORED. Se ao invés de calcular em tempo de inserção, já tivesse inserido o CRC32 na query teríamos um ganho aqui. Será que o custo alto de resposta na hora da inserção (que acontece uma vez por CPF) é um problema?

Considerações Finais

Já que você leu até aqui está na hora de concluir. Considerando as observações e análises detalhadas, acredito que é válido dizer que:

  1. O uso de índices é essencial para manter a eficiência das consultas, especialmente em bases de dados de grande volume;
  2. Antes armazenar CPF como inteiro poupava armazenamento enquanto mantinha um bom tempo de resposta, hoje não há nada que justifique e, conforme minha hipótese, CRC32 consegue ser uma melhor opção pelo conjunto de benefícios trazidos.

A minha defesa é que a comparação entre os tipos de armazenamento indica que o uso de CRC32 com índice, embora levemente mais custoso em termos de armazenamento, tem uma performance superior em consultas, especialmente para buscas específicas e recorrentes. E acredito que atualmente esse é o maior objetivo da maioria dos sistemas.

Entretanto, confesso que o custo de inserção é um fator a ser ponderado. Alternativas como pré-calcular o valor do CRC32 antes da inserção podem minimizar esse impacto e aumentar a eficiência geral do sistema. No fim, a escolha entre as opções testadas deve, portanto, considerar a frequência das consultas vs a necessidade de inserção constante.

E para vocês CPF ainda deve ser inteiro?

Carregando publicação patrocinada...
7

Achei sua postagem sensacional, porém dá pra resumir com uma frase (o que de nehuma forma invalida ou dimunui a postagem, ela é extremanente útil para quem não quer a resposta pronta).

CPF é dado descritivo e não quantitativo, não se discute o tipo depois de saber disso, não precisa de testes de performance, ainda mais nos tempos atuais.

Notem que o teste foi feito em um DB, em outro pode dar resultados diferentes. Ainda é possível fazer outras variações para testar.

O CRC só faz sentido em cenários específicos e bem raros. Quase sempre será uma otimização prematura.

O custo de inserção costuma ser negligenciado e ele pode ser muito mais importante em muitos cenários. Hoje pode não fazer tanto sentido, mas eu trabalahei com um sistema que quase não tinha índices porque a inserção era mais importante. E por isso usava um modelo ISAM.

Veja também: https://pt.stackoverflow.com/q/136398/101.

Para saber um pouco mais veja em https://pt.stackoverflow.com/q/47871/101.

Não esqueça de ler os comentários abaixo porque apesar de uma imniciativa inicial o autor original mostrou que há falhas nesse raciocício e não demonstrou que a conclusão está correta. Não tome decisões em algo que pode ser só uma crença. Ele discorda de mim e depois ele discorda de si mesmo.


Farei algo que muitos pedem para aprender a programar corretamente, gratuitamente (não vendo nada, é retribuição na minha aposentadoria) (links aqui no perfil também).

1

Agradeço a contribuição, mas discordo que a opção por CRC seja em cenários específicos ou bem raros. CPF/CNPJ são dados descritivos, PORÉM, geralmente usados em condições WHERE. Se você não otimizar a busca, você vai criar gargalo no banco de dados. E a pior coisa que você pode fazer em um banco é a indexação equivocada de um ado.

Sobre o custo de inserção, no estudo, foram inseridos 20 lotes simultâneos contendo 10 mil CPFs. Por isso, o custo foi contaminado.

4

Ok, eu tenho uma experiência diferente, há 40 anos faço sistemas assim, que rodam em milhares de empresas de todos os portes, algumas com volumes imensons e isso não faz cócegas no db, nunca vi um caso em consultorias que eu dei, até quando o db era medonhamente mal modelado que isso faria diferença, mas mas pode ser então que eu pego os casos específicos e você pega o universal. Eu quis mostrar para todos que devem investigar melhor se é o caso delas antes de adotar por padrão, pode ser que o caso delas seja parecido com os meus. Quando surgir um caso que preiso otmizar, algo que eu sou tarado pra fazer, eu penso em alguma solução assim, talvez algo melhor que o CRC, mas certamente farei em cada sistema de db que eu for usar, porque não tem orimização universal (ao contrário da crença popular que algum pregam e por isso dizem que você deve fazer algo que possa trocar de db - que pode funcionar, mas estoura a performance em alguns pontos).

De qualquer forma a análise é boa, só a conclusão nem tanto. Podemos olhar para o horizonte e vê-lo sumindo e concluirmos que nossa visão não alcança tanto na Terra plana, ou podemo pegar mais dados, ouvir outras pessoas com experiências diferente e descobrir que na verdade some porque há um inclinação que é constante e concluírmos que a Terra é redonda. Dados são ótimos, mas o perigo é que eles podem não contar a estória toda e aprender algo errado.

Os dasos podem não ser bons também, mas não posso afirma isso. Eu acho estranho uma consulta com menos de 16 caracteres seja 15 vezes mais lenta que um número de 4 bytes. Eu ja fiz engine de banco de dados para aprendizado e se o MySQL faz uma khda dessas eu preciso começar jogar pedra nele, acho mais dicífil fazer algo ficar tão lento.

Aproveitando, eu não diria "NÃO deixe de usar índices sempre para consultas filtradas", muito provável, mas pode ter casos que isso não seja verdade, devemos sempre testar, olhar o contexto, e só adotar algo sem pensar se for algo bobo ou estar com extrema pressa, criando dívida técnica, que pode ser aceitável ou não.

Espero que as pessoas leiam tudo aqui para uma visão mais abrangente.

1

Hoje trabalho muito com a web moderna, no caso a busca incansável das big techs: menor custo de milissegundos. Como tenho foco em performance de resultado em negócios a partir de sistema, essas otimizações são importantes desde já. Um impacto de 500ms pode diminuir consideravelmente a conversão em vendas e quando você está investindo milhares de reais em tráfego pago, isso faz uma diferença monstruosa. Agora, se seu sistema é acessado por 100 pessoas, nem precisamos ter essa discussão 😅 SQLite já resolvia.

2

Tenho visto mesmo as coisas cada vez mais lentas com as soluçõs modernas que está fazendo por aí. Todos os e-commerces grandes que eu usei até hoje eu tive vontade de chorar usando e nenhum foi porque estava demorando para achar o CPF. Eu quase posso afirmar que são arquitetutras "modernas" responsáveis por isso. O Google é uma tragédia de lento em quase tudo, mas super rápido quando faz buscas nos textos, não precisam criar CRC ou algo assim.

Você tem sites pequenos que tem menos performance que o Stack Overflow que já foi um dos 30 sites mais acessados do mundo, e que já testaram rodando com alguma folga na maior parte do tempo com apenas um servidor. E eu sei de perto que eles não possuem essas otimizações prematuras de fazer um índice de 4 bytes em vez de 16 para conseguir deixar rápido, até porque qualquer pessoa que entenda um pouco de banco de dados sabe que isso é irrelevante na maioria dos casos. Inclusive a busca de textos possui vários defeitos, mas não tem lentidão alguma.

Quando precisa de performance extrema colocar em memória ou fazer um array de SSDs fará com que o gargalo deixe de ser o acesso ao banco de dados, inclusive se for só para consultas e escritas não tão grandes assim, enfileiradas, o SQLite pode dar conta até melhor que MySQL, SQL Server e outros.

Você tem dados concretos sobre o CRC dar resultado efeitvamente melhor, não só na teoria ou em teste controlado, e que talvez tenha algo errado, conforme apontei no comentário anterior? Eu preciso de algo mais para aceitar especulações genéricas

1

Adoraria que você contribuisse com os testes referentes ao que você está falando, referências ou alguma coisa que pudesse enriquecer a discussão. Porque sinceramente, sem ofenças, parece a opinião de alguém atrasado na indústria ou com pouquissimo contato com sistema de média/grande escala.

  1. O Google, assim como ecommerces em geral (Amazon, Americanas, Magazine Luiza, Kabum, Mercado Pago, esses são os que sei) usam search engine para indexar os seus dados. Não fazem busca em banco de dados pela ineficiência de resposta. Essas engines são otimizadas para buscas realmente performáticas em grandes volumes de textos. Temos algumas disponíveis no formato open source, como é o caso do Meilisearch (para projetos mais enxutos) ou da ElasticSearch (para projetos mairoes). Por tanto você está correto, o Google não precisa usar CRC porque sequer usam banco de dados relacional na finalidade de indexação;
  2. Eu não vou entrar no mérito de falar que sites pequenos são mais ineficientes que X/Y/Z. Desenvolvedor ruim tem de monte. Até me pareceu que você tem contato com o StackOverflow, que poderia ser um argumento bom, mas novamente eles não usam banco de dados relacional para pesquisa e sim a ElasticSearch. Eles migraram em ~2010 pela sobrecarga na base de dados. No caso deles, eles fazem FULL TEXT SEARCH, o que é pior ainda. Então com certeza eles se aproveitam de todas as otimizações da ferramenta;
  3. Eu realmente não entendi se você de fato tem experiência ou não porque se você precisa de performance em banco de dados, depois de trabalhar com a indexação você trabalha com shards. Não faz sentido colocar em memória. Em memória deve estar apenas o que a aplicação irá usar;
  4. Sim tenho dados concretos. Eu tenho uma software house de desenvolvimento de software baseada em performance, já arquitetei e produzi sistemas (principalmente financeiros) que lidam diariamente com uma média de 10 mil transações diárias. E, sim, a aplicação da estratégia mencionada no arquivo cortou o tempo de resposta enviada ao cliente em 6 vezes. Não fazia sentido implementar um motor de busca fora do banco relacional, então no caso índice foi a melhor estratégia. A partir deles começaram a fazer os shards pelos índices de CRC.

Por fim, infelizmente tenho que lhe repetir o mesmo, preciso de bem mais de você para aceitar suas especulações genéricas.

Como mencionou o Stack Overflow, segue algumas recomendações de leitura para entender o porque banco relacional não é utilizado em buscas:

No caso das buscas em texto, que você menciona, segue os caminhos adotados:

Compartilho mais informações problemas relacionado a busca de dados de texto e soluções (se eu mandasse todos dos meus favoritos, seriam uma lista enorme 😅):

De todo modo, é papel de um DBA cuidar isso e arquitetar o banco de dados do software se preocupando com indexação, separação, normalização e processamento de dados. Você nunca participou de uma reunião sobre os usos de caso de busca antes de montar a estrutura de dados? É assim que se tomam decisões com base no cenário do projeto. Ademais, pode utilizar como quiser. Se o seu sistema não exige um tempo de resposta competivo, vai na fé.

2

Tem bastante programador ruim mesmo, eu acho que você deve tentar vender seus serviços para todas essas big techs que estão fazendo um péssimo trabalho com performance, justamente por usarem as soluções não atrasadas da indústria, ninguém está usando o arroz com feijão, estão usando o que está no hype, no que viram em algum lugar e tomaram como verdade.

Eu positivei 3x sua publicação original porque ela tem algum valor (que não deve usar inteiro para o CPF e porque se deu ao trabalho de fazer testes), mas essa parte que conclui algo sem comprovação espero que as pessoas leiam tudo antes de adotar, ou adotem e testem de forma real e publiquem para comprovar e complementar esse estudo que você fez, ou para refutar. Eu não vou fazer porque eu não espero ter ganho algum, eu teria que fazer só para provar que a conclusão tem falhas e não tenho interesse nisso. Para os demais que estão lendo podem pegar as duas opiniões e decidirem o que fazer, porque nenhum dos dois estamos apresentando comprovação que uma aplicação real terá ganho se fizer essa complexidade a mais. Minha regra básica é que a complexidade adicioona tem que se pagar. Mesmo que se pague o texto deveria deixar claro que só em aplicações grandes faz isso e ajudaria mais apresentar o dado concreto.

Eu queria o dado concreto, eu não posso só confiar no que diz. Mas se não pode fornecer vou continuar com o que eu faço, pode ser ruim, mas não consigo enxergar algo melhor, e não tem lentidão alguma, que é a parte mais importante, não consigo imaginar como essa busca do CRC vá trazer resultado significativo em aplicação real, e não só um teste sintético. Novamente, que provavelmente tem algo errado porque não faz sentido uma economia tão pequena ser 15x mais rápida.

10 mil transações diárias por dia é equivalente a uma em cada 9 segundos, ou seja, se você concentrar tudo em 3 horas e o resto ficar parado será uma a cada segundo. Esta é a grande escala que você mexe? Um bancod de dados todo errado sem índices deve dar performance suficiente. Agora eu preciso muito do dado concreto, porque está mostrando que tudo o que fala não faz sentido.

De qualquer forma você não precisa aceitar nada do que eu estou falando, porque não estou te propondo fazer nada, você está propondo que o CRC é melhor, por método científico cabe a você me mostrar que é real além do teste sintético.

Eu conheço profundamente o SO, inclusive conheço pessoalmente várias pessoas que trabalham/(vam) lá, e uso a bsuca quandoera FTS no SQL Server mesmo, e ia muito bem, chega um ponto que precisa separar, ter mais features, mas não era lento. E não vamos comparar a busca de um CPF com textos totais completos, são buscas completamente diferentes. Ciei para dizer que não é o que você está preocupado que faz algo ser lento, a não ser que tenha um problema que a busca do CPF seja a quase única coisa que o sistema faz e faz isso milhões de vezes por minuto, e se for, mais uma vez, é um cenário específico e raro.

Tudo que postou não mostra como o CRC deixa uma aplicação real ser muito rápida no cenário apresentado, algumas cosias são bem básicas, outras falam de particionamento que não vai tornar nada 15 vezes mais rápidas por si só (pode se colocar bem mais de 15x hardware), de qualquer forma parece que não foi o que usou no seu teste e não precisa de CRC para particionar. Inclusive um dos textxos, se ler com atenção, fala indiretamente que não deve usar o CRC no caso que você apresentou. Sua publicação para ser mais confável que as postagens que mandou agora (exceto do SO que desvia a conversa para outra coisa), eu quero saber o CRC que deixa a aplicação real muito mais rápida que vale o esforço de fazer, aí eu posso aceitar ou dizer que tem uma solução melhor ainda se é necessário. Agrdeço ter postado isso, será útil para muita gente que não conhece mais a fundo como funciona um banco de dados (eu até fiz engine de banco e dados).

Curioso eu ser atrasado na indústria mas ignorar um banco de dados na memória, ou um array de SSD que vai produzir algo semelhante, que evitam particionamento, sharding ou soluções mais complexas. De qualquer forma só citei para dizer que existem outras soluções e é um atraso na indústria ignorá-las, eu nunca precisei, mas muita gente tem conseguido ótimos resultados.

Em memória deve estar apenas o que a aplicação irá usar

Acho que aqui você precisa rever seus conceitos. Pode ser, mas não em todos os casos

Eu não gosto de falar sobre soluções sem ver o cao real, eu não faço tudo igual, mas se realmente eu precisasse dessa performance pra buscar CPF eu pensaria nas diversas soluções possíveis, eu testaria se usar um CRC me ajudaria. Shard provavelmente seria uma das últimas tentativas.

Eu não gosto de dar carteirada, mas dese o TCC o que mais fiz foi consertar performance de aplicações, e ultimamente anda fácil, é só mandar tirar as coisas "não atrasadas da indústria" e se atentar aos fundamentos, ao conhecimento real, ao dado concreto com testes em produção eliminando os ruídos que podem dar resultados arté mesmo contrários.

Eu não tenho fé. Eu tenho tempo de resposta competivo me preocupando em problemas reais. Eu gostaria de aprender com fatos o que eu estou perdendo. De qualquer forma, seu texto agora indicou que o uso da expressão "cenários específicos e raros" estava correto (passamos discutir se é útil até mesmo para esses cenários, o que eu acho que existe uma possibilidade de ser, mas ainda tenho dúvidas), para a maioria dos cenários você já está admitindo que não precisa. Pode refutar se quiser, mas acho que sua discordância inicial foi refutada por você mesmo agora.

Vou terminar com seu começo, e só porque você escreveu isso, eu não falaria assim se não fosse o caso, atré porque tem falhas lógicas no parágrafo, vou mudar só o final.

Adoraria que você contribuisse com os testes referentes ao que você está falando, referências ou alguma coisa que pudesse enriquecer a discussão. Porque sinceramente, sem ofenças (sic), parece a opinião de alguém com mais crenças do que comhecimento, que foi o oposto do que parecia na postagem original, tem diversos pontos que demonstra neste texto que isso acontece, por exemplo quando faz conjecturas a meu respeito.

Se eu for fazer conjecturas me passa a impressão que não sabe como um índice funciona além do superficial ou que gosta de soluções complexas quando as simples resolvem o problema. Não estou dizendo que é assim, mas o texto passa essa impressão, eu teria que te conhecer muito para afirmar isso.

Quando fizer isso, eu mudo de opinião. De maneira alguma estou falando que você está errado, só não posso aceitar enquanto parecer uma crença. E em nenhum momento eu te fiz uma ofenSa, inclusive que para não parecer um idiota eu teria queco nhecer você para fazê-lo, só posso falar do que estou lendo aqui e mais nada. Por enquanto ainda fico com a ideia de otimização prematura. Se você não o fizer, quem sabe alguém faça.

4

Guardar esse post na cabeça e no coração pra toda vez que algum dev emocionado vim dizer que "não precisa faculdade" e que "o curso do meu YouTuber favorito basta".

Pensamento científico só se aprende na faculdade, nas materias de metodologias de pesquisa que muitos insistem que poderia ser trocadas pelo curso de react da moda.

Pensamento científico cala a boca de dev junior, senior e de chefe emocionado, que quer tomar a decisao "x" porque sim.

Parabéns meu chapa!

JÁ PENSOU SE TODAS OS POSTS DO TABNEWS FOSSEM NESSE NÍVEL???

"Imagine there's no heaven." — John Lennon

2
1
1

Realmente eu acredito que sim um ensino acadêmico certamente faz a diferença, não acho que o ensino seja perfeito pois as vezes você despediça um semetre para aprender em uma matéria o mesmo que um curso. Porém a programação em si nem é o foco. Tem muita materia que você vê, que a galera de curso não sabe o básico. À exemplo, tem gente que tem formação em React naquela famosinha, mas mal sabe como funciona DNS (algo que se aprende no primeiro período de uma universidade).

Ademais, os grandes avanços da tecnologia aparecem todos em papers a partir de pesquisas, estudos e metodologias. Concordo que universidade é fundamental.

4

Mas com a notícia de CNPJ alfanumérico para 2026, as opções agora serão mais limitadas, então os tipos numericos deixam de ser opções para novos projetos desenvolvidos a partir de agora já prevendo essa mudança.

3

vergilSparda, nesse caso do CNPJ Alfanumérico seria guardado no banco de dados como uma sequência de bytes ou um Base64 (ou ainda, um Base32)? Não pensei no que seria mais eficiente. 2026 chega em menos de 14 meses!!

Encontrei uma discussão referente ao caso do CNPJ Alfanumérico, que inclusive cita uma documentação oficial.
Nota Técnica nº49/2024, COCAD/SUARA/RFB (PDF | 603 KB)

Com relação à discussão no blog, a mesma é bem sucinta. Segundo explicam (achei melhor citar ipsis litris):

A fórmula de cálculo do dígito verificador do CNPJ Alfanumérico não muda: foi mantido o cálculo pelo módulo 11. Porém, para garantir a utilização dos atuais números do CNPJ (tipo numérico), será necessária a alteração do modo como se calcula o dígito verificador pelo módulo 11. Serão utilizados, no cálculo do módulo 11, os valores relativos a letras maiúsculas lastreadas na tabela denominada código ASCII, como solução para unificar a representação de caracteres alfanuméricos.

Na rotina de cálculo do Dígito Verificador (DV)no CNPJ, serão substituídos os valores numéricos e alfanuméricos pelo valor decimal correspondente ao código constante na tabela ASCII e dele subtraído o valor48. Desta forma os caracteres numéricos continuarão com os mesmos montantes, e os caracteres alfanuméricos terão os seguintes valores: A=17, B=18, C=19… e assim sucessivamente. Esta definição permitirá que o atual número do CNPJ tenha o mesmo cálculo do seu dígito verificador quando os sistemas iniciarem a identificação alfanumérica.
Fonte: http://www.pctoledo.com.br/forum/viewtopic.php?f=4&t=27693#p165547

O motivo do salto indicado na explicação anterior (... "A"=17, "B"=18, "C"=19 etc.) é facilmente observado ao inspecionar a posição dos números e letras maiúsculas na tabela ASCII:

Caractere ASCII | Valor em decimal | Correspondente
       0                48                 0
       1                49                 1
     ...               ...               ...
       8                56                 8
       9                57                 9
     ...               ...               ...
       A                65                17
       B                66                18
     ...               ...               ...
       Y                89                41
       Z                90                42

Antevendo a alteração do padrão do CNPJ ajustei a rotina de validação co CNPJ...
Espero poder auxiliar os colegas no tocante ao assunto...
Fonte: http://www.pctoledo.com.br/forum/viewtopic.php?f=43&t=27691#p165545
Código a seguir:

O markdown ainda não tem syntax hightlight compatível com pascal language.

function CNPJ_validar(pCNPJ, plMsg )
// #toya: 24/06/2024 - 07:00:00 - mrb_2024 Release .001.001

Local lResult := .t.
local soma := 0
local dv := ""
local digito := 0
local num := 0
Local wCGC := iif(ValType(pCNPJ)="U", "", pCNPJ)
local i := 0
local j := 0
local Validos := "0123456789"

DEFAULT plMsg := .t.

wCGC := StrTran(wCGC, ".", "")   
wCGC := StrTran(wCGC, "-", "")   
wCGC := StrTran(wCGC, "/", "")
if Empty(wCGC)
    lResult := .t.
else
    if len(wCGC) < 14
        lResult := .f.
    else
      for i = 1 to 12
         if substr(wCGC, i, 1) $ "ABCDEFGHIJKLMNOPQRSTUWYXZ"
            Validos := "0123456789ABCDEFGHIJKLMNOPQRSTUWYXZ"
            exit    
         endif
      next
        dv := ""
        num := 5
        for j = 1 to 2
            soma := 0
            for i = 1 to 12
                soma += (asc(substr(wCGC, i, 1)) - 48) * num
                num--
                if num == 1
                    num := 9
                endif
            next
            if j == 2
                soma+=( 2 * val(dv))
            endif
            digito = soma - (int(soma / 11) * 11)
            if digito == 0 .or. digito == 1
                dv := dv + "0"
            else
                dv := dv + str(11 - digito, 1)
            endif
            num := 6
        next
        if dv <> substr(wCGC, 13, 2)
            lResult := .f.
        endif
    endif
    if !lResult
       if plMsg
           Msg_OK("CNPJ Incorreto ou Digito Invalido..." + " [" + dv + "]" )
       endif
   endif
endif

return lResult

function Msg_Ok(cMsg, cTitle)

Return(MessageBox(GetActiveWindow(), cMsg, cTitle, nOR(16,0)))

# fonte: Reforma Tributária
         - Nota Técnica nº49/2024, COCAD/SUARA/RFB
         - Novo formato de CNPJ em alfanumérico

Outra fonte propõe um código bem mais simples

* nPos  [Int]
* sCNPJ [String, UpperCase]
* cPos  [Char]
* aDigito [Array[12], Int]

   For nPos := 1 To 12
      cPos := Subs( sCNPJ, nPos, 1 )
      aDigito[ nPos ] := Asc( cPos ) - 48
   Next
* Fonte: http://www.pctoledo.com.br/forum/viewtopic.php?f=4&t=27693#p165547
3

Complicação desnecessária. A validação de CNPJ fica por conta da aplicação. Fazer uma interpretação de dados na hora de ler e escrever é um desperdício a nível de banco, pois você teria que fazer uma interpretação atômica. Nesse caso, mesma solução se aplica, armazena-se em CHAR e utiliza um CRC para otimizar o cálculo de busca.

2

Verdade! A ideia que perguntei empregando Base64/32 parece-se com aquela invenção do Ra-Tim-Bum para apagar uma vela 8-). Valeu pela crítica, caiquearaujo e pela proposta mais eficiente. O campo com CHAR é nativo e um dos diferentes CRCs funcionaria como se fosse um hash de comprimento relativamente curto.

1

Acredito que números, exceto antigamente pela questão de armazenamento que tanto justificava isso, nem deveriam ser considerados nesse caso. A natureza de números de identificação de pessoas não é numérica. Em breve, até CPFs deverão passar pela mudança.

2

Ótimo post, muito bem explicado e esclarecedor. Sempre tive essa dúvida sobre a melhor forma para salvar esse tipo de dado em DBs, apesar que minha preferência sempre foi salvar como texto mesmo.

2

Preciso contribuir com o Tabnews adicionando um recurso de "Favoritos", porque esse é aquele post que vale a pena salvar para consultas futuras!

Fica aqui os meus parabéns e meus agradecimentos por essa contribuição @caiquearaujo, tu é fera!

2
2

Parabéns pelo artigo. me arrisco a dizer que é um dos melhores que já li por aqui.
Este sim é um conteúdo que relevante

Sobre esse comparativo, chegou a validar em outros bancos? SQLServer por exemplo teria essa opção de CRC32?

3

O MySQL/MariaDB possuem nativamente a função CRC32, logo conseguem calcular em tempo real para inserir, atualizar ou selecionar. Demais bancos não possuem essa função, embora a maioria deles possuam a geração de colunas calculadas em tempo real.

De todo modo é necessário entender que CRC32 (hexadecimal) pode ser convertido para um número inteiro e armazenado dessa forma sem perder as características dos dados. Logo, significa que você pode fazer isso do lado da aplicação (e na maioria das vezes é melhor que seja feito). No caso dos testes, a coluna calculada em tempo real é um facilitador.

Em todos os bancos o índice de uma coluna numérica será expressivamente menor e mais eficiente do que uma coluna alfa-numérica. Então o desempenho seria parecido, no caso do PostgreSQL ainda mais eficiente.

Então basicamente na hora de fazer uma inserção seria INSERT INTO cpfs_data (cpf, crc) VALUES (%s, %s) e na aplicação você executaria crc32(cpf), já adicionando calculado no banco de dados. Na hora de ler, o mesmo processo SELECT * FROM cpfs_data WHERE crc = %s AND cpf %s, executando novamente na aplicação crc32(cpf).

Todas as linguagens tem implementações. Exemplo abaixo com NodeJS:

const mysql = require('mysql2/promise');
const crc = require('crc');

const connectionConfig = {
  host: 'localhost',
  user: 'user',
  password: 'pass',
  database: 'database',
};

async function insertData(cpf) {
  const crcValue = crc.crc32(cpf); // Calcula o CRC32 como um inteiro
  const connection = await mysql.createConnection(connectionConfig);

  try {
    const query = `INSERT INTO cpfs_data (cpf, crc) VALUES (?, ?)`;
    await connection.execute(query, [cpf, crcValue]);
    console.log('CPF inserido com sucesso!');
  } catch (error) {
    console.error('Erro ao inserir CPF:', error);
  } finally {
    await connection.end();
  }
}

async function getData(cpf) {
  const crcValue = crc.crc32(cpf); // Calcula o CRC32 como um inteiro para a busca
  const connection = await mysql.createConnection(connectionConfig);

  try {
    const query = `SELECT * FROM cpfs_data WHERE crc = ? AND cpf = ?`;
    const [rows] = await connection.execute(query, [crcValue, cpf]);
    if (rows.length > 0) {
      console.log('CPF encontrado:', rows[0]);
    } else {
      console.log('CPF não encontrado.');
    }
  } catch (error) {
    console.error('Erro ao buscar CPF:', error);
  } finally {
    await connection.end();
  }
}

O código acima poderia funcionar da mesma forma em qualquer banco pois tudo que você precisa na verdade é:

  • Uma coluna inteira indexada que irá armazenar o CRC32 calculado, encurtando o processo de busca substâncialmente;
  • Uma coluna para armazenar a string do CPF.

Digo mais, você poderia armazenar o CPF até formatado (teria que buscar por ele formatado também) que não faria diferença. O CRC32 vai sintetizar o primeiro dado independente do que ele seja e diminuirá bastante o número de dados que serão lidos pelo banco.

Outro exemplo é, você poderia ter uma coluna public_id com um uuid e usar a mesma técnica. Veja que um uuid tem 36 caracteres. Mas ao adicionar o CRC32 você poderia buscar tranquilamente por qualquer public_id no banco.

1

Ler isso me lembrou quando eu fiz pix pra outra conta minha chave celular, vi os 3 últimos números antes dos digitos verificadores e fiquei curioso sobre a possibilidade de descobrir os outros números. Gastei 1 dia entendendo como o cpf funciona, criando um script py pra gerar 1.000 cpfs "validados" pelo digito verificador, escrevendo o script pra usar selenium, acessar um desses sites que permite digitar o cpf e ver a situação cadastral além do nome do dono, raspar os dados e saber se o meu cpf poderia ser descoberto dessa forma.

Sei que seu caso é mais "academico", mas senti a msm vibe.

ps: o script funcionou e consegui pegar meu cpf nele. 1 dia jogado no lixo, mas enfim kkkkk.

1

Estou voltando a estudar, entrei em Ciencia da Computação e voltei pro tab pra poder entrar de volta neste ambiente.

Fico muito feliz por ver esse tipo de post por aqui principalmente pq eu acredito que o pensamento científicio é extremamente necessário, a busca por conhecimento e o conhecimento sobre as métricas de cada modelo, de cada técnica. Acho fascinante demais.

Espero me tornar pelo menos tão bom quanto o pessoal que está por aqui.
Esse tipo de coisa "Pushes me forward"

1

Bom, dificilmente algum sistema terá o campo CNPJ como algum tipo de chave relacional, sendo assim, cria um novo campo char, por exemplo, e copia-se os dados do int para esse novo char ou algo do gênero, e o resto já é óbvio.

1

Parabéns pelo post, mas uma dúvida.
Quando eu trabalhei no passado com desenvolvimento, tive problemas em salvar CPF quando o mesmo começa com zero. Como que fica essa situação no banco de dados? Na época eu fiz salvar como texto, e ainda salvava ele formatado, pois a maios parte dos casos nós precisávamos dele formatado, então era uma facilidade boa.

1

Se você teve problemas para salvar o CPF apenas com zero, provavelmente você não está salvando ele como uma string do CHAR ou VARCHAR. Afinal, essas colunas terão a codificação de texto escolhida para o banco/tabela (charset).

O que remove zeros a esquerda é o processo de conversão para inteiro. Não sei como o seu algoritmo foi construído, tenho pouco a opinar. Porém, acredito que essa conversão devia acontecer em algum momento.

Não existe nenhum problema em salvar o CPF formato, inclusive. E a estratégia com CRC32 auxilia ainda mais nesse processo melhorando a performance. Vamos supor que você tenha a string com 015.599.874-80. O CRC32 dela é um decimal 1947733562.

Quando você for inserir, você insere formatado (015.599.874-80) com o CRC32 calculado. E quando você buscar, você irá localizar o CRC32 primeiro (1947733562) e depois o CPF. O ponto é que o CRC32 sempre será o mesmo para a mesma entrada, então não importa a entrada. Funciona como um hash.

Já o armazenamento, é importante escolher o tipo de coluna correto para não enfretar os problemas de salvamento que mencionou.

0
0

ParabénsCaique! Excelente post com uma metodologia muito bem explicada!

Sempre deixei mesmo como INT e nunca tive problemas, embora sempre em aplicações de pequeno porte. Em larga scala valeria a pena rever.

Outra discussão é se devemos misturar CPF e CNPJ no mesmo campo e mais um campo PF/PJ numa tabela de cadastro de clientes, por exemplo. Como tem feito? Por questão de praticidade fazemos essa combinação, embora possa vir a ferir alguma regra de normalização.

3

Então, essas são outras discussões que não couberam no post original por uma limitação de caracteres, mas vamos lá 😅

Sempre que você vai armazenar um dado no banco de dados, você precisa entender a finalidade daquela dado na hora de projetar o MER (modelo de entidade-relacionamento).

Um dado como CPF pode ter diferentes aplicações em diferentes sistemas. Em um sistema ele pode ser usado para consulta, em outro não, etc. Mas acima de tudo a representação deste dado tem como característica a identificação única de um indivíduo.

Dentro do contexto de dados, a identificação única de um registro mais eficiente já o seu próprio ID (auto incrementado, por exemplo). O CPF, por exemplo, é a identificação do indivíduo e, em algum momento, assim como aconteceu com placas de carro, aconteceu com CNPJ, vai acontecer com o CPF: letras serão incluídas.

Quando você analisa a estrutura do dado, existem apenas 8 dígitos produtivos. Isso porque os três últimos dígitos fazem referência ao número do estado (em uma tabela de correspondência) e aos dígitos verificadores. Isso dá uma razão de 99 milhões de opções por grupo de estado. Parece muito, porém a Receita Federal ainda aplica um conjunto de regras para limitar essa combinação de números de modo a não gerar número em sequência de repetição alta, por exemplo.

Logo a natureza do dado em si nunca foi numérica, pois todos os dígitos formam esse dado. Os zeros à esquerda eliminados por uma coluna inteira, por exemplo, descaracterizam a entidade. Se eu precisar processar esses valores do lado do banco de dados, pode ter certeza que será uma dor de cabeça enorme. Mesmo para sistemas pequenos, não recomendaria a prática, ainda mais pelos poucos benefícios que você ganha com ela e, ainda, na camada da aplicação tem que: (1) converter para string; (2) preencher zeros a esquerda; (3) aplicar a formatação.

Sobre a outra discussão: devemos misturar dados? É outra coisa que depende do seu MER, quando você olha para o contexto e significado do CNPJ e CPF, estamos falando de pessoas diferentes. Uma é física, um indivíduo com nome, sobrenome, RG, etc. E a outra é jurídica, com razão social, nome fantasia, inscrição estadual, inscrição municipal, etc. E essa última ainda é composta de um quadro societário de pessoas físicas.

Em alguns sistemas, principalmente fiscais, essas separações lógicas são aplicadas no banco também, criando uma tabela para cada tipo de pessoa. Mas fora isso, existem muitas alternativas:

  • Podemos criar uma tabela usuários e uma outra tabela de metadados desse usuário;
  • Podemos criar uma única tabela com uma separação lógica por coluna.

Sobre o segundo caso que você mencionou sobre praticidade, por exemplo, é simples. Você irá precisar de três colunas para garantir uma consulta mais eficiente:

  • doc_type: Um coluna do tipo ENUM com cpf, cnpj e indexada. O custo dessa coluna tanto em tempos de processamento quanto armazenamento é insignificante, porque ela é armazenada como inteiro, mas com o benefício da fácil visualização e busca de dados por string;
  • doc_number: Uma coluna do tipo VARCHAR com 14 caracteres para armazenar o valor do documento sem formatação;
  • doc_crc: Uma coluna do tipo BIGINT para armazenar os CRC32 do número do documento e indexada.

Quando você executar um WHERE, o banco irá filtrar primeiro por doc_type (menor índice), depois pelo doc_crc e por fim o doc_number. Não terá qualquer problema de ineficiência.

Agora, concordo que a estrutura acima só funciona para sistemas muito simples ou, geralmente, quando dados relacionados não são usados (nome completo, razão social, RG, inscrições federais, etc). Por exemplo, você tem um sistema de cadastro de usuário e você apenas quer saber se ele é pessoa física ou jurídica, junto com sua identificação. Nesse caso, a flag doc_type se faz mais interessante. Contudo se você precisa, por exemplo, coletar dados fiscais dessas pessoas, geralmente você vai precisar separar, não só para organizar melhor os dados, mas também relacionar uma pessoa física a uma jurídica quando aplicável.

No fim se você trabalha com banco de dados, tem que entender de banco de dados. Entender sobre a construção do MER e, principalmente, desvendar as características e relações dos dados de modo a resolver um problema mais eficiente. Nesse caso, um número de documento jamais deveria ser interpretado como inteiro, mas muitos fazem e por suas razões.

Contudo, assim que CPF ganhar dígitos alfanuméricos, esteja prepara para atualizar todas as suas aplicações de pequeno porte.

1

Verdade caiquearaujo, é bem provável que o número de Cadastro de Pessoa Física (CPF) seja o próximo a passar por uma alteração (ainda não encontrei nada a respeito), pois a estrutura desse número não é contínua como se pensa. Atualmente já se encontra incorporado pela Carteira de Identidade Nacional (CIN) sendo o identificador único do cidadão. Dos 9 dígitos que o compõe, apenas 8 são realmente livres dentro de cada região fiscal da Receita Federal, identificada no 9º dígito (região fiscal responsável pela primeira emissão do documento). Os dois últimos restantes são dígitos verificadores.

NNN.NNN.NNX-YZ : CPF
sendo:
NNN.NNN.NN     : representam o número da inscrição oficial da pessoa
          X    : região fiscal da Receita Federal responsável pela criação deste CPF
            YZ : verifica os nove primeiros dígitos

O nono dígito corresponde a um ou mais estados do país, a saber:

1: DF, GO, MS, MT e TO
2: AC, AM, AP, PA, RO e RR
3: CE, MA e PI
4: AL, PB, PE e RN
5: BA e SE
6: MG
7: ES e RJ
8: SP
9: PR e SC
0: RS

Fonte: https://www.serasa.com.br/blog/o-que-e-cpf

Segundo o IBGE, a população residente no Brasil é de 203.080.756 indivíduos. Do exposto, supõe-se que cada região fiscal pode emitir cerca de 99.999.999 números de CPF. Caso o 9º dígito fosse uma letra [A-Z] representando cada uma delas um estado da federação, teoricamente seriam possíveis 2.600.000.000 de CPFs com 100.000.000 por estado (DF precisa ser incluso em algum outro estado). O 9º dígito também poderia ser um elemento de conferência, caso o local de emissão fosse conhecido! Supondo-se que todos habitantes possuem inscrição no CPF, a síntese da população por UF poderia ser representativa de todos os CPFs ativos.

      Estado       |  Habitantes | Região Fiscal
Goiás              :   7.350.483   1
Mato Grosso        :   3.836.399   1
Distrito Federal   :   2.982.818   1
Mato Grosso do Sul :   2.901.895   1
Tocantins          :   1.577.342   1

Pará               :   8.664.306   2
Amazonas           :   4.281.209   2
Rondônia           :   1.746.227   2
Acre               :     880.631   2
Amapá              :     802.837   2
Roraima            :     716.793   2

Ceará              :   9.233.656   3
Maranhão           :   7.010.960   3
Piauí              :   3.375.646   3

Pernambuco         :   9.539.029   4
Paraíba            :   4.145.040   4
Rio Grande do Norte:   3.446.071   4
Alagoas            :   3.220.104   4

Bahia              :  14.850.513   5
Sergipe            :   2.291.077   5

Minas Gerais       :  21.322.691   6

Rio de Janeiro     :  17.219.679   7
Espírito Santo     :   4.102.129   7

São Paulo          :  45.973.194   8

Paraná             :  11.824.665   9
Santa Catarina     :   8.058.441   9

Rio Grande do Sul  :  11.229.915   0

Sei que não faz muito sentido apresentar esta tabela acima, pois para avaliar quantos possíveis CPF estão consumidos, é necessário levar em conta certas curvas de crescimento populacional e mortalidade.

Mas fica a dúvida. O Cadastro de Pessoas Físicas foi efetivamente instituído em 1968 por força do Decreto-lei nº 401 de 30 de dezembro de 1968. De lá para cá, vários cidadãos cadastrados faleceram. Neste caso, cedem seu número CPF para outro cadastro após determinado tempo legal ou manterá o número para sempre, mesmo falecido?

Brasil tem 12,5 milhões de CPFs ativos a mais que a população total, e TCU cobra Receita Federal: Auditoria diz que, em 3,3 milhões de cadastros, há indícios de que a pessoa já morreu. Há ainda 78 mil CPFs ativos de pessoas que, se vivas, têm mais de 110 anos; Receita não comentou dados.
Fonte: G1 em matéria publicada em 02/09/2020 19h07, atualizada em 02/09/2020

0