Estructura física de la base de datos (InterBase y Firebird)
Alexey Kovyazin, Sergey Vostrikov, última actualización 05-junio-2004
Estructura física de la base de datos
¿Por qué tenemos que estudiar la estructura física de la base de datos InterBase?
Normalmente, cuando hablamos de la estructura física de la base de datos InterBase, nos referimos a que representa los datos desde el punto de vista de la organización de datos de bajo nivel, hasta el nivel de bytes. Muchos programadores que desarrollan aplicaciones usando lenguajes de alto nivel descuidan el estudio de los detalles de bajo nivel. Sin embargo, conocer los principios principales de la organización de datos dentro de la base de datos es una clave para el diseño efectivo de aplicaciones de bases de datos. Por lo tanto, haremos una excursión al interior de la organización de la base de datos InterBase y descubriremos cómo está organizada.
Entonces, ¿para qué sirve un sistema de gestión de bases de datos (SGBD)? Obviamente para almacenar y controlar los datos. Suena banal, pero vale la pena pensarlo. Un usuario introduce los datos en el SGBD, que de alguna manera traduce estos datos a formatos internos que le resultan comprensibles. Puedes imaginar “0 y 1” si las palabras “formato interno de datos” causan algunas dificultades con las asociaciones. El SGBD almacena estos datos y, en el momento de la primera solicitud, debe extraerlos de su formato, convertirlos a una vista adecuada y entregarlos al usuario.
El tema de este capítulo es cómo el SGBD almacena sus datos, en qué vista y cómo están organizados en el nivel más bajo. Intentaremos explicarte cómo a partir de bits y bytes, que están en el disco duro, obtenemos datos valiosos.
Archivos de base de datos InterBase
Normalmente, cuando hablamos de una base de datos, nos referimos al SGBD en sí mismo y a la información del usuario, e incluso a los programas de los clientes que trabajan con los datos. En este capítulo, consideraremos una base de datos como archivos de base de datos.
La base de datos InterBase representa uno o varios archivos que contienen información sobre todo lo relacionado con esta base. La información sobre los usuarios es una excepción porque los usuarios se definen a nivel de todo el servidor y se almacenan por separado, en la base de datos de seguridad admin.ib (era ISC4.GDB en versiones anteriores a la 7).
Consejo: Consulta el capítulo “Seguridad del servidor y la base de datos” para estudiar más sobre los principios de seguridad de InterBase.
Entonces, toda la información sobre la base de datos se almacena dentro de estos archivos: los datos en sí, índices, disparadores, procedimientos almacenados, etc.
La base de datos InterBase para un proyecto promedio representa un solo archivo porque las versiones modernas de InterBase pueden usar IO de 64 bits para operar con el archivo de datos y esto te da la capacidad de tener un archivo de datos de hasta 64 GB. Las versiones anteriores de InterBase tenían la restricción de 4 gigabytes por cada archivo de base de datos (hasta 64 Tbytes para toda la base de datos). Como podemos suponer, 64 gigabytes son suficientes para almacenar información de casi cualquier aplicación de base de datos. Pero si es necesario, podemos dividir una base de datos en varios archivos. Por cierto, existen bases de datos InterBase de cientos de gigabytes de tamaño.
IBSurgeon - una guía a través de la base de datos InterBase
Tenemos que conocer en detalle la estructura de los archivos de la base de datos InterBase. Y por lo tanto, es deseable tener alguna herramienta conveniente que permita trabajar directamente con los archivos de la base de datos, no por medio del núcleo del servidor InterBase. La forma más fácil es usar un visor hexadecimal ordinario e intentar entender la estructura de los archivos de la base de datos considerando su representación HEX. Sería un trabajo bastante tedioso.
Pero afortunadamente, existe una herramienta para el trabajo directo con bases de datos InterBase. Es IBSurgeon Editor, una herramienta para el trabajo directo de bajo nivel con bases de datos InterBase, que se puede usar para estudiar la estructura interna de las bases de datos InterBase y diagnosticar bases de datos corruptas para restaurarlas. Para más detalles, consulta el apéndice “Herramientas para administradores y desarrolladores de InterBase”.
IBSurgeon utiliza su propio mecanismo alternativo de acceso a la base de datos que permite abrir y revisar bases de datos en cualquier estado, incluyendo las gravemente corruptas que no pueden ser abiertas por el núcleo del servidor InterBase/FireBird/Yaffil.
Usaremos IBSurgeon para ilustrar la estructura interna de la base de datos.
Archivos *.IB/*.FDB desde dentro
IB es una extensión recomendada para archivos de base de datos InterBase, y FDB para Firebird (anteriormente era GDB). Lo primero que tenemos que decir sobre la estructura del archivo IB es que representa un conjunto de páginas de tamaño estrictamente definido. El tamaño del archivo de la base de datos es divisible por el tamaño de página, que no se modifica para todos los archivos de esta base de datos. Diferentes versiones de InterBase soportan diferentes tamaños de página, como se muestra en la tabla 1. El tamaño de página se establece al crear una base de datos y no se puede cambiar durante su ciclo de vida. En otras palabras, solo podemos cambiar el tamaño de página al restaurar una base de datos desde una copia de seguridad.
Tabla 1. Tamaño de página soportado por diferentes versiones de InterBase
| Versión de InterBase | Tamaño de página, bytes | ||||
| 1024 | 2048 | 4096 | 8192 | 16384 | |
| InterBase 4.0 | * | * | * | * | |
| InterBase 5.x | * | * | * | * | |
| InterBase 6.x-7.x | * | * | * | * |
La lectura y escritura de datos en una base de datos se ejecuta página por página; muchas características importantes del servidor y de la base de datos, como el tamaño de la caché de la base de datos, dependen del tamaño de página y se cuentan en “páginas”.
Abramos cualquier base de datos InterBase con IBSurgeon. Es suficiente hacer doble clic en el archivo de la base de datos. La imagen 1 muestra una lista de páginas que aparece después de que IBSurgeon ha abierto la base de datos:

Imagen 1. Una lista de páginas de la base de datos
Las páginas pueden ser de diferentes tipos, cada una de las cuales sirve para un propósito determinado. Las interdependencias de diferentes tipos se representan condicionalmente en la imagen 2. La imagen 2 muestra esquemáticamente una asignación de páginas en el archivo de la base de datos, de izquierda a derecha, de arriba a abajo, si se cuenta desde el principio del archivo. Las páginas del mismo tipo no van estrictamente una tras otra; pueden estar fácilmente mezcladas, asignadas en un archivo en el orden en que fueron creadas por el servidor al extender o crear bases de datos.

Imagen 2. Interdependencias entre diferentes tipos de páginas en la base de datos InterBase
Debes haber notado que algunos tipos de páginas no tienen referencias a otros tipos de páginas. Sin embargo, no hay contradicción aquí; el asunto es que estos tipos de páginas están vinculados y se usan en otro nivel estructural. Pueden estar vinculados a la tabla RDB$PAGES y otras tablas del sistema (esta tabla y otros objetos del sistema los consideraremos más adelante, en el capítulo “Estructura lógica de la base de datos”). En la imagen 2 solo podemos ver referencias explícitas entre las páginas a nivel físico.
Consideremos en detalle qué tipos de páginas hay en la base de datos InterBase. En el archivo ods.h del conjunto de códigos primarios de InterBase hay información sobre todos los tipos posibles de páginas. Nos referiremos frecuentemente a este archivo para recibir los datos no solo sobre ODS sino también sobre muchas otras cosas fundamentales del núcleo de InterBase en la fuente original. Se declaran 11 tipos de páginas en total, pero solo 9 de ellos merecen ser explicados (podemos verlo claramente en la tabla 2). Los tipos de página con identificadores 0 y 1 no están definidos o no se usan.
Tabla 3. Tipos de página en FB
| La definición en ods.h | Identificador de tipo de página | Descripciones de página |
| pag_undefined | 0 | No definido - Si una página tiene este tipo de página, probablemente esté libre |
| pag_header | 1 | Página de cabecera de la base de datos |
| pag_pages | 2 | Página de inventario de páginas (o página de inventario de espacio - SIP) |
| pag_transactions | 3 | Página de inventario de transacciones (TIP) |
| pag_pointer | 4 | Página de puntero |
| pag_data | 5 | Página de datos |
| pag_root | 6 | Página raíz de índice |
| pag_index | 7 | Página de índice (árbol B) |
| pag_blob | 8 | Página de datos Blob |
| pag_ids | 9 | Gen-ids |
| pag_log | 10 | Información de registro de escritura anticipada |
Cada página tiene su cabecera, que contiene información sobre el tipo de página y un número de la siguiente página del mismo tipo. Podemos obtener la lista completa de parámetros que contiene cada cabecera de página si consideramos la estructura pag en el archivo de definiciones ods.h.
/\* Cabecera básica de página */
typedef struct pag {
SCHAR pag_type; /*identificador de tipo de página*/
SCHAR pag_flags; /*banderas de página*/
USHORT pag_checksum; /*suma de verificación de página: es igual a 12345 después de la versión 5.0 */
ULONG pag_generation; /*generación de página */
ULONG pag_seqno; /* número de secuencia WAL de la última actualización - obsoleto*/
ULONG pag_offset; /* desplazamiento WAL de la última actualización - obsoleto*/
} *PAG;
Tipos de página y su uso
Consideremos cada tipo de página en detalle y conozcamos su función y la información que contienen. Empezaremos paso a paso, desde la primera página.
Cualquier operación con una base de datos comienza con la lectura de la página de cabecera de la base de datos (o página de cabecera). La página de cabecera de la base de datos va primero en todos los archivos de bases de datos. En consecuencia, se representa primero en la imagen 2 (si imaginamos que la imagen representa una extensión del archivo de la base de datos de izquierda a derecha, de arriba a abajo).
Una página de cabecera contiene información sobre la base de datos en su conjunto. En la imagen 3 se muestra una página de datos de la manera en que IBSurgeon nos la presenta:

Imagen 3. Página de cabecera de la base de datos.
Puedes hacerte una idea del contenido de la página de cabecera al obtener las estadísticas de la base de datos. Para esto, puedes usar la utilidad de línea de comandos gstat u otra herramienta más conveniente para la administración de InterBase de la lista en la aplicación “Herramientas para administradores y desarrolladores de InterBase”. Para más detalles sobre el proceso de recibir estadísticas y la descripción de la página de cabecera, consulta el capítulo “Estadísticas”.
Cabe señalar que la página de cabecera contiene información importante como el tamaño de página, el número de versión de ODS (información sobre esto la encontrarás más adelante), los datos de creación de la base de datos, información sobre transacciones y un conjunto de información diversa. Por ejemplo, el ID de implementación almacena información sobre bajo qué sistema operativo se creó esta base de datos.
Al conectar una base de datos, el servidor InterBase lee los primeros 1024 bytes de información desde el principio del archivo y define según los valores leídos si el archivo señalado en la línea de conexión es una base de datos InterBase o no. Luego el servidor lee el número de versión de ODS de una página de cabecera y el tamaño de página en esta base de datos y, si la versión de ODS es compatible con la implementación del servidor, relee toda la página de cabecera usando el tamaño de página adecuado recibido de los primeros 1024 bytes. Después, el resto de parámetros importantes de la base de datos, como el modo de lectura-escritura, el dialecto de la base de datos, etc., se leen de la página de cabecera.
En la página de cabecera hay una referencia a la primera de las páginas de puntero, que almacenan referencias a páginas de datos que contienen metadatos: la tabla RDB$Pages (ver más adelante en el capítulo “Estructura lógica de la base de datos InterBase”). En la imagen 2 esta referencia se ilustra con una flecha con la inscripción “Número de la 1ª página de puntero en la base de datos”. El servidor lee el número de la 1ª página de puntero de la página de cabecera y continúa hacia ella. La página de puntero consiste en un array ordenado de números de páginas de datos que conforman una tabla determinada (una tabla se considera como un objeto SQL, descrito por la estructura lógica de la base de datos). Ahora puedes ver cómo IBSurgeon interpreta la página de puntero (mira la imagen 4):

Imagen 4. Página de puntero de la base de datos InterBase
La página contiene un vector de páginas de datos; estos datos conforman una tabla determinada en una base de datos. Este vector representa un array de punteros correspondientes a los números de páginas de datos en el archivo. El servidor lee un número de 4 bytes de la página de datos y continúa hacia la página de datos necesaria. Cuando continúa hacia la 1ª página de datos de RDB$Pages, el servidor comienza a construir la representación interna de la base de datos que se usará más adelante para todas las operaciones con la base de datos. RDB$Pages almacena referencias no solo a páginas de datos que contienen información sobre la base de datos, sino también al resto de páginas que juegan un papel en proporcionar el funcionamiento de la base de datos.
Mencionamos a menudo esta tabla que, estrictamente hablando, pertenece a la estructura lógica de la base de datos. Sin embargo, todo está interconectado, por lo tanto no podemos describir algo sin referirnos a otra cosa.
Uno de los tipos de página importantes es la página de inventario de transacciones (TIP). Estas páginas, como todas las páginas, constan de un encabezado y una parte principal que representa una matriz de secuencias de 2 bytes. Las secuencias describen el estado de las transacciones en una base de datos (para más detalles sobre transacciones, consulte el capítulo «Transacciones»).
Tabla 4. Posibles estados de transacción en TIP
| Valor de la secuencia en PIP | Significado |
| 0 | La transacción no ha comenzado, está activa o se ha perdido sin commit o rollback |
| 1 | La transacción ejecutó Commit |
| 2 | La transacción ejecutó Rollback |
| 3 | Transacción en limbo (para 2PC) |
Cada versión de registro tiene su identificador de transacción, lo que permite que las transacciones ejecutadas simultáneamente «conozcan» el estado de las demás y resuelvan los conflictos durante el trabajo multiusuario (consulte el capítulo «Arquitectura multigeneracional de InterBase» para obtener más información sobre las versiones de registros y otros elementos).
La página de encabezado de la base de datos, las páginas de punteros y la TIP se refieren a tipos de páginas de «mantenimiento», utilizadas únicamente por el servidor. Los usuarios de InterBase nunca obtienen explícitamente la información que contienen. Las páginas que almacenan información sobre la asignación de páginas (generalmente mencionadas como Páginas de Inventario de Páginas (PIP) o Páginas de Inventario de Espacio (SIP)) también se refieren al tipo de página de mantenimiento. Estas páginas se ubican comenzando desde la segunda, es decir, la primera PIP va justo después de la página de encabezado, y aparecen en una base de datos en intervalos fijos de páginas de otros tipos. El tamaño de estos intervalos indica cada cuántas páginas de otros tipos aparece la PIP, y depende del tamaño de página establecido para esta base de datos. Las Páginas de Inventario de Páginas no se contabilizan en las páginas de punteros y no se señalan en RDB$Pages. La integridad de estas páginas es vital para el trabajo exitoso de toda la base, porque el contenido de la PIP describe el estado de todas las demás páginas de la base de datos. Cada página de la base de datos puede tener 3 estados: no asignada, asignada con espacio, asignada y llena. Cuando hay necesidad de espacio adicional para nuevos datos, el servidor verifica la PIP con el propósito de tener páginas no asignadas. Si existe tal página, el servidor modifica su estado a asignada con espacio. Si no hay páginas no asignadas, la base de datos se expande: se agrega una nueva página de datos.
Un ejemplo de la página de datos en IBSurgeon y los datos que contiene se muestra en la imagen 5.

Imagen 5. Página de Inventario de Páginas
Tan pronto como se asigna la página, InterBase escribe su estado en la SIP y luego escribe la página en sí. Después de esto, tenemos que agregar esta página recién formada a algún gran número de páginas, por ejemplo, a las páginas de datos de una tabla. Para ello, debemos escribir la referencia a esta nueva página en la última página de este gran número de páginas - por ejemplo, en la última página de datos de una tabla. Si el servidor interrumpió su trabajo justo después de escribir en la SIP, pero sin escribir la referencia en las páginas que se refieren a la página recién asignada, entonces esta página se convierte en huérfana. Una página huérfana está físicamente creada, reservada en la SIP, pero no hay referencias a ella desde otras páginas, lo que significa que el servidor no podrá encontrarla y escribir datos en el disco. La página huérfana está marcada con el cuadrado rojo en la imagen 2. Las páginas huérfanas surgen principalmente como resultado de un corte de energía inesperado del servidor y son «curadas» por la herramienta especial para reparación de bases de datos gfix (o por FirstAID) (o por IBSurFirstAID).
Antes de considerar las páginas de datos, debemos mencionar tipos de páginas importantes: páginas de generadores y páginas de índices. Las páginas de generadores representan una matriz de números de 4 bytes, que muestran los estados de los generadores. En realidad, el generador es un contador ordinario.
En la imagen 6 puede ver una página de generador. Preste atención a que aunque IBSurgeon muestra los nombres de los generadores, no es que estos nombres se almacenen en las páginas de generadores. Esto se hace así para la conveniencia del usuario que estudia la base de datos. En realidad, los nombres de los generadores se almacenan en la tabla del sistema RDB$Generators.

Imagen 6. Página de generador (g en-ids )
Como ve en este ejemplo, la base de datos contiene generadores del sistema, que comienzan con el prefijo RDB$, y generadores definidos por el usuario. Si desea conocer la función y el uso de los generadores al desarrollar aplicaciones de bases de datos InterBase, consulte el capítulo «Tablas. Claves primarias y generadores». Las páginas de generadores se contabilizan junto con otras páginas en la tabla RDB$Pages.
Cada tabla tiene al menos una página raíz de índice, sin importar si tiene índices o no. Esta página contiene punteros a las páginas de índice de una tabla específica. Podemos decir que la página raíz de índice tiene la misma importancia para las páginas de índice que la página de punteros para las páginas de datos. Por lo tanto, IBSurgeon la representa de manera similar. Un ejemplo de página raíz de índice se muestra en la imagen 7.

Imagen 7. Página raíz de índice
La página raíz de índice contiene una lista de páginas donde se almacenan los valores de índice, así como información del índice: selectividad del índice y diferentes indicadores. Para más detalles sobre los índices, su papel y uso en bases de datos InterBase, consulte el capítulo «Índices».
Las páginas de índice contienen directamente los valores de los índices, o si el nivel de índice > 0, referencias a las páginas de índice subyacentes. Aquí hay un ejemplo de página de índice (imagen 8).

Imagen 8. Página de índice (B-tree)
La página de índice almacena valores empaquetados de los datos indexados. Se utiliza un mecanismo de indexación bastante complejo, especialmente al crear índices compuestos (que incluyen varios campos).
En general, las páginas de datos y las páginas que contienen valores BLOB almacenan información del usuario. Las páginas de datos contienen registros en las tablas de usuario de la base de datos, fragmentos de registros, versiones antiguas, diferencias entre versiones, campos BLOB y demás. En cuanto a los campos BLOB, están conectados con registros en las páginas de datos y contienen datos de gran tamaño que no pueden ubicarse en la página de datos. El tipo de referencia para almacenar valores BLOB permite almacenar datos grandes.
Un ejemplo de presentación de página de datos en IBSurgeon se muestra en la imagen 9:

Imagen 9. Página de datos
El encabezado de la página de datos contiene el tipo de página, el identificador de la tabla propietaria (relationID). Los registros se almacenan en las páginas de datos desde el final de la página y se asignan más cerca del comienzo de la página a medida que se llenan.
Podemos asegurarnos de esto si observamos los índices de fila, que contienen 2 valores: el desplazamiento en la página y su longitud. Como ve al comienzo de la fila, hay registros asignados al final de la página - por ejemplo, el primer registro tiene un desplazamiento de 8156 bytes y una longitud de 34 bytes - por lo tanto, termina en 8156+34=8192 bytes - en el borde mismo de la página (en nuestro caso, el tamaño de página es de 8192 bytes). Cuando la página se llena (con datos desde arriba e índices de fila desde abajo), el servidor comienza a escribir nuevos registros y versiones de registros antiguos en nuevas páginas. Del mecanismo de llenado de página descrito anteriormente, podemos decir fácilmente por qué los especialistas de InterBase recomiendan enfáticamente usar páginas de datos de gran tamaño (4096 bytes como mínimo, mejor 8192). Si creamos una tabla en la que un registro será de un tamaño bastante grande (por ejemplo, 10 campos de VARCHAR (255)), ocuparán, se llenarán, más de 2550 bytes. Esto significa que tal registro será demasiado grande para una página de tamaño pequeño (1024 o 2048). Es obvio que la necesidad de cargar varias páginas del disco para leer un solo registro no acelerará el trabajo con su base de datos. Por lo tanto, se recomienda redefinir el tamaño de la página de datos al crear o restaurar una base de datos, porque el tamaño de 1024 bytes se establece por defecto. Acabamos de considerar brevemente los tipos principales de páginas de archivos de datos de InterBase y su función. Ahora podemos pasar a un nivel estructural más alto.
ODS
ODS es la abreviatura de On-Disk Structure, es decir, la estructura de datos de la base de datos InterBase en disco. ODS define cómo se organizan los datos dentro de los archivos de la base de datos. La definición de las constantes principales y las estructuras de datos para implementar la estructura en disco está en el archivo del conjunto de códigos fuente de InterBase ods.h. ODS fue cambiando durante el proceso de desarrollo de InterBase, y al trabajar con una base de datos concreta, el servidor averigua el número de versión de ODS para saber con qué está tratando. El archivo ods.h nos presenta las siguientes versiones de la estructura en disco:
-
ODS 5 fue utilizado por InterBase 3.3 y no es compatible con versiones superiores
-
ODS 6 y ODS 7 nunca salieron
-
ODS 8 es utilizado por InterBase 4.0
-
ODS 9 es utilizado por InterBase 4.5 y superiores
-
ODS 10 salió con InterBase 6
-
ODS 11 salió con InterBase 7.0
Además de las versiones principales de ODS, hay versiones menores que dependen de la versión concreta del servidor de base de datos que las creó. Los números principales de la versión se escriben en la parte entera del número, indicando la versión, y los menores en la parte fraccionaria. Por ejemplo, la versión del servidor 4.0 crea bases de datos que tienen ODS 8.0 e InterBase 4.2 - 8.2. La transición entre versiones menores de abajo hacia arriba se ejecuta automáticamente. Por ejemplo, es suficiente abrir una base con ODS 8.0, creada por el servidor 4.0, con InterBase 5.6, y el ODS de esta base de datos tendrá la versión 8.2. La transición entre versiones principales de la base de datos se ejecuta solo mediante una copia de seguridad de la base de datos, utilizando una versión antigua, y una restauración, utilizando una nueva versión del servidor. El proceso de transición entre versiones se describe en detalle en el capítulo 1.4 «Migración».
El momento importante en la implementación del soporte de ODS para las versiones de InterBase 4.x y 5.x es la compatibilidad hacia atrás de los servidores InterBase 4.x y 5.x con la versión una unidad menor que la implementación de un servidor concreto. InterBase soporta varios ODS posibles, y según su versión de ODS al conectarse a la base de datos concreta, elige el soporte de la implementación de ODS requerida. El mecanismo para decidir qué implementación de soporte de ODS elegir en un caso concreto se llama Y-Valve ((c) de Steve Trenton).
Dicho más fácilmente, una base de datos con ODS 8.x, que corresponde a InterBase 4.0, puede abrirse en InterBase 5.x.
La tabla completa de compatibilidad de ODS se muestra a continuación:
| Versión de InterBase | ODS principal | ODS menor |
| 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 |
ODS tiene compatibilidad descendente. En otras palabras, un servidor con una versión superior y todas sus herramientas podrán trabajar con una base de datos creada por versiones anteriores del servidor, pero no al contrario. Si intenta abrir una base de datos creada en la versión 6 de InterBase con InterBase 5.x, recibirá un mensaje de error «Unsupported On-disk structure: Found ODS 10, supported ODS 9».
La descripción de la transición entre versiones de abajo hacia arriba y viceversa se encuentra en el capítulo «Migración».
ODS es muy importante para los problemas relacionados con la copia de seguridad y la extracción de bases de datos, así como la restauración de bases de datos dañadas. Las herramientas de copia de seguridad gbak y restauración gfix vigilan la versión de ODS y simplemente no funcionarán si la versión de ODS de la base de datos a la que deben servir es mayor que la versión implementada en ellas. Esto significa que gbak de 4.x no podrá crear una copia de seguridad de la base de datos si fue creada por el servidor 5.x, aunque sí fácilmente al contrario.
Un puente entre la estructura física y lógica de la base de datos
Consideramos la estructura física de los archivos de la base de datos de manera general. Ahora tenemos que pasar a la estructura lógica de la base de datos. Hagamos un puente entre los niveles físico y lógico de la representación de la información en una base de datos para que no haya delimitación en los conceptos ni vacíos en el material. Todo lo que se almacena en diferentes páginas de la base de datos debe organizarse de alguna manera en la memoria de la computadora; los datos del archivo de la base de datos deben convertirse en un conjunto de objetos y variables dentro del servidor. Este conjunto se llama imagen interna de la base de datos según la terminología de Ann Harrison [1.. Así que intentaremos considerar el proceso de creación de la imagen interna de la base de datos.
-
El servidor lee 1024 bytes desde el inicio del archivo y, si realmente es un archivo de base de datos InterBase, define el tamaño de página de esta base y vuelve a leer toda la página de cabecera.
-
De la cabecera, el servidor de páginas extrae el número de la página de punteros que almacena referencias a las páginas de datos, definiendo la tabla RDB$Pages.
-
El servidor continúa hacia esta página de punteros y comienza a leer información de las páginas de datos señaladas. Llena la primera tabla RDB$Pages con datos. Esta tabla es algo así como un puente entre los objetos físicos (páginas de archivos de base de datos) y los lógicos (tablas). La estructura de RDB$Pages, como la de otras tablas del sistema, está estrictamente fijada en InterBase.
-
Tras obtener datos sobre la asignación de páginas por relaciones (las relaciones son, de hecho, lo mismo que las tablas ordinarias, y podemos sustituir mentalmente estos conceptos para simplificar), InterBase comienza a formar estructuras de datos: primero tablas del sistema, restricciones e índices, y luego objetos de usuario.
-
Después de la inicialización de los metadatos del sistema y del usuario (tablas, restricciones, índices y otros objetos de base de datos), InterBase devuelve el identificador de esta base de datos al usuario que solicitó abrirla. En esencia, el identificador es un indicador que muestra a InterBase con qué base de datos trabajar, porque varios usuarios pueden trabajar al mismo tiempo y eso significa que varias bases de datos pueden estar abiertas.
-
Tras estas operaciones, la base de datos se considera abierta y el servidor está listo para ejecutar consultas de usuario sobre ella. Ahora que se ha establecido un cierto puente que conecta la estructura física y lógica de la base de datos, podemos comenzar a estudiar las peculiaridades de la estructura lógica.
Estructura lógica de la base de datos InterBase
La estructura lógica es un concepto bastante vago, por lo que intentaremos dominar las ideas clave gradualmente, esperando que más adelante resulten intuitivamente claras. Lo primero que consideraremos relacionado con la estructura lógica de la base de datos son las tablas del sistema y su contenido. Las tablas del sistema describen el sistema, así como los metadatos del usuario. En términos generales, el término «metadatos» significa «datos que describen un conjunto de datos». El prefijo «meta» significa: «describe un conjunto». Por ejemplo, un metalenguaje es un lenguaje que describe un conjunto de lenguajes. Los metadatos describen los datos del usuario, es decir, tablas, disparadores, vistas, procedimientos almacenados y demás: todo lo que implementa las reglas de almacenamiento y procesamiento de la información, razón por la cual se crea esta base de datos concreta.
Resulta bastante curioso saber por primera vez que todos los metadatos (tablas de usuario, disparadores, vistas, así como todos los objetos del sistema) se almacenan en las mismas tablas, de las cuales se pueden leer y escribir datos mediante consultas SQL ordinarias. Estas tablas se diferencian «visualmente» solo por el hecho de que sus nombres comienzan con RDB$. Estos 4 símbolos están reservados para los nombres de los objetos del sistema. Ninguna tabla de usuario, columna u otro objeto tiene derecho a tener nombres que comiencen con estos símbolos. Formalmente se puede crear una tabla cuyo nombre comience con los símbolos reservados, pero la documentación de InterBase no lo recomienda.
Surge una pregunta: si los datos sobre la estructura de la base de datos se almacenan en las mismas tablas que los datos del usuario, ¿dónde se guarda la información sobre las tablas que describen tablas? Un ejemplo clásico del problema del «huevo y la gallina»: ¿cómo podría aparecer uno antes que el otro si son interdependientes? La respuesta es que las tablas del sistema en su estado primitivo están fijadas en los códigos iniciales de InterBase y se abren automáticamente al crear una base de datos en un orden determinado. Ya hemos hablado de la tabla RDB$Pages, que compara las páginas físicas de los archivos de base de datos con objetos definidos de esta base. La estructura de esta tabla se muestra a continuación:
Tabla 5. Tabla del sistema RDB$Pages
| Nombre de columna | Tipo de datos | Descripción |
| RDB$PAGE_NUMBER | INTEGER | Número de página física |
| RDB$RELATION_ID | SMALLINT | Identificador de la tabla para la cual se asigna la página |
| RDB$PAGE_SEQUENCE | INTEGER | Número de esta página |
| RDB$PAGE_TYPE | SMALLINT | Tipo de página: ver tabla 3 |
Cada página de datos está relacionada con una tabla determinada. Esta relación se mantiene mediante el campo RDB$RELATION_ID, donde se almacena una referencia a la tabla. Como se describió anteriormente, en el proceso de construcción de la imagen interna de la base de datos, el servidor crea esta tabla y la llena con datos según un algoritmo establecido. Para ser precisos, en el momento de construir la imagen interna de la base de datos, RDB$Pages no es una tabla, sino solo un archivo de datos de formato definido, conocido por InterBase. Según un algoritmo fijo, el servidor lee datos de este archivo y crea una tabla, RDB$Relations, que es importante para toda la base de datos. Esta tabla describe todas las tablas de la base de datos. Si ejecutamos la consulta SQL:
SELECT * from RDB$Relations
para averiguar a qué tablas hace referencia RDB$Relations, veremos que contiene RDB$Pages y a sí misma. Es evidente que en este caso el servidor es un poco astuto, sustituyendo estas y otras tablas del sistema en RDB$Relations con efecto retroactivo, legalizándolas de esa manera. El servidor las registra como tablas «normales», donde puede agregar o eliminar registros. En otras palabras, proporciona una interfaz SQL estándar para trabajar con metadatos.
Y puede surgir una pregunta bastante razonable: ¿por qué los desarrolladores de InterBase ajustarían sus datos del sistema de acuerdo con la interfaz de usuario? Después de todo, los mecanismos internos de acceso y operaciones de lectura serían más rápidos. Por supuesto, tiene mucho sentido proporcionar un mecanismo universal de trabajo con tablas que describen metadatos.
El asunto es que la estructura lógica de la base de datos no consiste solo en tablas, sino también en otros objetos. En InterBase existen los siguientes objetos:
-
Tabla
-
Vista
-
Disparador
-
Campo_calculado
-
Validación
-
Procedimiento
-
Índice_de_expresión
-
Excepción
-
Usuario
-
Campo
-
Índice
-
Función definida por el usuario (UDF)
Aún no sabemos con certeza la función de algunos objetos, pero sabemos con seguridad que todos deben describirse y almacenarse de alguna manera conveniente para el usuario y para el acceso desde el núcleo de InterBase. Lo mejor sería guardar estos objetos en tablas del sistema. Su adición y modificación se realizan mediante consultas SQL. Una solución inteligente, ¿no? La implementación del servidor está completamente separada de una base de datos concreta: todas las interconexiones se describen mediante SQL y sus extensiones: el lenguaje de procedimientos almacenados y disparadores.
Entonces, todos los objetos del servidor se almacenan en tablas. Cada tipo de objeto tiene una tabla que describe todas las instancias definidas en la base de datos. Por ejemplo, para los disparadores existe una tabla RDB$Triggers, para los procedimientos almacenados, RDB$Procedures, y las vistas se describen en la tabla RDB$Relations.
Consideremos en detalle la estructura de esta última tabla, que describe todas las tablas y vistas de una base de datos. La estructura de la tabla RDB$RELATIONS está tomada de Language Reference para InterBase 6 y se muestra a continuación en la tabla 6.
Tabla 6. Tabla del sistema RDB$Relations
| Nombre de columna | Tipo de datos | Longitud | Descripción |
| RDB$VIEW_BLR | BLOB | 80 | BLR: para vistas, contiene el BLR (Binary Language Representation) de la consulta que InterBase ejecuta cada vez que se accede a la vista. |
| RDB$VIEW_SOURCE | BLOB | 80 | Texto: para vistas, contiene el código de la consulta SQL que implementa esta vista. |
| RDB$_DESCRIPTION | BLOB | 80 | Descripción de usuario de la tabla o vista |
| RDB$RELATION_ID | SMALLINT | Contiene el identificador interno de la tabla/vista | |
| RDB$SYSTEM_FLAG | SMALLINT | Define el tipo de tabla: datos de usuario - 0; información del sistema > 0. | |
| RDB$DBKEY_LENGTH | SMALLINT | Longitud de db$key | |
| RDB$FORMAT | SMALLINT | Reservado para uso interno de InterBase. Contiene el contador de modificaciones de metadatos para la tabla dada. | |
| RDB$FIELD_ID | SMALLINT | El número de campos en la tabla. | |
| RDB$RELATION_NAME | CHAR | 31 | Nombre único de la tabla. |
En la descripción de esta tabla del sistema, vemos una abreviatura: BLR. Para entender qué es, haremos una digresión hacia SQL. Como se sabe, las vistas, los disparadores y los procedimientos almacenados son código escrito en una extensión del lenguaje SQL (para cada servidor DBMS existen sus propias extensiones). Está cerca del lenguaje humano, lo que permite realizar consultas fácilmente. Pero InterBase, obviamente, lo traduce a algo más «máquina»: precisamente a BLR (Binary Language Representation). Cualquier consulta, vista, disparador o procedimiento almacenado siempre se traduce a BLR y luego se transmite al núcleo de InterBase para su ejecución.
BLR
BLR es un lenguaje especial utilizado como enlace intermedio entre el código SQL que escribe un programador y el código máquina que admite el servidor. Nadie escribe directamente en BLR: sería bastante difícil porque, para lograr la máxima velocidad de ejecución posible, en este lenguaje se utiliza la llamada notación polaca inversa. Aquí hay un pequeño ejemplo:
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,
El BLR para sus consultas, procedimientos, disparadores y otros disparadores lo forma el preprocesador especial que forma parte del núcleo del servidor. Como se muestra en la tabla 7, para las vistas se almacena su texto (inicial) de la vista, así como la vista compilada, es decir, el BLR. Al referirse a cualquier objeto que tenga BLR, el servidor ejecuta el código binario del objeto y no interpreta el texto inicial de estos objetos cada vez, lo que permite acelerar la ejecución de consultas complicadas.
Jerarquía de objetos en InterBase
Para tener una idea clara de qué representan los objetos de la base de datos, intentaremos hacer una jerarquía de objetos de la base de datos según el principio «quién contiene y qué». Las páginas físicas de los archivos de base de datos son lo primero que debe incluirse en nuestra jerarquía como el nivel más bajo de organización de datos. Luego van las tablas como objetos básicos que describen todos los demás tipos de objetos. Las tablas describen procedimientos almacenados, disparadores, campos calculados, validaciones, índices de expresión, excepciones, etc. ¡Atención: solo describen! Las tablas contienen únicamente declaraciones y definiciones de estos objetos, y los objetos se implementan mediante BLR. Por lo tanto, podemos representar las tablas como un marco que sostiene todos los demás objetos de la base de datos. El BLR estará en la base del marco como capa de implementación, luego los disparadores, procedimientos almacenados, índices de expresión y vistas.
Para calmar a los especialistas en la estructura interna de InterBase, que podrían objetar que el BLR de muchos objetos (como las vistas) se almacena en tablas del sistema, comentaremos que esta relación es bastante difícil de expresar en una imagen y, para simplificar, la omitimos. El esquema no tiene como objetivo recrear las interdependencias de los objetos de la base de datos con total precisión; solo ilustra su estrecha interconexión.
El hecho de que estos tipos de objetos estén directamente conectados con el BLR, que los implementa sin ninguna lógica intermedia, los une. Las excepciones deben asignarse por separado: representan tipos especiales de errores definidos por el usuario. Las excepciones se procesan a nivel del núcleo de InterBase y, por lo tanto, no tienen BLR. Tipos de restricciones como las comprobaciones (checks) se asignan por encima de los disparadores porque, en realidad, los disparadores implementan la lógica de las restricciones y comprobaciones.
Una jerarquía de objetos de la estructura lógica y física de la base de datos se muestra en la imagen 2.
Imagen 10. Objetos de la estructura lógica de la base de datos InterBase
Por supuesto, este esquema describe la estructura lógica y las interconexiones de los objetos en la base de datos solo aproximadamente y ofrece una idea general de ella. Quien desee estudiar la estructura de los metadatos de la base de datos InterBase puede realizar una ingeniería inversa de las tablas del sistema de la base de datos y considerar todas las interconexiones entre sus objetos, así como consultar la documentación y los códigos primarios de InterBase. Esta tabla muestra solo los objetos principales de la base de datos. Describamos brevemente las funciones principales que estos objetos realizan en la base de datos.
Tablas - el objeto principal, que contiene datos de usuario y del sistema. Una tabla tiene un nombre único y contiene un conjunto de campos nombrados. Un usuario puede colocar datos, extraerlos y modificarlos en las tablas. Podemos decir que una tabla es similar a las tablas de papel ordinarias dibujadas a mano.
Disparadores (Triggers) - partes ejecutables del código, utilizadas para implementar acciones adicionales en el momento de las operaciones con datos. Los disparadores se ejecutan antes o después de las operaciones de inserción, modificación o eliminación, y permiten realizar la sustitución de valores en registros recién creados y muchas otras cosas.
Un procedimiento almacenado es una herramienta poderosa para implementar lógica de negocio a nivel de la base de datos. Al ejecutarse a nivel del servidor, funciona muy rápido y permite ejecutar un conjunto de operaciones sobre conjuntos de datos. Los procedimientos almacenados de InterBase devuelven conjuntos de datos SQL estándar, sobre los cuales se pueden realizar todas las operaciones SQL, incluida la unificación con otras tablas.
Las vistas son consultas SQL compiladas, ejecutadas en el servidor. Las vistas permiten organizar conjuntos de datos, transfiriendo parte de la lógica de negocio al servidor.
Validaciones son restricciones establecidas sobre los valores de los campos en la tabla. Por ejemplo, podemos indicar que un campo determinado solo aceptará valores positivos. Las restricciones sobre los valores de los campos se implementan mediante disparadores y permiten controlar eficazmente la integridad referencial a nivel de la base de datos. Generalmente, las restricciones se utilizan para evitar que se coloquen valores incorrectos en una tabla.
Usuarios - InterBase nos permite tener varios usuarios para trabajar con la base de datos y distribuir entre ellos los derechos de acceso a diferentes objetos de la base de datos. Así, podemos controlar los permisos para estas u otras operaciones de la base de datos.
Funciones Definidas por el Usuario (UDF) - funciones definidas por el usuario. Es una de las capacidades más poderosas de InterBase, que nos permite ampliar la interfaz SQL estándar con nuestras propias funciones. Por ejemplo, las funciones de trabajo con cadenas como UPPER (que establece todos los símbolos en mayúsculas), están implementadas en la biblioteca UDF estándar, incluida en el conjunto de InterBase. Gracias a la posibilidad de crear UDF propias, los desarrolladores pueden ampliar la funcionalidad de InterBase prácticamente con cualquier función. Podemos usar cualquier entorno de programación que permita crear bibliotecas dinámicas (Visual C++, C++ Builder, Delphi, etc.) para crear UDF.
Conclusión
En este capítulo hemos considerado por primera vez las cuestiones sobre la implementación del almacenamiento y procesamiento de datos dentro de la base de datos InterBase. Desafortunadamente, no podemos hacer una revisión breve de este tema sin recurrir a una gran cantidad de términos y analogías imprecisas. Si describiéramos la estructura física y lógica de la base de datos con más detalle, tendríamos que referirnos de todos modos a los códigos primarios de InterBase, pero eso sería otro libro.
Sin embargo, creemos que sería útil para todo programador familiarizarse con el contenido del producto que utiliza todos los días.
Bibliografía
-
«The On-Disk Structure of InterBase» de Ann.W.Harrison
-
«Space Management in InterBase» de Ann W.Harrison
-
«Structure of a Data Page» de Paul Beach (Con agradecimiento a Dave Schnepper y Deej Bredenberg)