Estrutura física do banco de dados (InterBase e Firebird)
Alexey Kovyazin, Sergey Vostrikov, última atualização 05-junho-2004
Estrutura física do banco de dados
Por que temos que estudar a estrutura física do banco de dados InterBase?
Normalmente, quando falamos sobre a estrutura física do banco de dados InterBase, queremos dizer que ela representa dados do ponto de vista da organização de dados de baixo nível - até o nível de bytes. Muitos programadores que desenvolvem aplicações usando linguagem de alto nível negligenciam o estudo de detalhes de baixo nível. No entanto, conhecer os princípios principais da organização de dados dentro do banco de dados é a chave para o projeto eficaz de aplicações de banco de dados. Portanto, faremos uma excursão ao interior da organização do banco de dados InterBase e descobriremos como ele é estruturado.
Então, para que serve um sistema de gerenciamento de banco de dados (SGBD)? Obviamente para armazenar e controlar os dados. Parece banal, mas vale a pena pensar sobre isso. Um usuário insere os dados no SGBD, que de alguma forma traduz esses dados para formatos internos compreensíveis para ele. Você pode imaginar “0 e 1” se as palavras “formato interno de dados” causarem algumas dificuldades com associações. O SGBD armazena esses dados e, no momento da primeira solicitação, deve extraí-los do seu formato, convertê-los em uma visão adequada e entregá-los ao usuário.
O assunto deste capítulo é como o SGBD armazena seus dados, de que forma e como eles são organizados no nível mais baixo. Tentaremos explicar a você como, a partir de bits e bytes, que estão no disco rígido, obtemos dados valiosos.
Arquivos de banco de dados InterBase
Normalmente, quando falamos sobre um banco de dados, queremos dizer o próprio SGBD e as informações do usuário, e até mesmo os programas dos clientes que trabalham com os dados. Neste capítulo, consideraremos um banco de dados como arquivos de banco de dados.
O banco de dados InterBase representa um ou vários arquivos contendo informações sobre tudo o que está relacionado a esse banco. Informações sobre usuários são uma exceção, pois os usuários são definidos no nível de todo o servidor e são armazenados separadamente, no banco de dados de segurança admin.ib (era ISC4.GDB em versões anteriores à 7).
Conselho: Consulte o capítulo “Segurança do servidor e do banco de dados” para estudar mais sobre os princípios de segurança do InterBase.
Então, todas as informações sobre o banco de dados são armazenadas dentro desses arquivos: os próprios dados, índices, gatilhos, procedimentos armazenados, etc.
O banco de dados InterBase para um projeto médio representa um único arquivo, pois as versões modernas do InterBase podem usar IO de 64 bits para operar com o arquivo de dados, o que dá a capacidade de ter um arquivo de dados de até 64 GB. Versões anteriores do InterBase tinham a restrição de 4 gigabytes por cada arquivo de banco de dados (até 64 TB para todo o banco de dados). Como podemos supor, 64 gigabytes são suficientes para armazenar informações de quase qualquer aplicação de banco de dados. Mas, se necessário, podemos dividir um banco de dados em vários arquivos. A propósito, existem bancos de dados InterBase de centenas de gigabytes de tamanho.
IBSurgeon - um guia através do banco de dados InterBase
Temos que conhecer em detalhes a estrutura dos arquivos de banco de dados InterBase. E, portanto, é desejável ter qualquer ferramenta conveniente que permita trabalhar diretamente com os arquivos de banco de dados, não por meio do núcleo do servidor InterBase. A maneira mais fácil é usar um visualizador hexadecimal comum e tentar entender a estrutura dos arquivos de banco de dados considerando sua representação HEX. Seria um trabalho bastante tedioso.
Mas, felizmente, existe uma ferramenta para trabalho direto com bancos de dados InterBase. É o IBSurgeon Editor - uma ferramenta para trabalho direto de baixo nível com bancos de dados InterBase, que pode ser usada para estudar a estrutura interna de bancos de dados InterBase e diagnosticar bancos de dados corrompidos, a fim de restaurá-los. Para mais detalhes, consulte o apêndice “Ferramentas para administrador e desenvolvedor InterBase”.
O IBSurgeon usa seu próprio mecanismo alternativo de acesso ao banco de dados que permite abrir e revisar bancos de dados em qualquer estado, incluindo os severamente corrompidos que não podem ser abertos pelo núcleo do servidor InterBase/FireBird/Yaffil.
Usaremos o IBSurgeon para ilustrar a estrutura interna do banco de dados.
Arquivos *.IB/*.FDB por dentro
IB é uma extensão recomendada para arquivos de banco de dados InterBase, e FDB para Firebird (anteriormente era GDB). A primeira coisa que temos que dizer sobre a estrutura do arquivo IB é que ele representa um conjunto de páginas de tamanho estritamente definido. O tamanho do arquivo de banco de dados é divisível pelo tamanho da página, que é inalterado para todos os arquivos deste banco de dados. Diferentes versões do InterBase suportam diferentes tamanhos de página, como mostrado na tabela 1. O tamanho da página é definido ao criar um banco de dados e não pode ser alterado durante seu ciclo de vida. Em outras palavras, só podemos alterar o tamanho da página ao restaurar um banco de dados a partir de um backup.
Tabela 1. Tamanhos de página suportados por diferentes versões do InterBase
| Versão do InterBase | Tamanho da página, bytes | ||||
| 1024 | 2048 | 4096 | 8192 | 16384 | |
| InterBase 4.0 | * | * | * | * | |
| InterBase 5.x | * | * | * | * | |
| InterBase 6.x-7.x | * | * | * | * |
A leitura e a escrita de dados em um banco de dados são executadas página por página; muitas características importantes do servidor e do banco de dados, como o tamanho do cache do banco de dados, dependem do tamanho da página e são contadas em “páginas”.
Vamos abrir qualquer banco de dados InterBase pelo IBSurgeon. Basta clicar duas vezes no arquivo do banco de dados. A figura 1 expressa uma lista de páginas que aparecem depois que o IBSurgeon abriu o banco de dados:

Figura 1. Uma lista de páginas do banco de dados
As páginas podem ser de diferentes tipos, cada uma servindo a um determinado propósito. As interdependências de diferentes tipos são representadas condicionalmente na figura 2. A figura 2 representa esquematicamente uma alocação de páginas no arquivo de banco de dados - da esquerda para a direita, de cima para baixo, se contando a partir do início do arquivo. Páginas do mesmo tipo não vão estritamente uma após a outra - elas podem ser facilmente misturadas, alocadas em um arquivo na ordem em que foram criadas pelo servidor ao estender ou criar bancos de dados.

Figura 2. Interdependências entre diferentes tipos de páginas no banco de dados InterBase
Você deve ter notado que alguns tipos de páginas não têm referências a outros tipos de páginas. No entanto, não há contradição aqui; a questão é que esses tipos de páginas estão vinculados e usados em outro nível estrutural. Eles podem estar vinculados à tabela RDB$PAGES e outras tabelas do sistema (esta tabela e outros objetos do sistema serão considerados abaixo - no capítulo “Estrutura lógica do banco de dados”). Na figura 2, podemos ver apenas referências explícitas entre as páginas no nível físico.
Vamos considerar em detalhes quais tipos de páginas existem no banco de dados InterBase. No arquivo ods.h do conjunto de códigos primários do InterBase, há informações sobre todos os tipos possíveis de páginas. Referiremo-nos frequentemente a este arquivo para receber os dados não apenas sobre ODS, mas também sobre muitas outras coisas fundamentais do núcleo do InterBase na fonte original. 11 tipos de páginas são declarados no total, mas apenas 9 deles merecem ser explicados (podemos ver claramente isso na tabela 2). Tipos de página com identificadores 0 e 1 são indefinidos ou não usados.
Tabela 3. Tipos de página no FB
| A definição em ods.h | Identificador do tipo de página | Descrições da página |
| pag_undefined | 0 | Indefinido - Se uma página tem este tipo de página, provavelmente está livre |
| pag_header | 1 | Página de cabeçalho do banco de dados |
| pag_pages | 2 | Página de inventário de páginas (ou página de inventário de espaço - SIP) |
| pag_transactions | 3 | Página de inventário de transações (TIP) |
| pag_pointer | 4 | Página de ponteiro |
| pag_data | 5 | Página de dados |
| pag_root | 6 | Página raiz de índice |
| pag_index | 7 | Página de índice (B-tree) |
| pag_blob | 8 | Página de dados Blob |
| pag_ids | 9 | Gen-ids |
| pag_log | 10 | Informações de log de write-ahead |
Toda página tem seu cabeçalho, contendo informações sobre o tipo de página e o número da próxima página do mesmo tipo. Podemos obter a lista completa de parâmetros que todo cabeçalho de página contém, se considerarmos a estrutura pag no arquivo de definições ods.h.
/\* Cabeçalho básico da página */
typedef struct pag {
SCHAR pag_type; /*identificador do tipo de página*/
SCHAR pag_flags; /*flags da página*/
USHORT pag_checksum; /*checksum da página: é igual a 12345 após a versão 5.0 */
ULONG pag_generation; /*geração da página */
ULONG pag_seqno; /* WAL seqno da última atualização - obsoleto*/
ULONG pag_offset; /* WAL offset da última atualização - obsoleto*/
} *PAG;
Tipos de página e seu uso
Vamos considerar cada tipo de página em detalhes e conhecer sua função e as informações que contêm. Começaremos passo a passo - a partir da primeira página.
Qualquer operação com um banco de dados começa com a leitura da página de cabeçalho do banco de dados (ou página de cabeçalho). A página de cabeçalho do banco de dados vem primeiro em todos os arquivos de banco de dados. Consequentemente, ela é representada primeiro na figura 2 (se imaginarmos que a figura representa uma extensão do arquivo de banco de dados da esquerda para a direita, de cima para baixo).
Uma página de cabeçalho contém informações sobre o banco de dados como um todo. Na figura 3, uma página de dados é expressa da maneira que o IBSurgeon nos mostra:

Figura 3. Página de cabeçalho do banco de dados.
Você pode ter uma ideia do conteúdo da página de cabeçalho, tendo recebido as estatísticas do banco de dados. Para isso, você pode usar o utilitário de linha de comando gstat ou outra ferramenta mais conveniente para administração do InterBase da lista no apêndice “Ferramentas para administrador e desenvolvedor InterBase”. Para mais detalhes sobre o processo de recebimento de estatísticas e descrição dos dados da página de cabeçalho, consulte o capítulo “Estatísticas”.
Deve-se notar que a página de cabeçalho contém informações importantes como tamanho da página, número da versão ODS (informações sobre isso você encontrará abaixo), dados de criação do banco de dados, informações sobre transações e um conjunto de diferentes informações. Por exemplo, o ID de Implementação armazena informações sobre sob qual sistema operacional este banco de dados foi criado.
Ao conectar um banco de dados, o servidor InterBase lê os primeiros 1024 bytes de informações do início do arquivo e define, de acordo com os valores lidos, se o arquivo apontado na linha de conexão é um banco de dados InterBase ou não. Então o servidor lê o número da versão ODS de uma página de cabeçalho e o tamanho da página neste banco de dados e, se a versão ODS for compatível com a implementação do servidor, ele relê toda a página de cabeçalho, usando o tamanho de página adequado, recebido dos primeiros 1024 bytes. Depois disso, os demais parâmetros importantes do banco de dados, como modo de leitura-escrita, dialeto do banco de dados, etc., são lidos da página de cabeçalho.
Na página de cabeçalho há uma referência à primeira das páginas de ponteiro, armazenando referências a páginas de dados que contêm metadados: a tabela RDB$Pages (veja abaixo no capítulo “Estrutura lógica do banco de dados InterBase”). Na figura 2, esta referência é ilustrada por uma seta com a inscrição “Número da 1ª página de ponteiro no banco de dados”. O servidor lê o número da 1ª página de ponteiro da página de cabeçalho e vai para ela. A página de ponteiro consiste em um array ordenado de números de páginas de dados que formam uma determinada tabela (uma tabela é considerada como objeto SQL, descrito pela estrutura lógica do banco de dados). Agora você pode ver como o IBSurgeon interpreta a página de ponteiro (veja a figura 4):

Figura 4. Página de ponteiro do banco de dados InterBase
A página contém um vetor de páginas de dados; esses dados formam uma determinada tabela em um banco de dados. Este vetor representa um array de ponteiros correspondentes aos números das páginas de dados no arquivo. O servidor lê um número de 4 bytes da página de dados e vai para a página de dados necessária. Quando vai para a 1ª página de dados do RDB$Pages, o servidor começa a construir a representação interna do banco de dados que é usada posteriormente pelo servidor para todas as operações com o banco de dados. O RDB$Pages armazena referências não apenas a páginas de dados contendo informações sobre o banco de dados, mas também às demais páginas que desempenham um papel no fornecimento do funcionamento do banco de dados.
Mencionamos frequentemente esta tabela que, estritamente falando, pertence à estrutura lógica do banco de dados. No entanto, tudo está interconectado, portanto não podemos descrever algo sem nos referirmos a outra coisa.
Um dos tipos de página importantes é a página de inventário de transações (TIP). Essas páginas, como todas as páginas, consistem em um cabeçalho e uma parte principal que representa uma matriz de sequências de 2 bytes. As sequências descrevem o estado das transações em um banco de dados (para mais detalhes sobre transações, consulte o capítulo «Transações»).
Tabela 4. Possíveis estados de transação na TIP
| Valor da sequência na PIP | Significado |
| 0 | Transição não iniciada, ativa ou perdida sem commit ou rollback |
| 1 | Transição executou Commit |
| 2 | Transição executou Rollback |
| 3 | Transição em limbo (para 2PC) |
Cada versão de registro tem seu identificador de transação, o que permite que transações executadas simultaneamente «aprendam» sobre o estado umas das outras e resolvam conflitos durante o trabalho multiusuário (consulte o capítulo “Arquitetura multi-geração do InterBase” para saber mais sobre versões de registro e outros detalhes).
A página de cabeçalho do banco de dados, as páginas de ponteiro e a TIP referem-se aos tipos de página de «manutenção interna», usados apenas pelo servidor. Os usuários do InterBase nunca obtêm explicitamente as informações que elas contêm. As páginas que armazenam informações sobre a alocação de páginas (geralmente mencionadas como Páginas de Inventário de Páginas (PIP) ou Páginas de Inventário de Espaço (SIP)) também se referem ao tipo de página de manutenção interna. Essas páginas estão localizadas a partir da segunda, ou seja, a primeira PIP vem logo após a página de cabeçalho, e aparecem em um banco de dados em intervalos fixos de páginas de outros tipos. O tamanho desses intervalos indica em quantas páginas de outros tipos a PIP aparece e depende do tamanho de página definido para este banco de dados. As Páginas de Inventário de Páginas não são contabilizadas nas páginas de ponteiro e não são apontadas em RDB$Pages. A integridade dessas páginas é vital para o funcionamento bem-sucedido de todo o banco, pois o conteúdo da PIP descreve o estado de todas as demais páginas do banco de dados. Cada página do banco de dados pode ter 3 estados: não alocada, alocada com espaço, alocada e cheia. Quando há necessidade de espaço adicional para novos dados, o servidor verifica a PIP para ver se há páginas não alocadas. Se houver tal página, o servidor altera seu estado para alocada com espaço. Se não houver páginas não alocadas, o banco de dados se expande - uma nova página de dados é adicionada.
Um exemplo de página de dados no IBSurgeon e os dados que ela contém é dado na figura 5.

Figura 5. Página de Inventário de Páginas
Assim que a página é alocada, o InterBase escreve seu estado na SIP e depois grava a própria página. Depois disso, temos que adicionar esta página recém-formada a algum grande conjunto de páginas, por exemplo, às páginas de dados de uma tabela. Para isso, devemos escrever a referência a esta nova página na última página deste grande conjunto de páginas - por exemplo, na última página de dados de uma tabela. Se o servidor interromper seu trabalho logo após escrever na SIP, mas sem escrever a referência nas páginas que se referem à página recém-alocada, então esta página se torna órfã. Uma página órfã é fisicamente criada, reservada na SIP, mas não há referências a ela de outras páginas, o que significa que o servidor não conseguirá encontrá-la e escrever dados no disco. A página órfã é marcada pelo quadrado vermelho na figura 2. Páginas órfãs surgem principalmente como resultado de uma queda inesperada de energia do servidor e são «curadas» pela ferramenta especial de reparo de banco de dados gfix (ou pelo FirstAID) (ou pelo IBSurFirstAID).
Antes de considerar as páginas de dados, devemos mencionar tipos importantes de página: páginas de gerador e páginas de índice. As páginas de gerador representam uma matriz de números de 4 bytes, mostrando os estados dos geradores. Na verdade, o gerador é um contador comum.
Na figura 6 você pode ver uma página de gerador. Observe que, embora o IBSurgeon mostre os nomes dos geradores, esses nomes não são armazenados nas páginas de gerador. Isso é feito para a conveniência do usuário que está estudando o banco de dados. Na realidade, os nomes dos geradores são armazenados na tabela de sistema RDB$Generators.

Figura 6. Página de gerador (g en-ids )
Como você vê neste exemplo, o banco de dados contém geradores de sistema, começando com o prefixo RDB$, e geradores definidos pelo usuário. Se você quiser saber sobre a função e o uso de geradores ao desenvolver aplicativos de banco de dados InterBase, consulte o capítulo «Tabelas. Chaves primárias e geradores». As páginas de gerador são contabilizadas junto com outras páginas na tabela RDB$Pages.
Toda tabela tem pelo menos uma página raiz de índice, independentemente de ter índices ou não. Esta página contém ponteiros para as páginas de índice de uma tabela específica. Podemos dizer que a página raiz de índice tem a mesma importância para as páginas de índice que a página de ponteiro tem para as páginas de dados. Portanto, o IBSurgeon a representa de maneira semelhante. Um exemplo de página raiz de índice é dado na figura 7.

Figura 7. Página raiz de índice
A página raiz de índice contém uma lista de páginas onde os valores do índice são armazenados, bem como informações do índice - seletividade do índice e diferentes flags. Para mais detalhes sobre índices, seu papel e uso em bancos de dados InterBase, consulte o capítulo «Índices».
As páginas de índice contêm diretamente os valores dos índices, ou, se o nível do índice > 0, referências às páginas de índice subjacentes. Aqui está um exemplo de página de índice (figura 8).

Figura 8. Página de índice (B-tree)
A página de índice armazena valores compactados dos dados indexados. Um mecanismo de indexação bastante complexo é usado, especialmente ao criar índices compostos (incluindo vários campos).
Em geral, as páginas de dados e as páginas que contêm valores BLOB armazenam informações do usuário. As páginas de dados contêm registros nas tabelas de usuário do banco de dados, fragmentos de registros, versões antigas, diferenças entre versões, campos BLOB e assim por diante. Quanto aos campos BLOB, eles estão conectados aos registros nas páginas de dados e contêm dados de grande tamanho, que não podem ser localizados na página de dados. O tipo de referência para armazenar valores BLOB permite armazenar grandes dados.
Um exemplo de apresentação de página de dados no IBSurgeon é dado na figura 9:

Figura 9. Página de dados
O cabeçalho da página de dados contém o tipo de página e o identificador da tabela proprietária (relationID). Os registros são armazenados nas páginas de dados a partir do final da página e são alocados mais próximos do início da página à medida que são preenchidos.
Podemos ter certeza disso se olharmos os índices de linha, que contêm 2 valores - deslocamento na página e seu comprimento. Como você vê no início da linha, há registros alocados no final da página - por exemplo, o primeiro registro tem deslocamento de 8156 bytes e comprimento de 34 bytes - portanto, ele termina em 8156+34=8192 bytes - bem no limite da página (no nosso caso, o tamanho da página é 8192 bytes). Quando a página está cheia (com dados do topo e índices de registro da base), o servidor começa a escrever novos registros e versões de registros antigos em novas páginas. Pelo mecanismo de preenchimento de página descrito acima, podemos facilmente entender por que os especialistas em InterBase recomendam enfaticamente o uso de páginas de dados de tamanho grande (4096 bytes no mínimo, melhor 8192). Se criarmos uma tabela em que um registro será de tamanho bastante grande (por exemplo, 10 campos de VARCHAR (255)), eles ocuparão, serão preenchidos, mais de 2550 bytes. Isso significa que tal registro será grande demais para uma página de tamanho pequeno (1024 ou 2048). É óbvio que a necessidade de carregar várias páginas do disco para ler um único registro não acelerará o trabalho com seu banco de dados. Portanto, é recomendável redefinir o tamanho da página de dados ao criar ou restaurar um banco de dados, pois o tamanho de 1024 bytes é definido por padrão. Acabamos de considerar brevemente os principais tipos de páginas dos arquivos de dados do InterBase e sua função. Agora podemos passar para um nível estrutural mais alto.
ODS
ODS é a abreviação de On-Disk Structure, ou seja, a estrutura de dados do banco de dados InterBase no disco. ODS define como os dados dentro dos arquivos do banco de dados são organizados. A definição das principais constantes e estruturas de dados para implementar a estrutura On-Disk está no arquivo ods.h do conjunto de códigos-fonte do InterBase. O ODS foi sendo alterado durante o processo de desenvolvimento do InterBase, e ao trabalhar com um banco de dados concreto, o servidor descobre o número da versão do ODS para saber com o que está lidando. O arquivo ods.h nos apresenta as seguintes versões da estrutura On-Disk:
-
ODS 5 foi usado pelo InterBase 3.3 e não é suportado pelas versões acima
-
ODS 6 e ODS 7 nunca foram lançados
-
ODS 8 é usado pelo InterBase 4.0
-
ODS 9 é usado pelo InterBase 4.5 e acima
-
ODS 10 foi lançado com o InterBase 6
-
ODS 11 foi lançado com o InterBase 7.0
Além das versões principais do ODS, existem versões secundárias que dependem da versão concreta do servidor de banco de dados que as criou. Os números principais da versão são escritos na parte inteira do número, indicando a versão, e os secundários - na parte fracionária. Por exemplo, a versão 4.0 do servidor cria bancos de dados com ODS 8.0 e o InterBase 4.2 - 8.2. A transição entre versões secundárias de baixo para cima é executada automaticamente. Por exemplo, basta abrir um banco com ODS 8.0, criado pelo servidor 4.0, com o InterBase 5.6, e o ODS deste banco de dados terá a versão 8.2. A transição entre versões principais do banco de dados é executada apenas através de backup do banco de dados, usando uma versão antiga, e restauração, usando uma nova versão do servidor. O processo de transição entre versões é descrito em detalhes no capítulo 1.4 «Migração».
O ponto importante na implementação do suporte a ODS para versões do InterBase 4.x e 5.x é a compatibilidade retroativa dos servidores InterBase 4.x e 5.x com a versão uma unidade menor que a implementação de um servidor concreto. O InterBase suporta vários ODS possíveis e, de acordo com sua versão de ODS ao conectar-se a um banco de dados concreto, escolhe o suporte da implementação de ODS necessária. O mecanismo de decisão sobre qual implementação de suporte a ODS escolher em um caso concreto é chamado de Y-Valve ((c) de Steve Trenton).
Simplificando, um banco de dados com ODS 8.x, correspondente ao InterBase 4.0, pode ser aberto no InterBase 5.x.
A tabela completa de compatibilidade de ODS é mostrada abaixo:
| Versão do InterBase | ODS principal | ODS secundário |
| 4.0/4.1 | 8.0 | … |
| 4.2 | 8.2 | 8.2 |
| 5.0/5.1 | 9.0 | 8.2 |
| 5.5 | 9.1 | 8.2 |
| 5.6 | 9.1 | 8.2 |
| 6.0 | 10.0 | 9.0/9.1 |
| 7.0 | 11.0 | 10.0 |
| 7.1 | 11.1 | 10.0 |
O ODS tem compatibilidade descendente. Em outras palavras, um servidor com versão mais alta e todas as suas ferramentas conseguirão trabalhar com um banco de dados criado por versões anteriores do servidor, mas não o contrário. Se você tentar abrir um banco de dados criado na versão 6 do InterBase com o InterBase 5.x, receberá uma mensagem de erro «Unsupported On-disk structure: Found ODS 10, supported ODS 9».
Descrição da transição entre versões de baixo para cima e vice-versa, consulte o capítulo «Migração».
O ODS é muito importante para questões relacionadas a backup e extração de banco de dados, bem como restauração de bancos de dados danificados. As ferramentas de backup gbak e restauração gfix monitoram a versão do ODS e simplesmente não funcionarão se a versão do ODS do banco de dados que devem atender for maior que a versão implementada nelas. Isso significa que o gbak da versão 4.x não conseguirá criar um backup do banco de dados se ele foi criado pelo servidor 5.x, embora seja fácil o contrário.
Uma ponte entre a estrutura física e lógica do banco de dados
Consideramos a estrutura física dos arquivos do banco de dados de maneira geral. Agora temos que passar para a estrutura lógica do banco de dados. Vamos fazer uma ponte entre os níveis físico e lógico de representação da informação em um banco de dados para que não haja delimitação nos conceitos e nem lacunas no material. Tudo o que é armazenado nas diferentes páginas do banco de dados deve ser organizado de alguma forma na memória do computador; os dados do arquivo do banco de dados devem ser convertidos em um conjunto de objetos e variáveis internos do servidor. Esse conjunto é chamado de imagem interna do banco de dados, de acordo com a terminologia de Ann Harrison [1.. Então, vamos tentar considerar o processo de criação da imagem interna do banco de dados.
-
O servidor lê 1024 bytes do início do arquivo e, se for realmente um arquivo de banco de dados InterBase, define o tamanho da página dessa base e relê a página de cabeçalho inteira.
-
Do cabeçalho, o servidor de páginas extrai o número da página de ponteiros que armazena referências às páginas de dados, definindo a tabela RDB$Pages.
-
O servidor vai até essa página de ponteiros e começa a ler as informações das páginas de dados apontadas. Ele preenche a primeira tabela RDB$Pages com dados. Essa tabela é uma espécie de ponte entre objetos físicos - páginas de arquivos de banco de dados - e lógicos - tabelas. A estrutura da RDB$Pages, como a de outras tabelas de sistema, é estritamente fixa no InterBase.
-
Tendo recebido dados sobre a alocação de páginas por relações (relações - na verdade, é o mesmo que tabelas comuns, e podemos substituir mentalmente esses conceitos para simplificar), o InterBase começa a formar estruturas de dados: primeiro as tabelas de sistema, restrições e índices, depois os objetos de usuário.
-
Após a inicialização dos metadados de sistema e de usuário (tabelas, restrições, índices e outros objetos de banco de dados), o InterBase retorna o handle desse banco de dados ao usuário que pediu para abri-lo. Em essência, o handle é um identificador que mostra ao InterBase com qual banco de dados trabalhar, porque vários usuários podem trabalhar ao mesmo tempo, o que significa que vários bancos de dados podem estar abertos.
-
Após essas operações, o banco de dados é considerado aberto e o servidor está pronto para executar consultas do usuário. Agora que uma certa ponte foi criada, conectando a estrutura física e lógica do banco de dados, podemos começar a estudar as peculiaridades da estrutura lógica.
Estrutura lógica do banco de dados InterBase
A estrutura lógica é um conceito bastante vago; portanto, tentaremos dominar as ideias-chave gradualmente, esperando que depois fiquem intuitivamente claras. A primeira coisa que consideraremos relacionada à estrutura lógica do banco de dados são as tabelas de sistema e seu conteúdo. As tabelas de sistema descrevem o sistema, bem como os metadados do usuário. De modo geral, o termo «metadados» significa «dados que descrevem um conjunto de dados». O prefixo «meta» significa: «descreve um conjunto». Por exemplo, metalinguagem é uma linguagem que descreve um conjunto de linguagens. Os metadados descrevem os dados do usuário, ou seja, tabelas, gatilhos, visões, procedimentos armazenados e assim por diante - tudo o que implementa as regras de armazenamento e processamento das informações, por causa das quais esse banco de dados concreto é criado.
É bastante curioso saber pela primeira vez que todos os metadados - tabelas de usuário, gatilhos, visões, bem como todos os objetos de sistema - são armazenados nas mesmas tabelas, das quais você pode ler e escrever dados por consultas SQL comuns. Essas tabelas diferem «visualmente» apenas pelo fato de que seus nomes começam com RDB$. Esses 4 símbolos são reservados para os nomes de objetos de sistema. Nenhuma tabela de usuário, coluna ou outro objeto tem o direito de ter nomes começando com esses símbolos. Formalmente, você pode criar uma tabela cujo nome comece com os símbolos reservados, mas a documentação do InterBase não recomenda fazer isso.
Surge uma pergunta: se os dados sobre a estrutura do banco de dados estão armazenados nas mesmas tabelas que os dados do usuário, onde estão as informações sobre as tabelas que descrevem tabelas? Um exemplo clássico do problema da «galinha e do ovo» - como um poderia aparecer antes do outro, se são interdependentes? A resposta é que as tabelas de sistema em seu estado primitivo são fixas nos códigos iniciais do InterBase e são abertas automaticamente ao criar um banco de dados em uma ordem definida. Já falamos sobre a tabela RDB$Pages, que compara páginas físicas em arquivos de banco de dados com objetos definidos desse banco. A estrutura dessa tabela é fornecida abaixo:
Tabela 5. Tabela de sistema RDB$Pages
| Nome da coluna | Tipo de dado | Descrição |
| RDB$PAGE_NUMBER | INTEGER | Número da página física |
| RDB$RELATION_ID | SMALLINT | Identificador da tabela para a qual a página é alocada |
| RDB$PAGE_SEQUENCE | INTEGER | Número desta página |
| RDB$PAGE_TYPE | SMALLINT | Tipo de página - veja a tabela 3 |
Cada página de dados está relacionada a uma determinada tabela. Essa relação é suportada pelo campo RDB$RELATION_ID, onde uma referência à tabela é armazenada. Como descrito acima, no processo de construção da imagem interna do banco de dados, o servidor cria essa tabela e a preenche com dados de acordo com um algoritmo definido. Para ser preciso, no momento da construção da imagem interna do banco de dados, a RDB$Pages não é uma tabela; é apenas um arquivo de dados de formato definido, conhecido pelo InterBase. De acordo com um algoritmo fixo, o servidor lê dados desse arquivo e cria uma tabela - RDB$Relations - que é importante para todo o banco de dados. Essa tabela descreve todas as tabelas do banco. Se executarmos a consulta SQL:
SELECT * from RDB$Relations
para descobrir a quais tabelas a RDB$Relations se refere, veremos que ela contém a RDB$Pages e a si mesma. É óbvio que, nesse caso, o servidor está sendo um pouco astuto, substituindo essas e outras tabelas de sistema na RDB$Relations com efeito retroativo, legalizando-as dessa forma. O servidor as registra como tabelas «normais», onde pode adicionar ou excluir registros. Em outras palavras, fornece uma interface SQL padrão para trabalhar com metadados.
E uma pergunta bastante razoável pode surgir - por que os desenvolvedores do InterBase ajustariam seus dados de sistema de acordo com a interface do usuário? Veja, mecanismos internos de acesso e operações de leitura seriam mais rápidos. Claro, há um grande sentido em fornecer um mecanismo universal de trabalho com tabelas, descrevendo metadados.
O problema é que a estrutura lógica do banco de dados consiste não apenas em tabelas, mas também em outros objetos. Existem os seguintes objetos no InterBase:
-
Tabela
-
Visão
-
Gatilho
-
Campo_calculado
-
Validação
-
Procedimento
-
Índice_de_expressão
-
Exceção
-
Usuário
-
Campo
-
Índice
-
Função Definida pelo Usuário (UDF)
Ainda não sabemos ao certo a função de alguns objetos, mas sabemos com certeza que todos eles devem ser descritos e armazenados em alguma visão, conveniente para o usuário e para o acesso a partir do núcleo do InterBase. O melhor de tudo seria salvar esses objetos em tabelas de sistema. Sua adição e modificação são realizadas por consultas SQL. Uma solução inteligente, não é? A implementação do servidor é completamente separada de um banco de dados concreto - todas as interconexões são descritas por SQL e suas extensões - a linguagem de procedimentos armazenados e gatilhos.
Assim, todos os objetos do servidor são armazenados em tabelas. Cada tipo de objeto tem a tabela que descreve todas as instâncias descritas no banco de dados. Por exemplo, para gatilhos existe uma tabela RDB$Triggers, para procedimentos armazenados - RDB$Procedures, visões são descritas na tabela RDB$Relations.
Vamos considerar em detalhes a estrutura da última tabela, que descreve todas as tabelas e visões em um banco de dados. A estrutura da tabela RDB$RELATIONS é retirada do Language Reference for InterBase 6 e é fornecida abaixo na tabela 6.
Tabela 6. Tabela de sistema RDB$Relations
| Nome da coluna | Tipo de dado | Comprimento | Descrição |
| RDB$VIEW_BLR | BLOB | 80 | BLR: para visões, contém o BLR (Binary Language Representation) da consulta, que o InterBase executa toda vez que se aplica à visão. |
| RDB$VIEW_SOURCE | BLOB | 80 | Texto: para visões, contém o código da consulta SQL que implementa essa visão. |
| RDB$_DESCRIPTION | BLOB | 80 | Descrição do usuário da tabela ou visão |
| RDB$RELATION_ID | SMALLINT | Contém o identificador interno da tabela/visão | |
| RDB$SYSTEM_FLAG | SMALLINT | Define o tipo de tabela: dados do usuário - 0; Informação de sistema > 0. | |
| RDB$DBKEY_LENGTH | SMALLINT | Comprimento db$key | |
| RDB$FORMAT | SMALLINT | Reservado para uso interno do InterBase. Contém o contador de modificação de metadados para a tabela fornecida. | |
| RDB$FIELD_ID | SMALLINT | O número de campos na tabela. | |
| RDB$RELATION_NAME | CHAR | 31 | Nome único da tabela. |
Na descrição dessa tabela de sistema, vemos uma abreviação BLR. Para entender o que é, faremos uma digressão ao SQL. Como se sabe, visões, gatilhos e procedimentos armazenados são um código escrito em uma extensão da linguagem SQL (para cada servidor de SGBD existem suas próprias extensões). É próximo da linguagem humana, o que permite fazer consultas facilmente. Mas o InterBase, obviamente, traduz isso para algo mais «de máquina» - ou seja, para BLR (Binary Language Representation). Qualquer consulta, visão, gatilho, procedimento armazenado é sempre traduzido para BLR e depois transmitido ao núcleo do InterBase para execução.
BLR
BLR é uma linguagem especial, usada como um elo intermediário entre o código SQL que um programador escreve e o código de máquina que o servidor aceita. Ninguém escreve diretamente em BLR - seria bastante difícil porque, para a maior velocidade possível de execução, nessa linguagem é usado o chamado registro polonês reverso. Aqui está um pequeno exemplo:
blr_begin,
blr\_assignment,
blr\_field, 0, 7, 'D','A','T','E','I','Z','M',
blr\_variable, 1,0,
blr\_assignment,
blr\_field, 0, 4, 'R','A','T','E',
blr\_variable, 0,0,
blr\_block,
O BLR para suas consultas, procedimentos, gatilhos e outros gatilhos é formado pelo pré-processador especial que faz parte do núcleo do servidor. Como mostrado na tabela 7, para visões, seu texto (inicial) da visão, bem como a visão compilada, ou seja, o BLR, é armazenado. Ao se referir a qualquer objeto que tenha BLR, o servidor executa o código binário do objeto, e não interpreta o texto inicial desses objetos toda vez, o que permite acelerar a execução de consultas complicadas.
Hierarquia de objetos no InterBase
Para ter uma ideia clara do que representam os objetos de banco de dados, tentaremos fazer uma hierarquia dos objetos de banco de dados de acordo com o princípio «quem contém o quê». As páginas físicas dos arquivos de banco de dados são as primeiras que devem ser incluídas em nossa hierarquia como o nível mais baixo de organização de dados. Depois, as tabelas vêm como objetos básicos, descrevendo todos os outros tipos de objetos. As tabelas descrevem procedimentos armazenados, gatilhos, campos calculados, validações, índices de expressão, exceções e assim por diante. Preste atenção - apenas descrevem! As tabelas contêm apenas declarações e definições desses objetos, e os objetos são implementados por meio de BLR. Portanto, podemos representar as tabelas na forma de um quadro, sustentando todos os outros objetos de banco de dados. O BLR estará na base do quadro como a camada de implementação, depois gatilhos, procedimentos armazenados, índices de expressão e visões.
Para acalmar os especialistas em estrutura interna do InterBase, que podem objetar que o BLR de muitos objetos (como visões) é armazenado em tabelas de sistema, comentaremos que essa atitude é bastante difícil de expressar em uma imagem e, para simplificação, a descartamos. O esquema não tem o objetivo de recriar as interdependências dos objetos de banco de dados com precisão absoluta; apenas ilustra sua estreita interconexão.
O fato de que esses tipos de objetos estão diretamente conectados com o BLR, que os implementa sem qualquer lógica intermediária, os une. As exceções devem ser alocadas separadamente - elas representam tipos especiais de erros, definidos pelo usuário. As exceções são processadas no nível do núcleo do InterBase e, portanto, não têm BLR. Tipos de restrições como checks são alocados acima dos gatilhos porque, na realidade, os gatilhos implementam a lógica das restrições e verificações.
Uma hierarquia de objetos da estrutura lógica e física do banco de dados é pintada na figura 2.]
Figura 10. Objetos da estrutura lógica do banco de dados InterBase
Claro, esse esquema descreve a estrutura lógica e as interconexões dos objetos no banco de dados apenas aproximadamente e dá uma ideia geral dele. Qualquer pessoa que queira estudar a estrutura dos metadados do banco de dados InterBase pode executar a reengenharia das tabelas de sistema do banco de dados e considerar todas as interconexões entre seus objetos, bem como recorrer à documentação e aos códigos primários do InterBase. Esta tabela mostra apenas os principais objetos de banco de dados. Vamos descrever brevemente as principais funções que esses objetos desempenham no banco de dados.
Tabelas - o principal objeto, contendo dados de usuário e do sistema. Uma tabela tem um nome exclusivo e contém um conjunto de campos nomeados. Um usuário pode colocar dados, extrair e modificar dados em tabelas. Podemos dizer que uma tabela é semelhante a tabelas de papel comuns desenhadas à mão.
Gatilhos (Triggers) - partes executáveis do código, usadas para implementar ações adicionais no momento das operações de dados. Os gatilhos são executados antes ou depois de operações de inserção, modificação ou exclusão, e permitem realizar a substituição de valores em registros recém-criados e muitas outras coisas.
Um procedimento armazenado é uma ferramenta poderosa para implementar lógica de negócios no nível do banco de dados. Sendo executado no nível do servidor, ele funciona muito rápido e permite executar um conjunto de operações sobre conjuntos de dados. Os procedimentos armazenados do InterBase retornam conjuntos de dados SQL padrão, sobre os quais todas as operações SQL, incluindo unificação com outras tabelas, podem ser aplicadas.
As views são consultas SQL compiladas, executadas no servidor. As views permitem organizar conjuntos de dados, transferindo parte da lógica de negócios para o servidor.
Validações são restrições definidas sobre os valores de campos na tabela. Por exemplo, podemos indicar que determinado campo aceitará apenas valores positivos. As restrições sobre valores de campos são implementadas por gatilhos e permitem controlar efetivamente a integridade referencial no nível do banco de dados. Geralmente, as restrições são usadas para impedir que valores incorretos sejam colocados em uma tabela.
Usuários - o InterBase nos permite ter vários usuários para trabalhar com o banco de dados e distribuir os direitos de acesso a diferentes objetos do banco de dados entre eles. Assim, podemos controlar as permissões para estas ou aquelas operações no banco de dados.
Funções Definidas pelo Usuário (UDF) - funções definidas pelo usuário. É uma das capacidades mais poderosas do InterBase, permitindo-nos estender a interface SQL padrão com nossas próprias funções. Por exemplo, funções de trabalho com strings, como UPPER (definindo todos os caracteres em maiúsculas), são implementadas na biblioteca UDF padrão, incluída no conjunto do InterBase. Graças à possibilidade de criar UDFs próprias, os desenvolvedores podem estender a funcionalidade do InterBase praticamente com qualquer função. Podemos usar qualquer ambiente de programação que permita criar bibliotecas dinâmicas (Visual C++, C++ Builder, Delphi etc.) para criar UDFs.
Conclusão
Neste capítulo, consideramos pela primeira vez as questões sobre a implementação do armazenamento e processamento de dados dentro do banco de dados InterBase. Infelizmente, não podemos fazer uma revisão breve deste tema sem recorrer a um grande número de termos e analogias imprecisas. Se descrevêssemos a estrutura física e lógica do banco de dados em mais detalhes, teríamos que nos referir aos códigos primários do InterBase de qualquer forma, mas isso seria outro livro.
No entanto, achamos que seria útil para todo programador se familiarizar com o conteúdo do produto que ele usa todos os dias.
Bibliografia
-
«The On-Disk Structure of InterBase» por Ann.W.Harrison
-
«Space Management in InterBase» por Ann W.Harrison
-
«Structure of a Data Page» por Paul Beach (Com agradecimentos a Dave Schnepper e Deej Bredenberg)