Por que o modelo relacional de um banco de dados é importante?

61

Estou me aproximando de um projeto no qual terei que implementar um banco de dados com meu chefe; somos um começo muito pequeno, então o ambiente de trabalho é profundamente pessoal.

Ele havia me dado uma das bases de dados da empresa antes e foi completamente contra o que eu fui ensinado (e li sobre) na escola para RDBMS. Por exemplo, existem bancos de dados inteiros aqui que consistem em uma tabela (por banco de dados independente). Uma dessas tabelas tem mais de 20 colunas e, para o contexto, aqui estão alguns dos nomes das colunas da tabela one :

lngStoreID | vrStoreName | lngCompanyID | vrCompanyName | lngProductID | vrProductName

O ponto é que onde ele deve ter tabelas individuais que contêm os dados da entidade (nome, tamanho, data de compra, etc.), ele empurra tudo em uma grande tabela por banco de dados.

Eu quero melhorar esse design, mas não tenho certeza de por que um modelo de dados segmentado e corretamente normalizado realmente melhoraria esse produto. Embora eu esteja familiarizado com o design de banco de dados da faculdade e eu entendo como para fazê-lo, eu não tenho certeza porque isso realmente melhora os bancos de dados.

Por que um bom esquema relacional melhora um banco de dados?

    
por 8protons 26.04.2016 / 17:02
fonte

7 respostas

71

O argumento de desempenho é geralmente aquele que é mais intuitivo. Você quer destacar especialmente como será difícil adicionar bons índices em um banco de dados normalizado incorretamente (note: há casos de borda onde a desnormalização pode de fato melhorar o desempenho, mas quando ambos são inexperientes bancos de dados relacionais, você provavelmente não verá facilmente esses casos).

Outro é o argumento do tamanho de armazenamento. Uma tabela desnormalizada com muitas redundâncias exigirá muito mais armazenamento. Isso também afeta o aspecto de desempenho: quanto mais dados você tiver, mais lentas serão as suas consultas.

Há também um argumento que é um pouco mais difícil de entender, mas é de fato mais importante porque você não pode resolvê-lo jogando mais hardware nele. Esse é o problema de consistência de dados. Um banco de dados corretamente normalizado cuidará sozinho de que um produto com um ID específico sempre tenha o mesmo nome. Mas, em um banco de dados desnormalizado, essas inconsistências são possíveis, por isso um cuidado especial precisa ser tomado quando se trata de evitar inconsistências, o que demandará tempo de programação para acertar e ainda causar bugs que lhe custarão a satisfação do cliente.

    
por 26.04.2016 / 17:10
fonte
24

I'll be having to implement a database with my boss ...

Usar o software de gerenciamento de banco de dados dedicado pode ser consideravelmente mais fácil (desculpe, não resisti).

lngStoreID | vrStoreName | lngCompanyID | vrCompanyName | lngProductID | vrProductName

Se este banco de dados apenas se preocupa com "registrar" qual produto foi vendido onde, quando e por quem, então você pode ser capaz de estender a Definição de "banco de dados OK" o suficiente para cobri-lo. Se esses dados estão sendo usados para qualquer outra coisa , é muito ruim.

Mas ...

O aplicativo / consultas que usam esses dados respondem mal / lentamente? Se não, então não há problema real para resolver. Claro, parece feio, mas se funciona , você não obterá nenhum "ponto" por sugerir que "poderia" ser melhor.

Se você puder encontrar sintomas definidos (ou seja, problemas) que parecem ter sido causados por uma modelagem de dados inadequada, crie uma solução melhor. Faça uma cópia de um desses "bancos de dados", normalize os dados e veja se sua solução funciona melhor. Se for consideravelmente melhor (e eu esperaria que quaisquer operações de atualização sobre esses dados fossem maciçamente melhoradas), então volte para o seu chefe e mostre-lhes a melhoria.

É perfeitamente possível recriar sua "visão de tabela única" dos dados com .. well .. Views.

    
por 26.04.2016 / 17:26
fonte
14

Why does a good relational schema improve a database?

A resposta é: ele não sempre melhora um banco de dados. Você deve estar ciente de que o que você provavelmente aprendeu é chamado de Terceira Forma Normal .

Outros formulários são válidos em algumas situações, o que é fundamental para responder à sua pergunta. Seu exemplo parece First Normal Form , se isso ajudar você a se sentir melhor com relação ao estado atual.

As regras 3NF estabelecem relações entre dados que "melhoram" uma base de dados:

  1. Impede que dados inválidos entrem em seu sistema (se um relacionamento for 1-para-1, ele forçará um erro, apesar do código escrito em cima dele). Se os dados estiverem consistentes no banco de dados, é menos provável que resultem em inconsistências fora do banco de dados.

  2. Ele fornece uma maneira de validar o código (por exemplo, um relacionamento de muitos para um é um sinal para restringir as propriedades / comportamentos de um objeto). Ao escrever código para usar o banco de dados, às vezes os programadores observam a estrutura de dados como um indicador de como o código deve funcionar. Ou eles podem fornecer feedback útil se o banco de dados não corresponder ao seu código. (Isso é mais como um desejo, infelizmente.)

  3. Forneça regras que possam ajudar significativamente a reduzir erros ao criar um banco de dados, para que você não o esteja construindo com base em requisitos arbitrários que possam surgir a qualquer momento durante a vida útil de um banco de dados. Em vez disso, você está avaliando sistematicamente as informações para atingir metas específicas.

  4. Estruturas adequadas de banco de dados levam a um desempenho melhorado conectando dados de maneiras que minimizam o armazenamento de dados, minimizam as chamadas de armazenamento para recuperar dados, maximizam recursos na memória e / ou minimizam a classificação / manipulação de dados para o conjunto de dados específico que você possui em comparação com a consulta que você está executando contra ela. Mas a estrutura "adequada" depende da quantidade de dados, natureza dos dados, tipo de consulta, recursos do sistema, etc. Ao normalizar você pode piorar o desempenho (isto é, se você carregar todos os dados como 1 tabela, a união pode diminuir a velocidade uma consulta). O processamento de transações (OLTP) versus business intelligence (data warehouse) é muito diferente.

Em uma pequena empresa com pequenos conjuntos de dados, você pode descobrir que não há nada de errado com a maneira como está agora. Exceto que, se você crescer, será uma dor "consertar" mais tarde, porque à medida que a mesa fica grande, os sistemas que a utilizam provavelmente serão mais lentos.

Normalmente, você desejará enfatizar as transações rápidas à medida que uma empresa cresce. No entanto, se você dedicar tempo a esse projeto agora, em vez de outras coisas que a empresa pode precisar com mais urgência, talvez nunca tenha esse problema porque sua empresa nunca cresce de fato. Esse é o "desafio de pré-otimização" - onde gastar seu precioso tempo agora.

Boa sorte!

    
por 26.04.2016 / 22:20
fonte
11

Existem várias razões pelas quais usar uma grande "tabela de deus" é ruim. Vou tentar ilustrar os problemas com um banco de dados de exemplo inventado. Vamos supor que você esteja tentando modelar eventos esportivos. Nós diremos que você quer modelar jogos e as equipes que jogam nesses jogos. Um design com várias tabelas pode se parecer com isso (isso é muito simplista de propósito, portanto, não seja pego em lugares onde mais normalização possa ser aplicada):

Teams
Id | Name | HomeCity

Games
Id | StartsAt | HomeTeamId | AwayTeamId | Location

e um banco de dados de tabela única ficaria assim

TeamsAndGames
Id | TeamName | TeamHomeCity | GameStartsAt | GameHomeTeamId | GameAwayTeamId | Location

Primeiro, vamos analisar índices nessas tabelas. Se eu precisasse de um índice na cidade de origem para um time, eu poderia adicioná-lo facilmente à tabela Teams ou à tabela TeamsAndGames . Lembre-se de que, sempre que você criar um índice, ele precisa ser armazenado em algum lugar no disco e atualizado conforme as linhas são adicionadas à tabela. No caso da tabela Teams , isso é bastante simples. Eu coloquei uma nova equipe, o banco de dados atualiza o índice. Mas e quanto a TeamsAndGames ? Bem, o mesmo se aplica a partir do exemplo Teams . Eu adiciono uma equipe, o índice é atualizado. Mas também acontece quando eu adiciono um jogo! Mesmo que esse campo seja nulo para um jogo, o índice ainda precisa ser atualizado e armazenado no disco para esse jogo. Para um índice, isso não parece muito ruim. Mas quando você precisa de muitos índices para as múltiplas entidades amontoadas nesta tabela, você gasta muito espaço armazenando os índices e muito tempo de processador atualizando-os para as coisas em que eles não se aplicam.

Segundo, consistência de dados. No caso de usar duas tabelas separadas, posso usar chaves estrangeiras da tabela Games para a tabela Teams para definir quais equipes estão jogando em um jogo. E supondo que eu faça as colunas HomeTeamId e AwayTeamId não anuláveis, o banco de dados irá garantir que cada jogo que eu coloquei tenha 2 equipes e que essas equipes existam no meu banco de dados. Mas e o cenário da mesa única? Bem, como há várias entidades nessa tabela, essas colunas devem ser anuláveis (você pode torná-las não anuláveis e inserir dados de lixo lá, mas isso é apenas uma ideia horrível). Se essas colunas forem anuláveis, o banco de dados não poderá mais garantir que, ao inserir um jogo, ele tenha duas equipes.

Mas e se você decidir apenas ir de qualquer maneira? Você configura as chaves estrangeiras de modo que esses campos apontem para outra entidade na mesma tabela. Mas agora o banco de dados apenas fará com que essas entidades existam na tabela, e não que elas sejam do tipo correto. Você pode facilmente definir GameHomeTeamId para o ID de outro jogo e o banco de dados não irá reclamar. Se você tentasse isso no cenário de várias tabelas, o banco de dados seria um ajuste.

Você poderia tentar atenuar esses problemas dizendo "bem, só nos certificaremos de que nunca faremos isso no código". Se você está confiante em sua capacidade de escrever código livre de bugs pela primeira vez e em sua capacidade de levar em consideração todas as estranhas combinações de coisas que um usuário pode tentar, vá em frente. Eu pessoalmente não estou confiante em minha capacidade de fazer qualquer uma dessas coisas, então eu vou deixar o banco de dados me dar uma rede de segurança extra.

(Isso fica ainda pior se o design for aquele em que você copie todos os dados relevantes entre as linhas, em vez de usar chaves estrangeiras. Quaisquer inconsistências de ortografia / outros dados serão difíceis de resolver. Como saber se "Jon" é um erro de ortografia? de "John" ou se foi intencional (porque são duas pessoas separadas)?)

Em terceiro lugar, quase todas as colunas precisam ser anuláveis ou devem ser preenchidas com dados copiados ou lixo. Um jogo não precisa de TeamName ou TeamHomeCity . Então, qualquer jogo precisa de algum tipo de marcador ou precisa ser anulável. E se for anulável, o banco de dados terá um jogo feliz sem TeamName . Ele também levará uma equipe sem nome, mesmo que sua lógica de negócios diga que isso nunca deveria acontecer.

Há várias outras razões pelas quais você deseja tabelas separadas (incluindo preservar a sanidade do desenvolvedor). Existem ainda algumas razões pelas quais uma tabela maior pode ser melhor (desnormalização às vezes melhora o desempenho). Esses cenários são poucos e distantes entre si (e geralmente são mais bem tratados quando você tem métricas de desempenho para mostrar que esse é realmente o problema, não um índice ausente ou outra coisa qualquer).

Finalmente, desenvolva algo que seja fácil de manter. Só porque "funciona" não significa que está tudo bem. Tentar manter tabelas de deus (como classes de deus) é um pesadelo. Você está apenas se preparando para a dor depois.

    
por 26.04.2016 / 19:01
fonte
6

Frase do dia: " Teoria e prática devem ser as mesmas ... em teoria "

Tabela desnormalizada

Sua tabela única de retenção contém dados redundantes tem uma vantagem: torna o relatório em suas linhas muito simples de codificar e rápido de executar porque você não precisa fazer nenhuma junção. Mas isso a um custo alto:

  • Ele contém cópias redundantes de relações (por exemplo, IngCompanyID e vrCompanyName ). A atualização dos dados principais pode exigir a atualização de muito mais linhas do que em um esquema normalizado.
  • Mistura tudo. Você não pode garantir um controle de acesso fácil no nível do banco de dados, por exemplo, garantindo que o usuário A possa atualizar apenas as informações da empresa e o usuário B apenas as informações do produto.
  • Você não pode garantir regras de consistência no nível do banco de dados (por exemplo, chave primária para garantir que haja apenas um nome de empresa para um ID de empresa).
  • Você não se beneficia totalmente do otimizador de banco de dados que poderia identificar estratégias de acesso ideais para uma consulta complexa, aproveitando o tamanho das tabelas normalizadas e estatísticas de vários índices. Isso pode compensar rapidamente o benefício limitado de evitar junções.

Tabela normalizada

As desvantagens acima são vantagens para o esquema normalizado. Claro, as consultas podem ser um pouco mais complexas para escrever.

Em resumo, o esquema normalizado expressa muito melhor a estrutura e as relações entre seus dados. Vou ser provocativo e dizer que é o mesmo tipo de diferença do que entre a disciplina necessária para usar um conjunto de gavetas de escritório ordenadas e a facilidade de uso de uma lixeira.

    
por 27.04.2016 / 00:41
fonte
5

Acho que há pelo menos duas partes para sua pergunta:

1. Por que entidades de tipos diferentes não devem ser armazenadas na mesma tabela?

As respostas mais importantes aqui são a legibilidade do código e a velocidade. Um SELECT name FROM companies WHERE id = ? é muito mais legível do que um SELECT companyName FROM masterTable WHERE companyId = ? e você tem menos probabilidade de consultar acidentalmente um disparate (por exemplo, SELECT companyName FROM masterTable WHERE employeeId = ? não seria possível quando as empresas e funcionários estiverem armazenados em tabelas diferentes). Quanto à velocidade, os dados de uma tabela de banco de dados são recuperados lendo a tabela completa sequencialmente ou lendo um índice. Ambos são mais rápidos se a tabela / índice contiver menos dados, e esse é o caso se os dados forem armazenados em tabelas diferentes (e você só precisar ler uma das tabelas / índices).

2. Por que as entidades de um único tipo devem ser divididas em subentidades armazenadas em tabelas diferentes?

Aqui, a razão é principalmente para evitar inconsistências de dados. Com a abordagem de tabela única, para um sistema de gerenciamento de pedidos, você pode armazenar o nome do cliente, endereço do cliente e ID do produto do produto que o cliente solicitou como uma única entidade. Se um cliente encomendou vários produtos, você teria várias instâncias do nome e endereço do cliente em seu banco de dados. No melhor dos casos, você acabou de obter dados duplicados em seu banco de dados, o que pode diminuir um pouco a velocidade. Mas um caso pior é que alguém (ou algum código) cometeu um erro quando os dados foram inseridos, para que as empresas acabem com endereços diferentes no banco de dados. Isso por si só já é ruim o suficiente. Mas se você consultasse o endereço de uma empresa com base em seu nome (por exemplo, SELECT companyAddress FROM orders WHERE companyName = ? LIMIT 1 ), você obteria arbitrariamente um dos dois endereços retornados e nem perceberia que havia uma inconsistência. Mas cada vez que você executa a consulta, você pode obter um endereço diferente, dependendo de como sua consulta é resolvida internamente pelo DBMS. Isso provavelmente quebrará seu aplicativo em outro lugar, e a causa raiz dessa quebra será muito difícil de encontrar.

Com a abordagem de várias tabelas, você perceberia que existe uma dependência funcional do nome da empresa para o endereço da empresa (se uma empresa puder ter apenas um endereço), você armazenaria o ( companyName, companyAddress) tupla em uma tabela (por exemplo, company ) e a tupla (productId, companyName) em outra tabela (por exemplo, order ). Uma restrição UNIQUE na tabela company poderia, então, impor que cada empresa tenha apenas um único endereço em seu banco de dados, de modo que nenhuma inconsistência nos endereços da empresa possa surgir.

Observação: na prática, por motivos de desempenho, você provavelmente geraria um companyId exclusivo para cada empresa e o usaria como uma chave estrangeira, em vez de usar diretamente o companyName. Mas a abordagem geral permanece a mesma.

    
por 27.04.2016 / 10:06
fonte
3

TL; DR - Eles estão projetando o banco de dados com base em como eles foram ensinados quando estavam na escola.

Eu poderia ter escrito essa pergunta há 10 anos atrás. Demorei algum tempo para entender por que meus antecessores projetaram seus bancos de dados da maneira que fizeram. Você está trabalhando com alguém que:

  1. Adquiriram a maioria de suas habilidades de design de banco de dados usando o Excel como banco de dados ou
  2. Eles estão usando as melhores práticas de quando saíram da escola.

Eu não suspeito que é # 1 desde que você realmente tem números de identificação em sua tabela, então eu vou assumir # 2.

Depois que eu saí da escola, eu estava trabalhando para uma loja que usava um AS / 400 (também conhecido como IBM i ). Eu encontrei algumas coisas estranhas na forma como eles projetaram seus bancos de dados, e comecei a defender que fizéssemos mudanças para seguir como eu aprendi como projetar bancos de dados. (eu era idiota naquela época)

Demorou um paciente programador mais velho para me explicar porque as coisas foram feitas dessa maneira. Eles não mudaram o esquema porque isso teria causado a quebra de programas que eram mais antigos do que eu. Literalmente, o código-fonte de um programa tinha uma data de criação do ano anterior ao meu nascimento. No sistema em que estávamos trabalhando, os seus programas tinham que implementar toda a lógica e as operações que o planejador de consulta de seu banco de dados manipula para você . (Você pode ver isso executando EXPLAIN em uma de suas consultas)

Ele estava atualizado sobre as técnicas que eu estava tentando implementar, mas manter o sistema funcionando era mais importante do que fazer mudanças "porque isso ia contra o que me foi ensinado". Todo novo projeto que qualquer um de nós começou fez melhor uso do modelo relacional que conseguimos. Infelizmente, outros programadores / consultores dessa época ainda projetaram seus bancos de dados como se estivessem trabalhando com as antigas restrições desse sistema.

Alguns exemplos do que encontrei que não se encaixam no modelo relacional:

  • As datas foram armazenadas como números do dia juliano que exigiam uma associação a uma tabela de datas para obter a data real.
  • Tabelas desnormalizadas com colunas sequenciais do mesmo tipo (por exemplo, code1,code2, ..., code20 )
  • colunas CHAR de comprimento NxM representando uma matriz de N cordas de comprimento M.

As razões que me foram dadas para essas decisões de design foram todas baseadas nas restrições do sistema quando o banco de dados foi criado pela primeira vez.

Datas - me disseram que demorava mais tempo de processamento para usar as funções de data (qual mês, dia ou dia da semana) para processar uma data do que criar uma tabela com todas as datas possíveis. essa informação.

Colunas sequenciais do mesmo tipo - O ambiente de programação em que elas estavam permitia que um programa criasse uma variável de matriz sobre parte da linha. E foi uma maneira mais fácil de reduzir o número de operações de leitura.

Colunas CHAR de comprimento NxM - foi mais fácil colocar os valores de configuração em uma única coluna para reduzir as operações de leitura de arquivos.

Um exemplo mal concebido em C equivalente para refletir o ambiente de programação que eles tinham:

#define COURSE_LENGTH 4
#define NUM_COURSES 4
#define PERIOD_LENGTH 2

struct mytable {
    int id;
    char periodNames[NUM_COURSES * PERIOD_LENGTH];  // NxM CHAR Column
    char course1[COURSE_LENGTH];
    char course2[COURSE_LENGTH];
    char course3[COURSE_LENGTH];
    char course4[COURSE_LENGTH];
};

...

// Example row
struct mytable row = {.id= 1, .periodNames="HRP1P2P8", .course1="MATH", .course2="ENGL", .course3 = "SCI ", .course4 = "READ"};

char *courses; // Pointer used to access the sequential columns
courses = (char *)&row.course1;


for(int i = 0; i < NUM_COURSES; i++) {

    printf("%d: %.*s -> %.*s\n",i+1, PERIOD_LENGTH, &row.periodNames[PERIOD_LENGTH * i], COURSE_LENGTH,&courses[COURSE_LENGTH*i]);
}

Outputs

1: HR -> MATH
2: P1 -> ENGL
3: P2 -> SCI
4: P8 -> READ

De acordo com o que me foi dito, parte disso foi considerada a melhor prática na época.

    
por 28.04.2016 / 05:14
fonte