graph TD
R[Relacao ALUNO] --> E[Esquema: nome dos atributos e dominios]
R --> C[Corpo: conjunto de tuplas]
E --> A1[atributo id_aluno -> dominio inteiro]
E --> A2[atributo nome -> dominio texto]
E --> A3[atributo nascimento -> dominio data]
C --> T1[tupla 1: 7, Ana, 2001-03-10]
C --> T2[tupla 2: 8, Bruno, 2000-11-22]
O Modelo Relacional e as Propriedades ACID
Neste módulo eu quero sentar ao seu lado e abrir, peça por peça, a engrenagem que sustenta praticamente todo banco de dados que você vai encontrar numa prova da USP ou no mundo real. No módulo anterior nós passamos rápido por palavras como “relação”, “tupla” e “ACID”, quase de raspão. Agora vou puxar cada uma dessas ideias até o osso, porque é exatamente aqui que as bancas gostam de armar pegadinhas: elas misturam o vocabulário formal de Codd com as garantias transacionais de um SGBD moderno, e quem não domina a teoria escorrega. Minha promessa para você é simples: ao terminar esta leitura, você não vai apenas reconhecer os termos, vai conseguir explicar por que cada um existe, o que aconteceria se ele não existisse e como o SGBD efetivamente entrega aquilo na prática.
Vou organizar nossa conversa em duas grandes metades que se conversam o tempo todo. Primeiro, o modelo relacional como uma construção matemática rigorosa, com suas chaves e suas regras de integridade. Depois, as propriedades ACID, que são as garantias que o sistema oferece quando você começa a mexer nesses dados com transações. A ponte entre as duas metades é a palavra “consistência”, e eu vou mostrar isso para você no momento certo.
O modelo relacional: uma teoria, não só uma tabela
Quando Edgar Frank Codd publicou seu modelo em 1970, ele fez algo que parece óbvio hoje, mas era revolucionário: separou completamente a forma como os dados são guardados fisicamente no disco da forma como nós pensamos sobre eles. Antes dele, programar contra um banco significava saber por quais ponteiros e arquivos os registros estavam encadeados. Codd disse, em essência: esqueça o disco, pense em conjuntos. Ele ancorou tudo na teoria dos conjuntos e na lógica de predicados de primeira ordem, e foi essa base matemática que deu ao modelo relacional uma propriedade rara: você pode raciocinar sobre ele com rigor, provar coisas, e o sistema pode otimizar suas consultas livremente porque você descreve o que quer, não como buscar.
A unidade central dessa teoria é a relação, que no dia a dia chamamos de tabela. Mas repare comigo numa sutileza que cai em prova: uma relação, no sentido formal, é um conjunto de tuplas. E conjunto, em matemática, não tem ordem e não tem elementos repetidos. Isso tem duas consequências diretas que muita gente esquece. A primeira é que, formalmente, não existe “primeira linha” ou “última linha” de uma relação; qualquer ordem que você veja é fruto de um ORDER BY ou de um acaso físico, nunca uma propriedade da relação em si. A segunda é que, no modelo puro, não podem existir duas tuplas idênticas, pois um conjunto não admite duplicatas. Guarde essa tensão, porque o SQL real, por razões práticas, permite linhas duplicadas quando você não define uma chave, e essa diferença entre o modelo teórico e a implementação é tema clássico de questão.
Definição central. Uma relação é um conjunto de tuplas que compartilham o mesmo conjunto de atributos. Cada tupla é um registro, uma associação de valores a atributos. Cada atributo é uma coluna nomeada, e a cada atributo está associado um domínio, que é o conjunto de valores válidos que aquele atributo pode assumir.
Deixe eu desenhar essa anatomia para você. Pense numa tabela de alunos: o nome da tabela e seus atributos formam o que chamamos de esquema (ou cabeçalho da relação), e as tuplas formam a extensão (o corpo, os dados de fato em determinado momento).
O domínio é um conceito que parece menor, mas é a base da integridade de domínio. Quando eu declaro que nascimento é do tipo DATE, estou dizendo ao SGBD que o conjunto de valores legais ali são datas, e o sistema vai recusar qualquer tentativa de gravar a string “azul” naquela coluna. Em SQL declaramos esse esqueleto assim:
Note que id_aluno é um número inteiro (um domínio), nome é um texto de até 120 caracteres (outro domínio com restrição de tamanho) e a cláusula PRIMARY KEY declara a chave primária. Vamos justamente às chaves agora, porque é o assunto que mais separa quem estudou de quem decorou.
Superchave, chave candidata e chave primária
Aqui eu preciso que você desacelere comigo, porque esses três termos formam uma hierarquia precisa, e as bancas adoram trocar um pelo outro para te confundir. Vou construir do mais geral para o mais específico.
Uma superchave é qualquer conjunto de atributos que identifica unicamente cada tupla de uma relação. “Qualquer” é a palavra-chave: se {id_aluno} já identifica unicamente um aluno, então {id_aluno, nome} também identifica, porque acrescentar atributos a uma identificação única continua sendo único. Por isso superchaves são abundantes e, em geral, contêm “gordura” — atributos que não são necessários para a unicidade.
Uma chave candidata é uma superchave mínima, ou seja, uma superchave da qual você não pode remover nenhum atributo sem perder a propriedade de identificação única. É a superchave enxuta, sem gordura. Uma relação pode ter várias chaves candidatas. Imagine uma tabela de funcionários onde tanto a matrícula quanto o CPF identificam unicamente cada pessoa: temos duas chaves candidatas.
A chave primária é, simplesmente, a chave candidata que o projetista escolheu para ser a identificadora oficial daquela relação. As demais candidatas passam a ser chamadas de chaves alternativas. A escolha não é arbitrária no vácuo: preferimos chaves estáveis, pequenas e que nunca sejam nulas, porque a chave primária carrega a regra de integridade de entidade, que diz que nenhum atributo da chave primária pode ser nulo. Faz sentido: se a chave existe para identificar a tupla, um valor nulo (que significa “desconhecido”) destruiria a capacidade de identificação.
Atenção à hierarquia. Toda chave candidata é uma superchave, mas nem toda superchave é candidata (só as mínimas são). Toda chave primária é uma chave candidata, mas nem toda candidata é primária (só a escolhida é). Se você visualizar isso como três círculos encaixados — superchaves contêm as candidatas, que contêm a primária —, dificilmente vai errar.
Formalmente, dizemos que um conjunto de atributos K é superchave de uma relação R se, para quaisquer duas tuplas t_1 e t_2 de R, vale a implicação t_1[K] = t_2[K] \Rightarrow t_1 = t_2. Em palavras: se duas tuplas concordam nos valores de K, então elas são a mesma tupla. A chave candidata adiciona a essa definição a exigência de minimalidade: nenhum subconjunto próprio de K é superchave.
| Conceito | É único por tupla? | É mínimo? | É o escolhido oficial? |
|---|---|---|---|
| Superchave | Sim | Não necessariamente | Não |
| Chave candidata | Sim | Sim | Não necessariamente |
| Chave primária | Sim | Sim | Sim |
| Chave alternativa | Sim | Sim | Não (é candidata não escolhida) |
Chave estrangeira e o nascimento dos relacionamentos
Até aqui falamos de identificar tuplas dentro de uma única relação. A chave estrangeira é o mecanismo que conecta relações diferentes, e é ela que transforma um amontoado de tabelas isoladas num banco de dados de verdade. Uma chave estrangeira é um atributo (ou conjunto de atributos) em uma relação que referencia a chave primária de outra relação. A tabela que aponta é a filha (ou referenciadora); a tabela apontada é a pai (ou referenciada).
Vou amarrar nossa tabela de alunos a uma tabela de matrículas em cursos para você ver o vínculo nascer:
graph LR
subgraph PAI
A[ALUNO -- PK id_aluno]
end
subgraph FILHA
M[MATRICULA -- PK id_matricula -- FK id_aluno]
end
M -->|id_aluno referencia| A
A coluna id_aluno na tabela matricula não identifica a matrícula; ela aponta para qual aluno aquela matrícula pertence. E aqui surge a regra que governa toda essa conexão.
Integridade referencial e as regras de ação
A integridade referencial é a garantia de que toda chave estrangeira preenchida aponta para uma tupla que realmente existe na tabela pai. Em outras palavras: não pode haver uma matrícula apontando para um aluno que não está na tabela aluno. Se isso acontecesse, teríamos um registro órfão, um filho sem pai, e o banco perderia a consistência. O SGBD impede isso automaticamente assim que você declara a chave estrangeira. Repare nas três frentes em que essa proteção atua.
Na inserção da tupla filha, o sistema verifica se o valor da chave estrangeira existe na tabela pai; se não existir, rejeita. Na atualização ou exclusão da tupla pai, surge o problema interessante: o que fazer com os filhos que dependiam dela? É aqui que entram as regras de ação referencial, que você declara com as cláusulas ON DELETE e ON UPDATE.
As quatro regras de ação referencial. Elas dizem ao SGBD como reagir quando a tupla pai é alterada ou removida e existem filhos apontando para ela.
A regra NO ACTION (e sua prima quase idêntica RESTRICT) é a postura defensiva e costuma ser o padrão: o sistema bloqueia a operação se ela deixar filhos órfãos. Você tenta apagar um aluno que tem matrículas, e o banco recusa, protegendo a integridade pela negativa. A diferença sutil entre as duas é o momento da checagem dentro da transação — RESTRICT verifica imediatamente, NO ACTION pode adiar até o fim do comando —, mas para a maioria das provas trate-as como sinônimos de “proibir”.
A regra CASCADE é a postura propagadora: o que acontece com o pai escorre para os filhos. No ON DELETE CASCADE, apagar o aluno apaga junto todas as suas matrículas. No ON UPDATE CASCADE, mudar o id_aluno na tabela pai atualiza automaticamente esse valor em todas as matrículas referenciadoras. É poderoso e perigoso: uma exclusão pode disparar uma reação em cadeia por várias tabelas, então use com consciência.
A regra SET NULL define a chave estrangeira dos filhos como nulo quando o pai some. A matrícula sobrevive, mas fica “sem aluno”, o que só faz sentido se a coluna da chave estrangeira aceitar nulos (não pode ser NOT NULL). A regra SET DEFAULT é parecida, mas em vez de nulo coloca um valor padrão previamente definido — útil, por exemplo, para reapontar registros a uma categoria genérica “indefinido”.
| Regra | Ao apagar/alterar o pai com filhos | Exige FK aceitar NULL? |
|---|---|---|
| NO ACTION / RESTRICT | Bloqueia a operação | Não |
| CASCADE | Propaga a operação aos filhos | Não |
| SET NULL | Filhos ficam com FK nula | Sim |
| SET DEFAULT | Filhos recebem valor padrão | Não (mas o default deve existir no pai) |
Pegadinha comum. SET NULL só funciona se a coluna da chave estrangeira puder ser nula. Se você declarou id_aluno INTEGER NOT NULL e ao mesmo tempo ON DELETE SET NULL, há uma contradição: o SGBD não consegue cumprir a regra sem violar o NOT NULL. As bancas montam exatamente esse cenário para testar se você percebe a incompatibilidade.
Repare que a integridade referencial é uma das três integridades clássicas. A integridade de entidade cuida da chave primária não nula; a integridade referencial cuida das chaves estrangeiras válidas; e a integridade de domínio cuida de cada valor pertencer ao seu domínio, frequentemente reforçada por restrições CHECK. Juntas, elas formam a malha de regras que o próximo conceito — a consistência das transações — precisa preservar.
As propriedades ACID em profundidade
Agora cruzamos a ponte. Tudo o que vimos sobre chaves e integridade descreve o banco em repouso, um estado estático e válido. Mas bancos não ficam parados: eles recebem operações o tempo todo, muitas vezes simultâneas, e o hardware às vezes falha no pior momento possível. As propriedades ACID são as quatro garantias que o SGBD oferece para que, mesmo no meio desse caos, os dados continuem confiáveis. ACID é a sigla de Atomicidade, Consistência, Isolamento e Durabilidade, e o veículo dessas garantias é a transação: uma sequência de operações que o sistema trata como uma unidade lógica única.
Vou usar o exemplo que é praticamente um clichê da área, mas que é clichê justamente por ser perfeito: uma transferência bancária de R$ 200 da conta de Ana para a de Bruno. São duas operações: debitar 200 de Ana e creditar 200 em Bruno.
Atomicidade: tudo ou nada
A atomicidade garante que a transação seja indivisível: ou todas as suas operações são efetivadas, ou nenhuma é. Não existe meio-termo. Imagine que, depois de debitar os 200 de Ana, o servidor cai antes de creditar em Bruno. Sem atomicidade, o dinheiro simplesmente evaporaria: saiu de uma conta e não chegou na outra. A atomicidade impede esse desastre. Quando a transação não consegue chegar ao fim, o SGBD executa um rollback, desfazendo o débito já aplicado e devolvendo o banco ao exato estado anterior ao BEGIN. Só quando o COMMIT é alcançado é que as duas operações se tornam permanentes em conjunto. A atomicidade é o “tudo ou nada”.
Consistência: o banco respeita as próprias regras
A consistência afirma que uma transação leva o banco de um estado válido para outro estado válido. E “válido” aqui significa exatamente aquilo que construímos na primeira metade do módulo: chaves primárias não nulas, chaves estrangeiras apontando para tuplas existentes, domínios respeitados, restrições CHECK satisfeitas. Esta é a propriedade que amarra as duas metades da nossa conversa. No nosso exemplo, suponha que exista uma regra de negócio óbvia: a soma total do dinheiro no banco não pode mudar numa transferência (o que sai de uma conta entra na outra). Uma transferência bem feita preserva esse invariante. Se uma transação tentasse violar uma restrição declarada — por exemplo, deixar um saldo negativo onde há um CHECK (saldo >= 0) —, o SGBD recusa o COMMIT e cai em rollback. Vale uma observação honesta de professor: parte da consistência é responsabilidade do SGBD (as restrições declaradas), e parte é responsabilidade de quem escreve a transação (as regras de negócio que não foram declaradas como restrição). O sistema garante o que você declarou.
Isolamento: transações concorrentes não se atrapalham
O isolamento trata do que acontece quando várias transações rodam ao mesmo tempo. A garantia é que o resultado final seja como se as transações tivessem rodado uma após a outra, em alguma ordem sequencial — mesmo que, por baixo, o SGBD as execute entrelaçadas para ganhar desempenho. Os efeitos intermediários de uma transação não devem ser visíveis para as outras antes do COMMIT. Pense em duas transferências simultâneas mexendo na conta de Ana: sem isolamento, uma poderia ler o saldo no meio da outra e calcular em cima de um valor que ainda vai ser desfeito, gerando os famosos fenômenos de leitura suja, leitura não repetível e leitura fantasma. Para controlar o quanto de isolamento você quer (pois isolamento total custa desempenho), o padrão SQL define quatro níveis, do mais permissivo ao mais rígido. Vamos aprofundar concorrência, bloqueios e MVCC em módulo próprio, mas você já precisa reconhecer esta escala:
| Nível de isolamento | Leitura suja | Leitura não repetível | Leitura fantasma |
|---|---|---|---|
| Read Uncommitted | Permite | Permite | Permite |
| Read Committed | Evita | Permite | Permite |
| Repeatable Read | Evita | Evita | Permite |
| Serializable | Evita | Evita | Evita |
Durabilidade: o que foi confirmado, persiste
A durabilidade garante que, uma vez dado o COMMIT, as mudanças sobrevivem a qualquer falha posterior — queda de energia, travamento do sistema operacional, pane no processo do banco. O dinheiro que chegou na conta de Bruno está lá para ficar. O mecanismo que torna isso possível, sem sacrificar desempenho, é o Write-Ahead Logging: antes de aplicar a mudança nas páginas de dados em si, o SGBD grava num log sequencial em disco a descrição do que vai fazer. Como o log é gravado de forma garantida antes do COMMIT retornar sucesso, mesmo que o sistema caia logo em seguida, na reinicialização o banco lê o log e refaz as transações confirmadas que ainda não tinham chegado ao disco de dados, e desfaz as que ficaram pela metade. É a durabilidade conversando com a atomicidade.
stateDiagram-v2
[*] --> Ativa: BEGIN
Ativa --> ParcialmenteConfirmada: ultima operacao executada
Ativa --> Falha: erro ou abort
ParcialmenteConfirmada --> Confirmada: COMMIT gravado no log
ParcialmenteConfirmada --> Falha: erro ao confirmar
Falha --> Abortada: ROLLBACK desfaz tudo
Confirmada --> [*]
Abortada --> [*]
Esse diagrama de estados é um mapa que vale memorizar. Uma transação nasce ativa, vai executando suas operações; quando a última operação termina ela fica parcialmente confirmada; se o COMMIT é gravado com durabilidade no log, ela vira confirmada e seus efeitos são permanentes. Em qualquer ponto, um erro a leva para o estado de falha, do qual o ROLLBACK a conduz a abortada, restaurando o estado anterior. Atomicidade e durabilidade são, no fundo, propriedades sobre esses caminhos: atomicidade garante que o caminho da falha apague todo rastro, durabilidade garante que o caminho do commit nunca seja apagado.
A grande síntese de ACID. Atomicidade protege contra falhas no meio de uma transação (tudo ou nada). Consistência garante que o resultado respeita todas as regras de integridade do esquema. Isolamento protege contra a interferência entre transações concorrentes. Durabilidade protege contra falhas depois do commit. Quatro garantias, quatro tipos de ameaça diferentes.
Estruturas de dados que dão vida ao modelo
O modelo relacional é uma abstração lógica, mas ele precisa de estruturas concretas para funcionar com desempenho. A tabela é a estrutura fundamental de armazenamento, composta por colunas (os atributos), linhas (as tuplas) e chaves (que identificam e relacionam). Mas ao lado das tabelas existem duas estruturas que você precisa conhecer.
Os índices são estruturas auxiliares que aceleram as buscas, funcionando como o índice remissivo no fim de um livro: em vez de o SGBD varrer a tabela inteira procurando uma tupla, ele consulta o índice e salta direto para a localização. O tipo mais comum é o índice B-Tree, eficiente tanto para igualdade quanto para buscas por intervalo, e por isso o padrão para a maioria dos casos. Há também índices hash, ótimos para igualdade exata mas inúteis para intervalos, e índices de texto completo para busca em campos textuais. Há um custo escondido que cai em prova: índices aceleram leituras, mas tornam as escritas mais lentas, porque cada INSERT, UPDATE ou DELETE precisa atualizar também os índices. Índice não é gratuito.
As visões (ou views) são tabelas virtuais definidas por uma consulta. Elas não guardam dados próprios: ao consultar uma view, o SGBD executa a consulta subjacente na hora. Servem para simplificar consultas complexas, dando um nome amigável a uma junção elaborada, e para implementar segurança, expondo apenas certas colunas ou linhas a determinados usuários.
Síntese do que mais cai em prova. Domine a hierarquia das chaves: superchave é qualquer identificador único, chave candidata é a superchave mínima, chave primária é a candidata escolhida, e as demais candidatas viram alternativas. Lembre que a chave primária carrega a integridade de entidade (nunca nula). A chave estrangeira referencia a chave primária de outra tabela e impõe a integridade referencial, proibindo registros órfãos. Saiba de cor as quatro regras de ação: NO ACTION/RESTRICT bloqueia, CASCADE propaga, SET NULL anula a FK e SET DEFAULT a leva ao padrão — e que SET NULL exige uma coluna que aceite nulos. No terreno transacional, ACID é o coração: Atomicidade é tudo ou nada com rollback; Consistência preserva todas as regras de integridade do esquema; Isolamento faz transações concorrentes não interferirem, com quatro níveis indo de Read Uncommitted a Serializable; e Durabilidade torna o commit permanente via Write-Ahead Logging. Se você souber explicar a transferência bancária pelas quatro letras e conectar a Consistência às regras de chave que estudamos na primeira metade, você entendeu o módulo inteiro.
Fechamos aqui o aprofundamento que o módulo anterior só prometeu. Você agora tem o vocabulário formal de Codd e as garantias transacionais de um SGBD moderno amarrados num único raciocínio. No próximo módulo vamos pegar a normalização, que mencionei de passagem, e mostrar como ela usa exatamente essas chaves para eliminar redundância e anomalias de forma sistemática.