En el mundo de las bases de datos, es común que múltiples usuarios o procesos intenten acceder y modificar los mismos datos simultáneamente. Sin un mecanismo adecuado para gestionar estas operaciones concurrentes, podrían surgir problemas graves como la pérdida de actualizaciones, lecturas sucias o lecturas inconsistentes, lo que comprometería la integridad y consistencia de la información almacenada.

Aquí es donde entran en juego los horarios (o schedules en inglés). Un horario es, en esencia, un modelo abstracto que describe el orden en que se ejecutan las operaciones (acciones) de un conjunto de transacciones que se están ejecutando de forma concurrente en el sistema. Piensa en ello como una línea de tiempo que muestra cuándo y en qué orden ocurren las acciones de cada transacción sobre los objetos de la base de datos.

El objetivo principal del manejo de horarios es garantizar que, a pesar de la ejecución concurrente, el resultado final sea equivalente a ejecutar esas mismas transacciones de forma secuencial, una tras otra, sin solapamientos. A este principio se le conoce como serializabilidad, y es la piedra angular para asegurar la corrección en sistemas de bases de datos multiusuario.
¿Qué es exactamente un Horario?
Un horario es una secuencia de las operaciones realizadas por un conjunto de transacciones. Estas operaciones pueden incluir leer datos (R), escribir datos (W), solicitar un bloqueo, adquirir un bloqueo, liberar un bloqueo, abortar (Abort) o confirmar (Commit) una transacción. Aunque en la práctica las operaciones tienen una duración, en el modelo teórico de horarios se suelen considerar como eventos atómicos que ocurren en un punto específico en el tiempo.
Un horario puede representarse como una lista ordenada de operaciones o, en casos más complejos donde el orden entre ciertas operaciones no está estrictamente determinado por el sistema, como un grafo dirigido acíclico (DAG). Lo crucial es que el orden temporal de las operaciones dentro de una misma transacción siempre se mantiene; es decir, si una transacción T1 primero lee y luego escribe un dato, ese orden se respetará en cualquier horario donde participe T1.
Consideremos un ejemplo simple con dos transacciones, T1 y T2, accediendo a un dato A:
Horario S1:
T1: Lee(A)T2: Lee(A)T1: Escribe(A)T2: Escribe(A)T1: CommitT2: Commit
Este horario muestra cómo las operaciones de T1 y T2 se entrelazan.
La Importancia del Control de Concurrencia
La razón fundamental de estudiar y gestionar los horarios es el control de concurrencia. Cuando varias transacciones acceden a los mismos datos al mismo tiempo, pueden ocurrir inconsistencias si sus operaciones no se coordinan adecuadamente. Algunos problemas comunes son:
- Actualización Perdida (Lost Update): Una transacción sobrescribe una actualización realizada por otra transacción sin haber leído previamente el valor actualizado.
- Lectura Sucia (Dirty Read): Una transacción lee datos que han sido modificados por otra transacción, pero que aún no han sido confirmados (commit). Si la segunda transacción luego aborta, los datos leídos por la primera transacción son inválidos.
- Lectura No Repetible (Non-Repeatable Read): Una transacción lee el mismo dato dos veces y obtiene valores diferentes porque otra transacción modificó y confirmó el dato entre las dos lecturas.
- Lectura Fantasma (Phantom Read): Una transacción ejecuta una consulta que devuelve un conjunto de filas. Si otra transacción inserta o elimina filas que cumplen los criterios de la consulta y confirma, una ejecución posterior de la misma consulta por la primera transacción devolverá un conjunto de filas diferente.
El control de concurrencia, a través de la gestión de horarios, busca evitar estos problemas, asegurando que la ejecución concurrente sea equivalente a una ejecución serial.
Tipos de Horarios en Bases de Datos
Existen diferentes clasificaciones de horarios basadas en las propiedades que cumplen, especialmente en relación con la serializabilidad y la recuperación ante fallos.
Horarios Completos
Un horario se considera completo si incluye una acción de finalización (ya sea Commit o Abort) para cada una de las transacciones que participan en él. La última acción de cualquier transacción en un horario completo debe ser Commit o Abort. Para mantener la atomicidad, si una transacción aborta, todas sus acciones deben ser deshechas.
Horarios Seriales
Un horario es serial si las transacciones se ejecutan una después de otra, sin ningún tipo de solapamiento o entrelazamiento en sus operaciones. Es decir, una transacción no comienza hasta que la transacción anterior ha finalizado (commit o abort). Los horarios seriales, por definición, no presentan problemas de concurrencia, ya que no hay concurrencia real. Sin embargo, son ineficientes porque limitan el rendimiento del sistema al no permitir el paralelismo.
Ejemplo de Horario Serial D:
T1: Lee(X)T1: Escribe(X)T1: CommitT2: Lee(Y)T2: Escribe(Y)T2: CommitT3: Lee(Z)T3: Escribe(Z)T3: Commit
En este caso, T1 se ejecuta completamente, luego T2, y finalmente T3.
Horarios Serializables
Un horario es serializable si su resultado final es equivalente al resultado de alguna ejecución serial de las mismas transacciones. La serializabilidad es el criterio principal para la corrección de los horarios de transacciones concurrentes y es el objetivo de la mayoría de los mecanismos de control de concurrencia en bases de datos. Un horario serializable garantiza que la base de datos permanece en un estado consistente, como si las transacciones se hubieran ejecutado secuencialmente.
Ejemplo de Horario Serializable E (equivalente a D):
T1: Lee(X)T2: Lee(Y)T3: Lee(Z)T1: Escribe(X)T2: Escribe(Y)T3: Escribe(Z)T1: CommitT2: CommitT3: Commit
Aunque las operaciones están entrelazadas, el estado final de los datos es el mismo que en el horario serial D.
Conflictos en Horarios
Para entender algunos tipos de serializabilidad, es fundamental definir qué son las acciones conflictivas. Dos acciones de diferentes transacciones se consideran en conflicto si cumplen las siguientes tres condiciones:
- Pertenecen a transacciones diferentes.
- Acceden al mismo objeto de datos.
- Al menos una de las acciones es una operación de escritura (Write).
Esto implica que los pares de acciones conflictivas son Read-Write (lectura-escritura), Write-Read (escritura-lectura) y Write-Write (escritura-escritura) sobre el mismo dato por transacciones distintas. Las lecturas concurrentes sobre el mismo dato (Read-Read) por transacciones diferentes no son conflictivas.
Los conflictos son la causa fundamental de los problemas de concurrencia y de los retrasos o abortos en los sistemas de bases de datos.
Horarios Conflict-Serializable
Un horario es conflict-serializable si es equivalente a algún horario serial en cuanto al orden de sus operaciones conflictivas. Dos horarios son conflict-equivalentes si involucran el mismo conjunto de transacciones y tienen el mismo conjunto de pares de operaciones conflictivas en el mismo orden.
Una forma común de determinar si un horario es conflict-serializable es construir su grafo de precedencia. En este grafo, los nodos son las transacciones. Se dibuja un arco dirigido de la transacción Ti a Tj si una operación de Ti entra en conflicto con una operación de Tj y la operación de Ti ocurre antes en el horario. Un horario es conflict-serializable si y solo si su grafo de precedencia es acíclico (no contiene ciclos), considerando solo las transacciones que confirman (Commit).
La conflict-serializabilidad es un criterio práctico y ampliamente utilizado para garantizar la serializabilidad en sistemas de bases de datos, ya que es más fácil de implementar y verificar que otros tipos de serializabilidad. Mecanismos como el bloqueo de dos fases (Two-Phase Locking, 2PL) o el ordenamiento por marcas de tiempo (Timestamp Ordering) buscan generar horarios conflict-serializables.
Horarios View-Serializable
Un horario es view-serializable si es equivalente a algún horario serial en cuanto a las vistas (views) que tienen las transacciones sobre los datos. Dos horarios son view-equivalentes si cumplen tres condiciones:
- Cada transacción lee el valor inicial de un dato si y solo si lo hace en el mismo horario serial equivalente.
- Si una transacción Tj lee un valor escrito por Ti en un horario, entonces Tj también debe leer el valor escrito por Ti en el horario serial equivalente.
- La transacción que realiza la escritura final sobre cada dato es la misma en ambos horarios.
Todo horario conflict-serializable es también view-serializable. Sin embargo, existen horarios view-serializables que no son conflict-serializables, particularmente aquellos que involucran escrituras ciegas (blind writes), donde una transacción escribe un dato sin haberlo leído previamente.
Ejemplo de escritura ciega (Horario H):
T1: Lee(A)T1: Escribe(A)T1: CommitT2: Escribe(A)T2: CommitT3: Escribe(A)T3: Commit
Este horario no es conflict-serializable (hay conflictos de escritura-escritura entre T1, T2 y T3 sin un orden claro), pero podría ser view-serializable si es equivalente a un horario serial como
Horarios Recuperables
La recuperabilidad es una propiedad crucial para la robustez del sistema ante fallos. Un horario es recuperable si, para cada par de transacciones Ti y Tj tal que Tj lee un dato escrito por Ti, la transacción Ti confirma sus cambios (Commit) antes de que Tj confirme sus cambios. En otras palabras, una transacción solo confirma después de que todas las transacciones cuyos cambios ha leído (lecturas dependientes) han confirmado.
Horario J (NO Recuperable):
T1: Escribe(A)T2: Lee(A)T2: CommitT1: Abort
En este ejemplo, T2 lee el valor escrito por T1 y confirma. Si T1 luego aborta (quizás debido a un fallo), el valor que T2 leyó y en el que se basó para confirmar es incorrecto. Como T2 ya confirmó, sus cambios no se pueden deshacer fácilmente sin medidas adicionales (como compensación manual), dejando la base de datos en un estado inconsistente. Por lo tanto, J no es recuperable.
Horario F (Recuperable):
T1: Escribe(A)T2: Lee(A)T1: CommitT2: Commit
Aquí, T1 confirma antes que T2. Si T1 abortara antes de que T2 lea, T2 simplemente leería el valor original. Si T1 confirma, T2 lee el valor correcto y puede confirmar. Si T1 confirmara y T2 luego necesitara abortar, T1 ya está confirmada y no se ve afectada. Este horario es recuperable.
Horarios sin Cascadas (Cascadeless)
Un tipo de horario recuperable deseable en la práctica es aquel que evita los abortos en cascada. Un aborto en cascada ocurre cuando el aborto de una transacción Ti provoca que otras transacciones Tj que leyeron datos escritos por Ti también tengan que abortar. Esto sucede cuando se permiten las "lecturas sucias" (dirty reads), es decir, leer datos escritos por transacciones no confirmadas.
Los horarios sin cascadas (cascadeless schedules) evitan los abortos en cascada al prohibir las lecturas sucias. Una transacción Tj solo puede leer un dato escrito por Ti si Ti ya ha confirmado.
Horario F2 (Recuperable, pero con posibles cascadas):
T1: Escribe(A)T2: Lee(A)T1: AbortT2: ??? (T2 debe abortar)
T2 leyó un valor de T1 que luego fue deshecho por el aborto de T1. T2 leyó "sucio". Para mantener la consistencia, T2 debe abortar, causando un aborto en cascada.
Horario F3 (Recuperable y Cascadeless):
T1: Escribe(A)T1: AbortT2: Lee(A)T2: Commit
En este caso, T1 aborta antes de que T2 lea su valor. T2 leerá el valor original de A (o el valor escrito por otra transacción ya confirmada). No hay lectura sucia, por lo tanto, no hay riesgo de aborto en cascada debido a esta dependencia.
Todo horario sin cascadas es recuperable, pero lo contrario no es cierto (como vimos con F2).
Horarios Estrictos
Un horario es estricto si, para cualquier par de transacciones Ti y Tj, si una operación de escritura de Ti precede a una operación conflictiva (lectura o escritura) de Tj, entonces la acción de finalización (Commit o Abort) de Ti también precede a esa operación conflictiva de Tj.
Los horarios estrictos son particularmente útiles para la recuperación eficiente del sistema ante fallos. Si un sistema falla y necesita recuperarse, deshacer las transacciones incompletas es más sencillo si ninguna transacción confirmada ha leído o escrito datos modificados por transacciones que no han confirmado.
Todo horario estricto es también sin cascadas, lo que a su vez implica que es recuperable. Esta jerarquía (Estricto → Sin Cascadas → Recuperable) muestra propiedades deseables para la robustez y fiabilidad de un sistema de bases de datos.
Comparativa de Tipos de Horarios
Aquí presentamos una tabla resumen de las propiedades clave de algunos tipos de horarios:
| Tipo de Horario | Descripción | Garantiza Serializabilidad | Es Recuperable | Evita Cascadas | Es Estricto |
|---|---|---|---|---|---|
| Serial | Transacciones ejecutadas secuencialmente | Sí (por definición) | Sí | Sí | Sí |
| Serializable | Equivalente a algún horario serial | Sí | No necesariamente | No necesariamente | No necesariamente |
| Conflict-Serializable | Conflict-equivalente a algún serial | Sí | No necesariamente | No necesariamente | No necesariamente |
| View-Serializable | View-equivalente a algún serial | Sí | No necesariamente | No necesariamente | No necesariamente |
| Recuperable | Las transacciones que leen cambios ajenos confirman después | No necesariamente | Sí | No necesariamente | No necesariamente |
| Sin Cascadas (Cascadeless) | No permite lecturas sucias | No necesariamente | Sí | Sí | No necesariamente |
| Estricto | Las lecturas/escrituras conflictivas solo ocurren después de la finalización de la transacción que escribió primero | No necesariamente | Sí | Sí | Sí |
Nota: Los sistemas de bases de datos prácticos suelen implementar mecanismos que generan horarios que son conflict-serializables y, al mismo tiempo, estrictos y recuperables, ya que esto ofrece un buen equilibrio entre rendimiento (permitiendo concurrencia) y robustez (garantizando consistencia y facilitando la recuperación).
¿Cómo se Implementan Estos Conceptos en la Práctica?
Los sistemas de bases de datos utilizan diversos mecanismos para garantizar que los horarios generados cumplan con las propiedades deseadas, principalmente la conflict-serializabilidad y la estrictez/recuperabilidad. Los enfoques más comunes incluyen:
- Protocolos de Bloqueo: Como el bloqueo de dos fases (2PL), donde las transacciones adquieren bloqueos sobre los datos antes de acceder a ellos y los liberan en una fase posterior. Esto previene conflictos al serializar el acceso a los datos.
- Ordenamiento por Marcas de Tiempo (Timestamp Ordering): Asigna una marca de tiempo única a cada transacción y ordena las operaciones basándose en estas marcas.
- Control de Concurrencia Multiversión (MVCC): Permite que las transacciones lean versiones anteriores de los datos mientras otras transacciones los modifican, reduciendo los conflictos de lectura-escritura. La Serialización de Snapshot Isolation (SSI) es un ejemplo que busca la serializabilidad.
Estos mecanismos implementan reglas que restringen el orden en que las operaciones de las transacciones concurrentes pueden ejecutarse, forzando así la generación de horarios correctos.
Preguntas Frecuentes sobre Horarios en Bases de Datos
¿Cuál es la diferencia entre un horario serial y uno serializable?
Un horario serial es aquel en el que las transacciones se ejecutan una después de otra, sin solapamiento. Un horario serializable es un horario concurrente (con operaciones entrelazadas) cuyo resultado final es el mismo que el de algún horario serial. La serialidad es una propiedad del horario en sí (no hay concurrencia), mientras que la serializabilidad es una propiedad de un horario concurrente que indica su corrección.
¿Por qué es tan importante la serializabilidad?
La serializabilidad es crucial porque garantiza que la ejecución concurrente de transacciones no llevará a un estado inconsistente de la base de datos. Asegura que, a pesar de que múltiples usuarios trabajen al mismo tiempo, el estado final de los datos será el mismo que si hubieran trabajado uno tras otro de forma secuencial. Esto es vital para la fiabilidad y precisión de los datos, especialmente en aplicaciones críticas como sistemas bancarios o de inventario.
¿Qué es una "lectura sucia" (dirty read)?
Una lectura sucia ocurre cuando una transacción lee un dato que ha sido modificado por otra transacción, pero que aún no ha sido confirmado (Commit). Si la transacción que realizó la escritura luego aborta, la lectura sucia se vuelve inválida, ya que el cambio nunca debió haber ocurrido. Esto puede llevar a que la transacción que realizó la lectura sucia actúe basándose en información incorrecta.
¿Qué significa que un horario sea recuperable?
Que un horario sea recuperable significa que el sistema puede recuperarse correctamente si alguna transacción aborta. Específicamente, si una transacción lee un dato escrito por otra, la transacción que escribió debe confirmar antes de que la transacción que leyó confirme. Esto previene situaciones donde una transacción confirma basándose en un valor que luego es deshecho por un aborto, evitando estados inconsistentes que son difíciles de corregir.
¿Cuál es el tipo de horario más deseable en la práctica?
En la práctica, los sistemas de bases de datos modernos buscan generar horarios que sean conflict-serializables (para garantizar la consistencia lógica) y al mismo tiempo estrictos (para facilitar la recuperación ante fallos y evitar lecturas sucias y cascadas). Los horarios estrictos y conflict-serializables ofrecen un buen compromiso entre rendimiento y robustez.
Conclusión
Los horarios son un concepto fundamental en el estudio y diseño de sistemas de bases de datos concurrentes. Permiten analizar y comprender cómo las operaciones de múltiples transacciones se entrelazan y afectan la consistencia de los datos. Propiedades como la serializabilidad, la recuperabilidad y la estrictez son objetivos clave de los mecanismos de control de concurrencia, que buscan garantizar que, a pesar de la complejidad de la ejecución simultánea, la base de datos se mantenga siempre en un estado correcto y confiable. Comprender estos conceptos es esencial para diseñar y gestionar sistemas de bases de datos robustos y eficientes.
Si quieres conocer otros artículos parecidos a Horarios en Bases de Datos puedes visitar la categoría Bases de datos.

Aprende mas sobre MySQL