O que é mais eficiente: várias tabelas MySQL ou uma grande tabela?

103

Eu armazeno vários detalhes do usuário em meu banco de dados MySQL. Originalmente, ele foi configurado em várias tabelas, o que significa que os dados são vinculados a UserIds e emitidos por meio de chamadas às vezes complicadas para exibir e manipular os dados conforme necessário. Configurando um novo sistema, quase faz sentido combinar todas essas tabelas em uma grande tabela de conteúdo relacionado.

  • Isso vai ser uma ajuda ou um obstáculo?
  • Considerações de velocidade na chamada, atualização ou pesquisa / manipulação?

Aqui está um exemplo de algumas das minhas estruturas de tabela:

  • usuários - UserId, nome de usuário, e-mail, senha criptografada, data de registro, ip
  • user_details - dados de cookies, nome, endereço, detalhes de contato, afiliação, dados demográficos
  • user_activity - contribuições, última online, última visualização
  • user_settings - configurações de exibição de perfil
  • user_interests - variáveis ​​segmentáveis ​​de publicidade
  • user_levels - direitos de acesso
  • user_stats - hits, contagens

Edit: Eu votei todas as respostas até agora, todas elas têm elementos que essencialmente respondem à minha pergunta.

A maioria das tabelas tem uma relação de 1: 1 que foi o principal motivo para desnormalizá-las.

Haverá problemas se a tabela abranger mais de 100 colunas quando uma grande parte dessas células provavelmente permanecerá vazia?

Peter Craig
fonte
Esta outra pergunta também pode ser útil
Mosty Mostacho,

Respostas:

65

Múltiplas tabelas ajudam nas seguintes formas / casos:

(a) se pessoas diferentes irão desenvolver aplicativos envolvendo tabelas diferentes, faz sentido dividi-las.

(b) Se você quiser dar diferentes tipos de autoridades a diferentes pessoas para diferentes partes da coleta de dados, pode ser mais conveniente dividi-las. (Obviamente, você pode examinar a definição de visualizações e dar autorização apropriada para elas).

(c) Para mover dados para locais diferentes, especialmente durante o desenvolvimento, pode fazer sentido usar tabelas que resultem em tamanhos de arquivo menores.

(d) Uma pegada menor pode proporcionar conforto enquanto você desenvolve aplicativos na coleta de dados específicos de uma única entidade.

(e) É uma possibilidade: o que você pensava como um dado de valor único pode acabar sendo, no futuro, realmente valores múltiplos. por exemplo, o limite de crédito é um campo de valor único a partir de agora. Mas amanhã, você pode decidir alterar os valores como (data de, data até, valor de crédito). As tabelas divididas podem ser úteis agora.

Meu voto seria para várias tabelas - com os dados devidamente divididos.

Boa sorte.

user115905
fonte
3
@RohitKhatri: Pelo que sei, ter várias tabelas aumentará o desempenho na maioria dos casos.
Hari Harker de
1
@HariHarker Obrigado pela sua resposta, mas descobri que depende do seu padrão de acesso.
Rohit Khatri de
Até recentemente, eu sempre armazenava todos os dados em uma tabela, mas pensando bem, tem muitas vantagens dividir os dados em termos de desempenho (dependendo do caso de uso, é claro), semântica (alguns dados são melhor agrupados em um tabela diferente) e desenvolvimento. Por exemplo, estou desenvolvendo um sistema ERP customizado agora em cima de um sistema legado. Tive que expandir as tabelas do banco de dados antigo com colunas extras. Decidi fazer novas tabelas para os novos dados. Alguns novos recursos são úteis para o sistema legado e agora posso integrá-los facilmente sem ter que reescrever muitas das consultas antigas
Ogier Schelvis
35

A combinação das tabelas é chamada de desnormalização.

Pode (ou não) ajudar a fazer algumas consultas (que JOINgeram muitas s) para serem executadas mais rapidamente ao custo de criar um inferno de manutenção.

MySQLé capaz de usar apenas um JOINmétodo, a saber NESTED LOOPS.

Isso significa que, para cada registro na tabela de MySQLcontrole , localiza um registro correspondente na tabela de controle em um loop.

Localizar um registro é uma operação bastante cara, que pode demorar dezenas de vezes mais do que a digitalização de um registro puro.

Mover todos os seus registros para uma tabela ajudará você a se livrar dessa operação, mas a própria tabela fica maior e a varredura da tabela leva mais tempo.

Se você tiver muitos registros em outras tabelas, o aumento na varredura da tabela pode sobrecarregar os benefícios dos registros que estão sendo varridos sequencialmente.

O inferno de manutenção, por outro lado, é garantido.

Quassnoi
fonte
1
Se você tiver 10.000 usuários e estiver fazendo uma junção com um banco de dados configurado com chaves estrangeiras corretamente, você só deve precisar de uma pesquisa intensa, fazendo algo como select * from users where name = "bob". Depois de ter bob, você está usando um índice para encontrar as tabelas unidas a bob, que é significativamente mais rápido porque você está usando o id de bob. Isso acontece independentemente de você estar fazendo uma junção em sua consulta ou consultando bob e, em seguida, consultando uma tabela separadamente. Claro, esperançosamente, sua segunda consulta é baseada no id de bob e não em outra coisa.
Rudy Garcia
17

Todos eles são relacionamentos 1: 1? Quer dizer, se um usuário pudesse pertencer a, digamos, níveis de usuário diferentes, ou se os interesses do usuário fossem representados como vários registros na tabela de interesses do usuário, então mesclar essas tabelas estaria fora de questão imediatamente.

Com relação às respostas anteriores sobre normalização, deve-se dizer que as regras de normalização do banco de dados desconsideraram completamente o desempenho e estão apenas olhando para o que é um design de banco de dados legal. Freqüentemente, é isso que você deseja alcançar, mas há momentos em que faz sentido desnormalizar ativamente em busca de desempenho.

Resumindo, eu diria que a questão se resume a quantos campos existem nas tabelas e com que frequência eles são acessados. Se a atividade do usuário geralmente não é muito interessante, pode ser um incômodo tê-la sempre no mesmo registro, por motivos de desempenho e manutenção. Se alguns dados, como configurações, digamos, são acessados ​​com frequência, mas simplesmente contêm muitos campos, também pode não ser conveniente mesclar as tabelas. Se você está interessado apenas no ganho de desempenho, pode considerar outras abordagens, como manter as configurações separadas, mas salvá-las em uma variável de sessão própria para que não seja necessário consultá-las no banco de dados com frequência.

David Hedlund
fonte
Tenho que discordar totalmente de seu comentário de que a normalização se concentra apenas na limpeza e desconsidera completamente o desempenho. Há uma troca em ambos os cenários e a desnormalização realmente coloca em risco a integridade dos dados. Eu diria que a normalização de seu banco de dados realmente melhora o desempenho geral do banco de dados, em vez de ter um aumento de desempenho insignificante rápido de uma tabela desnormalizada.
Rudy Garcia
Dado que a discussão é especificamente sobre relacionamentos 1: 1, dividir as tabelas não é uma tarefa de normalização , certo? Se não houver informações duplicadas, é normal mesmo quando é uma única tabela. (Bem, pode não satisfazer a 3NFnormalização, então se beneficie de uma segunda tabela para resolver isso, mas não parece ser a isso que OP está se referindo com relação às outras tabelas.)
ToolmakerSteve
14

Fazer tudo dessas tabelas ter um 1-to-1relacionamento? Por exemplo, cada linha do usuário terá apenas uma linha correspondente em user_statsou user_levels? Nesse caso, pode fazer sentido combiná-los em uma mesa. Se o relacionamento não for 1 to 1 , provavelmente não faria sentido combiná-los (desnormalizá-los).

Tê-los em tabelas separadas em comparação com uma tabela provavelmente terá pouco efeito no desempenho, a menos que você tenha centenas de milhares ou milhões de registros de usuário. O único ganho real que você obterá é simplificar suas consultas combinando-as.

ETA:

Se sua preocupação é sobre ter muitas colunas , então pense nas coisas que você normalmente usa juntas e combine-as , deixando o resto em uma tabela separada (ou várias tabelas separadas se necessário).

Se você observar a maneira como usa os dados, meu palpite é que você descobrirá que algo como 80% de suas consultas usam 20% desses dados, com os 80% restantes sendo usados ​​apenas ocasionalmente. Combine os 20% usados ​​com frequência em uma tabela e deixe os 80% que você não usa com frequência em tabelas separadas e você provavelmente terá um bom compromisso.

Eric Petroelje
fonte
Sim, cada tabela tem apenas 1 linha para cada usuário, simplesmente para evitar a dor de cabeça de gerenciar muitos dados duplicados. É por isso que estou pensando que uma mesa serve. Se os dados do usuário abrangessem várias linhas, eu esperaria ter essas tabelas separadas da tabela do usuário principal.
Peter Craig
1
Se cada tabela tiver uma relação de 1 para 1, então uma tabela seria mais fácil de usar. Não há necessidade de dividir a tabela nesse caso. Dividir a tabela sugere que há mais de 1 linha, o que pode levar a um caso em que outro desenvolvedor os trataria dessa forma.
Richard L
Pensamento muito interessante aplicando 80/20 ao design de tabelas de banco de dados. Me fez pensar também no design de classe OOP (eu sou principalmente um desenvolvedor Java) e me perguntando se o mesmo poderia ser eficaz lá (coloque 80% da funcionalidade do aplicativo principal em uma classe e o resto em outras classes).
Zack Macomber
1
@ZackMacomber - Não, a divisão de classes deve ser baseada na localidade de referência . A vantagem de dividir em várias classes é traçar uma borda em torno de uma unidade menor de funcionalidade, de modo que seja mais fácil compreender / testar / alterar e deixar claro onde essa unidade interage com outras unidades de funcionalidade. O objetivo é manter a maioria das conexões (referências, chamadas) dentro de uma unidade, com poucas conexões entre as unidades . Definir várias interfaces que a classe implementa, com interface diferente por caso de uso, pode ser um primeiro passo útil para essa divisão.
Toolmaker Steve
@ToolmakerSteve Bons pensamentos +1
Zack Macomber
9

A criação de uma tabela massiva vai contra os principais do banco de dados relacional. Eu não combinaria todos eles em uma mesa. Você obterá várias instâncias de dados repetidos. Se o seu usuário tem três interesses, por exemplo, você terá 3 linhas, com os mesmos dados do usuário apenas para armazenar os três interesses diferentes. Definitivamente, opte pela abordagem de múltiplas tabelas "normalizadas". Veja esta página do Wiki para normalização do banco de dados.

Edit: Eu atualizei minha resposta, assim como você atualizou sua pergunta ... Concordo com minha resposta inicial ainda mais desde ...

uma grande parte dessas células provavelmente permanecerá vazia

Se por exemplo, um usuário não tinha nenhum interesse, se você normalizar, então você simplesmente não terá uma linha na tabela de interesses para esse usuário. Se você tiver tudo em uma tabela massiva, terá colunas (e aparentemente muitas delas) que contêm apenas NULLs.

Eu trabalhei para uma empresa de telefonia onde havia toneladas de tabelas, a obtenção de dados pode exigir muitas associações. Quando o desempenho da leitura dessas tabelas era crítico, foram criados procedimentos que poderiam gerar uma tabela plana (ou seja, uma tabela desnormalizada) que não exigiria junções, cálculos etc. para os quais os relatórios pudessem apontar. Estes foram usados ​​em conjunto com um agente de servidor SQL para executar o trabalho em determinados intervalos (ou seja, uma exibição semanal de algumas estatísticas seria executada uma vez por semana e assim por diante).


fonte
Eu gosto dessa abordagem, porque os dados desnormalizados existem apenas temporariamente, como um instantâneo de um momento no tempo. Sem problemas de inserção / modificação / exclusão - apenas jogue fora quando terminar.
Toolmaker Steve de
7

Por que não usar a mesma abordagem que o Wordpress faz, tendo uma tabela de usuários com informações básicas do usuário que todos possuem e, em seguida, adicionando uma tabela "user_meta" que pode ser basicamente qualquer par de chave e valor associado ao id do usuário. Portanto, se você precisar encontrar todas as meta informações do usuário, basta adicioná-las à sua consulta. Você também não teria sempre que adicionar a consulta extra se não fosse necessário para coisas como fazer login. O benefício dessa abordagem também deixa sua mesa aberta para adicionar novos recursos aos seus usuários, como armazenar o identificador do Twitter ou cada interesse individual. Você também não terá que lidar com um labirinto de IDs associados porque você tem uma tabela que rege todos os metadados e irá limitá-la a apenas uma associação em vez de 50.

O Wordpress faz isso especificamente para permitir que recursos sejam adicionados por meio de plug-ins, portanto, permitindo que seu projeto seja mais escalonável e não exigirá uma revisão completa do banco de dados se você precisar adicionar um novo recurso.

Rudy Garcia
fonte
A wp_usermetamesa do Wordpress cresce geometricamente. Cada usuário adiciona X linhas à wp_usermetatabela, uma linha para cada informação meta que queremos manter para aquele usuário. Se você mantiver 8 campos personalizados para cada usuário, significa que wp_usermeta terá users * 8linhas longas. Isso parece estar causando problemas de desempenho, mas não tenho certeza se esse é o problema ou não ...
terceiro,
1
Eu pude ver como isso pode causar problemas de desempenho se você tiver dezenas de milhares de usuários. Basicamente, o banco de dados teria que pesquisar 10.000 * 8 entradas na meta-tabela do usuário para encontrar aquelas que você procura. No entanto, se você consultar os metadados apenas quando necessário, acho que seu desempenho seria melhor. Se você está sempre pedindo metadados, mesmo quando não precisa deles, você pode ter problemas. Se você sempre precisa dos metadados, talvez dividir as tabelas não seja a melhor abordagem.
Rudy Garcia
1
Ainda ontem lidamos com um tema WP que carregava todos os usuários (usando get_users()) apenas para calcular a paginação. Depois que corrigimos o código para usar uma SELECT COUNT(…)consulta para a paginação, o tempo de carregamento da página foi de 28 segundos para cerca de 400 ms. Ainda me pergunto como o desempenho se compara a tabelas unidas ou uma única mesa plana ... Tive problemas para encontrar qualquer métrica de desempenho na web.
terceiro, de
Pensando em meu comentário anterior, parece que dividir a tabela ainda é eficiente, a menos que por algum motivo, como o exemplo de paginação acima, você precise selecionar todos os usuários. Porém, se você estiver recuperando todas as metainformações, ainda terá 80k entradas na tabela usermeta. Isso é muito para pesquisar. Talvez alguém possa testar qual é a melhor abordagem executando um script em ambas as implementações e 100 vezes para obter a média, talvez eu faça isso.
Rudy Garcia
1
Eu li tudo de novo hoje e percebi que meu comentário sobre 10.000 * 8 entradas é verdadeiro, no entanto, a maneira como um banco de dados funciona deve torná-lo um problema. Se por algum motivo você estivesse capturando todos os 10.000 usuários E também suas meta-informações, isso seria ridículo. Não consigo pensar em nenhum cenário em que você queira isso. Um banco de dados recuperará facilmente o meta para um único usuário com a velocidade da luz, embora por causa de chaves estrangeiras e indexação. Supondo que seu modelo de banco de dados esteja configurado corretamente.
Rudy Garcia
5

Eu acho que esta é uma daquelas situações "depende". Ter várias tabelas é mais limpo e provavelmente teoricamente melhor. Mas quando você precisa juntar 6 a 7 tabelas para obter informações sobre um único usuário, você pode começar a repensar essa abordagem.

Tundey
fonte
1

Eu diria que depende do que as outras tabelas realmente significam. Um user_details contém mais de 1 mais / usuários e assim por diante. O nível de normalização mais adequado para suas necessidades depende de suas demandas.

Se você tiver uma tabela com um bom índice, provavelmente será mais rápido. Mas, por outro lado, provavelmente é mais difícil de manter.

Para mim, parece que você pode pular User_Details, pois provavelmente é uma relação de 1 para 1 com os usuários. Mas o resto são provavelmente muitas linhas por usuário?

Richard L
fonte