Lista de Artículos Inicio
- Año II  


Control de Concurrencia de Transacciones en un Sistema de Base de Datos - (PARTE 3)

Violeta N. Chang Camacho

Ing. Informatico.
Univerisidad Nacional de Trujillo http://www.unitru.edu.pe
Departamento Académico de Informática.
informatizate(at)informatizate(dot)net
Julio 26 del 2004.


Una transacción Ti está confirmada (o cancelada) en la historia H si ci H (o ai H). Ti está activa en H si está confirmada o cancelada. Por supuesto que una historia completa no tiene transacciones activas.

Una historia completa H es serial si, para cada par de transacciones Ti y Tj que aparecen en H, todas las operaciones de Ti aparecen antes de todas las operaciones de Tj o viceversa. Por lo tanto, una historia serial representa una ejecución en la cual no hay intercalación de las operaciones de transacciones diferentes. Cada transacción se ejecuta desde el principio hasta el fin antes de que la siguiente comience.

Decimos que dos historias son equivalentes si tienen el mismo conjuntos de operaciones y las operaciones conflictivas están en el mismo orden en ambas historias. Por ejemplo, las dos historias siguientes son equivalentes

H1 = r1[x] r2[x] w1[x] c1 w2[y] c2
H2 = r2[x] r1[x] w2[y] c2w1[x] c1

Sin embargo, ninguna de ellas es equivalente a

H3 = r1[x] w1[x] r2[x] c1 w2[y] c2

porque r2[x] y w1[x] están en conflicto y r2[x] precede a w1[x] en H1 y H2 pero w1[x] precede a r2[x] en H3.

Definimos una historia H como serializable si es equivalente a alguna historia serial Hs. Por ejemplo,

H1 = r1[x] r2[x] w1[x] c1w2[y] c2

es equivalente a H4 = r2[x] w2[y] c2r1[x] w1[x] c1 puesto que r2[x] y w1[x] están en el mismo orden en H1 y H4, por lo tanto H1 es serializable.

Podemos determinar si una historia es serializable o no analizando un grafo derivado de la historia llamado grafo de serialización. Sea H una historia sobre T={T1, T2, ..., Tn}. El grafo de serialización (SG) para H es una grafo dirigido cuyos nodos son las transacciones en T que están confirmadas en H y cuyos arcos son todos aquellos de la forma Ti -› Tj (i =/ j) tal que una de las operaciones de Ti preceda y esté en conflicto con una de las operaciones de Tj en H. Por ejemplo, para la historia

H5 = r1[x] r2[x] w1[x] r3[x] w2[y] w3[x] c3 w1[y] c1 c2

su grafo de serialización es SG(H5) = T2 -› T1 -› T3

El arco T1 -› T3 está en SG(H5) porque w1[x] < r3[x], y el arco T2 -› T3 está en SG(H5) porque r2[x] < w3[x]. Es importante resaltar que un arco en SG(H5) puede originarse por más de un par de operaciones conflictivas; por ejemplo, el arco T2 -› T1 es causado tanto por r2[x] > w1[x] y w2[y] > w1[y].

Cada arco Ti -› Tj en SG(H) significa que al menos una de las operaciones de Ti precede y está en conflicto con una de las operaciones de Tj. Esto indica que Ti debe preceder a Tj en cualquier historia serial que es equivalente a H. Si es posible encontrar una historia serial, Hs, consistente de todos los arcos en SG(H), entonces Hs es equivalente a H y por lo tanto, H es serializable siempre que SG(H) sea acíclico.

en el ejemplo anterior, SG(H5) es acíclico. Una historia serial en la que las transacciones aparecen en un orden consistente con los arcos de SG(H5) es T2 T1 T3, es decir,

H6 = r2[x] w2[y] c2 r1[x] w1[x] w1[y] c1 r3[x] w3[x] c3

que es equivalente a H5 y por lo tanto H5 es serializable.

Teorema de Seriabilidad

Una historia H es serializable si y sólo si SG(H) es acíclico.

Este teorema se usa para caracterizar el conjunto de historias permitido por un algoritmo de control de concurrencia y probar que cualquiera de esas historias debe tener un grafo de serialización acíclico, por lo tanto, el algoritmo de control de concurrencia garantiza las ejecuciones serializables.


5 Métodos de Control de Concurrencia

El criterio de clasificación más común de los algoritmos de control de concurrencia es el tipo de primitiva de sincronización. Esto resulta en dos clases: aquellos algoritmos que están basados en acceso mutuamente exclusivo a datos compartidos (bloqueos) y aquellos que intentar ordenar la ejecución de las transacciones de acuerdo a un conjunto de reglas (protocolos). Sin embargo, esas primitivas se pueden usar en algoritmos con dos puntos de vista diferentes: el punto de vista pesimista que considera que muchas transacciones tienen conflictos con otras, o el punto de vista optimista que supone que no se presentan muchos conflictos entre transacciones.

Los algoritmos pesimistas sincronizan la ejecución concurrente de las transacciones en su etapa inicial de su ciclo de ejecución. Los algoritmos optimistas retrasan la sincronización de las transacciones hasta su terminación. Ambos grupos de métodos, pesimistas y optimistas, consisten de algoritmos basados en bloqueos y algoritmos basados en marcas de tiempo, entre otros.

En la siguiente sección se presenta uno de los métodos pesimistas basado en bloqueos más utilizado conocido como Bloqueo de Dos Fases


6 Bloqueo de Dos Fases

El bloqueo es un mecanismo usado comúnmente para resolver el problema de la sincronización en el acceso a datos compartidos. La idea fundamental del bloqueo es simple y de sentido común. Cada dato tiene un bloqueo asociado con él. Antes de que una transacción T1 pueda accesar al dato, el sincronizador primero examina el bloqueo asociado. Si ninguna transacción tiene el bloqueo, entonces el sincronizador obtiene el bloqueo en representación de T1. Si alguna otra transacción T2 tiene el bloqueo, T1 debe esperar hasta que T2 libere el bloqueo. Esto significa que el sincronizador no le asignará el bloqueo a T1 hasta que T2 lo libere. Por lo tanto, el sincronizador asegura que sólo una transacción a la vez pueda tener el bloqueo y, de acuerdo a esto, sólo una transacción puede acceder al dato a la vez.

El bloqueo puede ser usado por un sincronizador para garantizar la seriabilidad. Debido a que las transacciones acceden a los datos para leerlos o escribirlos, existen dos tipos de bloqueos asociados con los datos: bloqueos de lectura y bloqueos de escritura. Usaremos rl[x] para denotar un bloqueo de lectura sobre el elemento x y wl[x] para denotar un bloqueo de escritura sobre x. Específicamente, usaremos rli[x] (o wli[x]) para denotar la operación por la cual Ti obtiene un bloqueo de lectura (o escritura) sobre x; mientras que rui[x] (o wui[x]) denota la operación mediante la cual Ti libera el bloqueo de lectura (o escritura) sobre x.

Dos bloqueos cualesquiera pli[x] y qlj[y] están en conflicto si x=y, i =/ j, y las operaciones p y q son de tipo conflictivo. Es decir, dos bloqueos son conflictivos si son sobre el mismo dato, son requeridos por transacciones diferentes y uno o ambos son bloqueos de escritura. Por lo tanto, dos bloqueos en diferentes datos no están en conflicto ni tampoco dos bloqueos sobre el mismo dato requeridos por la misma transacción aún cuando sean de tipo conflictivo.

La función del sincronizador de Bloqueo de Dos Fases (2PL) es manejar los bloqueos controlando cuando las transacciones obtienen y liberan sus bloqueos, de acuerdo a las siguientes reglas :

  1. Cuando recibe una operación pi[x] del Administrador de Transacciones (TM), el sincronizador verifica si pli[x] está en conflicto con algún qlj[x] que ya esté establecido. Si es así, retrasa pli[x], forzando a Ti a esperar hasta que pueda recibir el bloqueo que necesita. Si no, el sincronizador acepta pli[x] y envía pi[x] al Administrador de Datos (DM).

  2. Una vez que el sincronizador ha establecido un bloqueo para Ti, digamos pli[x], no puede liberar el bloqueo al menos hasta que el DM reconozca que se ha procesado la operación corresponidente al bloqueo, es decir pi[x].

  3. Una vez que el sincronizador ha liberado un bloqueo para una transacción, no puede, subsecuentemente, obtener ningún otro bloqueo para esa transacción (sobre ningún dato).


La primera regla previene que dos transacciones accedan concurrentemente al mismo dato con operaciones conflictivas. Por lo tanto, las operaciones conflictivas son planificadas en el mismo orden en el que se obtienen sus correspondientes bloqueos. La regla (2) complementa la primera regla, garantizando que el DM procese operaciones sobre el dato en el orden en el que el sincronizador las envía. Por ejemplo, supongamos que Ti obtiene rli[x], el cual libera antes que el DM haya confirmado que ri[x] se haya procesado. Entonces, es posible que algún Tj obtenga un bloqueo conflictivo sobre x, wlj[x], y envíe wj[x] al DM. A pesar que el sincronizador ha enviado ri[x] antes que wj[x] al DM, sin la regla (2) no hay garantía de que el DM recibirá y procesará las operaciones en ese orden.

La tercera regla, conocida como la regla de las dos fases, es el origen del nombre del sincronizador. Cada transacción debe ser dividida en dos fases: una fase de crecimiento durante la cual obtiene bloqueos, y una fase de encogimiento durante la cual libera sus bloqueos.

Consideremos dos transacciones T1 y T2 :

T1 = r1[x] -› w1[y] -› c1
T2 = w2[x] -› w2[y] -› c2

y supongamos que se ejecutan en el siguiente

H1 = rl1[x]r1[x] ru1[x] wl2[x] w2[x] wl2[y] w2[y] wu2[x] wu2[y] c2 wl1[y] w1[y] wu1[y] c1

Como r1[x] > w2[x] y w2[y] > w1[y], se presenta el ciclo T1 -› T2 -› T1 y, como consecuencia, H1 no es serializable.

El problema en H1 es que T1 libera un bloqueo (ru1[x]) y luego obtiene otro (wl1[y]), violando la regla de las dos fases. Entre ru1[x] y wl1[y], otra transacción T2 escribe sobre x y y, y es así que aparece después de T1 con respecto a x y la precede con respecto a y. Si T1 hubiese seguido la regla de las dos fases, esta "ventana" entre ru1[x] y wl1[y] no se habría abierto, y T2 no se hubiera podido ejecutar como ocurrió en H1. Por ejemplo, T1 y T2 pudo haberse ejecutado como sigue.

  1. Inicialmente, ninguna transacción tiene bloqueo alguno.

  2. El sincronizador recibe r1[x] del TM. De acuerdo a esto, asigna rl1[x] y envía r1[x] al DM. Luego el DM reconoce el procesamiento de r2[x].

  3. El sincronizador recibe w2[x] del TM. El sincronizador no puede asignar wl2[x], puesto que está en conflicto con rl1[x], por lo que retrasa la ejecución de w2[x] colocándolo en una cola de espera.

  4. El sincronizador recibe w1[y] del TM. Asigna wl1[y] y envía w1[y] al DM. Luego el DM reconoce el procesamiento de w1[y].

  5. El sincronizador recibe c1 del TM, señalando que T1 ha terminado. El sincronizador envía c1 al DM. Después que el DM reconoce el procesamiento de c1, el sincronizador libera rl1[x] y wl1[y]. Esto está de acuerdo a la regla (2) porque r1[x] y w1[y] ya han sido procesadas, y de acuerdo a la regla (3) porque T1 no solicitará ningún otro bloqueo.

  6. El sincronizador asigna wl2[x] tal que w2[x], que había sido retrasada, puede ser enviada al DM. Luego el DM reconoce w2[x].

  7. El sincronizador recibe w2[y] del TM. Asigna wl2[y] y envía w2[y] al DM. El DM luego reconoce el procesamiento de w2[y].

  8. T2 termina y el TM envía c2 al sincronizador. El sincronizador envía c2 al DM. Después que el DM reconoce el procesamiento de c2, el sincronizador libera wl2[x] y wl2[y].

Esta ejecución es representada por la siguiente historia

H2 = rl1[x] r1[x] wl1[y] w1[y] c1 ru1[x] wu1[y] wl2[x] w2[x] wl2[y] w2[y] c2 wu2[x] wu2[y]
H2 es serial y por lo tanto serializable.

Una importante desventaja de los sincronizadores de 2PL es que están propensos a bloqueos mutuos (deadlocks), conocidos también como "abrazo mortal". Por ejemplo, supongamos que un sincronizador 2PL está procesando las transacciones T1 y T3

T1 = r1[x] -› w1[y] -› c1
T3 = w3[y] -› w3[x] -› c3

y consideremos la siguiente secuencia de eventos:

  1. Inicialmente, ninguna transacción tiene bloqueo alguno.

  2. El sincronizador recibe r1[x] del TM. Asigna rl1[x] y envía r1[x] al DM.

  3. El sincronizador recibe w3[y] del TM. Asigna wl3[y] y envía w3[y] al DM.

  4. El sincronizador recibe w3[x] del TM. El sincronizador no puede asignar wl3[x] porque entra en conflicto con rl1[x] que ya está asignado. Por lo tanto, se retrasa la ejecución de w3[x].

  5. El sincronizador recibe w1[y] del TM. Como en (4), se retrasa la ejecución de w1[y].

Aún cuando el sincronizador se ha desempeñado exactamente de acuerdo a las reglas del 2PL, ni T1 ni T3 pueden ser completadas sin violar una de estas reglas. Si el sincronizador envía w1[y] al DM sin asignar wl1[y] estaría violando la regla (1). Similarmente para w3[x]. Supongamos que el sincronizador libera wl3[y], de modo que se puede asignar wl1[y] y sería posible enviar w1[y] al DM. En este caso, el sincronizador no podría asignar wl3[x] (y por lo tanto, tampoco podría procesar w3[x]), o, en caso contrario, violaría la regla (3). Similarmente si libera rl1[x]. Esta es una clásica situación de bloqueo mutuo. Antes de que cualquiera de las transacciones pueda ser procesada, una de ellas debe liberar un bloqueo que la otra necesita para continuar.

Los bloqueos mutuos también aparecen cuando las transacciones tratan de convertir bloqueos de lectura en bloqueos de escritura, Supongamos que la transacción Ti lee un dato x y luego intenta escribir sobre él. Ti transmite ri[x] al sincronizador, el cual asigna rli[x]. Cuando Ti transmite wi[x] al sincronizador, éste debe actualizar rli[x] a wli[x]. Esta actualización de bloqueo se conoce como conversión de bloqueo. Para seguir las reglas de 2PL, el sincronizador no debe liberar rli[x]. Esto no es un problema, porque los bloqueos asignados por la misma transacción no son conflictivos entre sí. Sin embargo, si dos transacciones intentan concurrentemente convertir sus bloqueos de lectura sobre un dato en bloqueos de escritura, el resultado es un bloqueo mutuo.

Por ejemplo, supongamos que T4 y T5 transmiten operaciones a un sincronizador 2PL.

T4 = r4[x] -› w4[x] -› c4
T5 = r5[x] -› w5[x] -› c5

y consideremos la siguiente secuencia de eventos:

  1. El sincronizador recibe r4[x], y por consiguiente, asigna rl4[x] y envía r4[x] al DM.

  2. El sincronizador recibe r5[x], y por consiguiente, asigna rl5[x] y envía r5[x] al DM.

  3. El sincronizador recibe w4[x]. Debe retrasar la operación porque wl4[x] está en conflicto con rl5[x].

  4. El sincronizador recibe w5[x]. Debe retrasar la operación porque wl5[x] está en conflicto con rl4[x].

Dado que ninguna de las transacciones puede liberar el rl[x] que posee, y dado que ninguna de ellas puede continuar hasta que se asigne su wl[x], las transacciones están bloqueadas mutuamente. Este tipo de abrazo mortal ocurre comúnmente cuando una transacción busca una gran cantidad de datos que contengan un determinado valor y luego los actualiza. Esto origina la asignación de un bloqueo de lectura en cada uno de estos datos y luego la conversión de estos bloqueos en bloqueos de escritura.

El sincronizador requiere una estrategia para detectar bloqueos mutuos, de tal manera que no existan transacciones que permanezcan bloqueadas. Una estrategia es utilizar lapsos de tiempo. Si el sincronizador descubre que una transacción está esperando mucho tiempo por un bloqueo, simplemente asume que puede existir un bloqueo mutuo que involucre esta transacción y por lo tanto la cancela.


Referencias

  1. Berstein P.A., Shipman D.W., Wong W.S., Formal Aspects of Serializability in Database Concurrency Control, IEEE Trans. on Software Engineering, 1979, 5, 3, 203-216, Mayo

  2. Bjork L.A., Davies C.T., The Semantics of the Preservation and Recovery of Integrity in a Data System, Technical Report TR-02.540, IBM}, Diciembre,1972,

  3. Papadimitriou C.H., Serializability of Concurrent Database Updates, Journal of the ACM, 1979, 26, 4, 631-653, Octubre

  4. Papadimitriou C.H., The Theory of Concurrency Control, Computer Science Press, Rockville, MD, 1986

  5. Lomet D.B., Process Structuring, Synchronization and Recovery using Atomic Actions, ACM SIGPLAN Notices, 1977, 12, 3, 128-137, Marzo

  6. Devor C., Carlson C.R., Structural Locking Mechanisms and Their Effect on Database Management System Performance, Information Systems, 1982, 7, 2, 345-358.

  7. Eswaran K.P., Gary J.N., Lorie R.A., Traiger I.L., Liu M.T., The Notions of Consistency and Predicate Locks in a Database System, Comm. ACM, 1976, 19, 11, 624-633, Noviembre.

  8. Galler B.I., Bos L., A Model of Transaction Blocking in Databases, Performance Evaluation, 1983, 3, 95-122

  9. Stearns R.E., Lewis P.M., Rosenkrantz D.J., Concurrency Control for Database Systems, Proc. 17th Symp. on Foundations of Computer Sience, IEEE, 1976, 19-32

  10. Gary J.N., Lorie R.A., Putzulo G.R., Traiger J.L., Granularity of Locks and Degrees of Consistency in a Shared Database, Research Report RJ1654, IBM, 1975, Setiembre.

  11. Yannakakis M., Serializability by Locking, Journal of the ACM, 1984, 31, 2, 227-244.

  12. Mitra D., Weinberger P.J., Probabilistic Models of Database Locking: Solutions, Computational Algorithms and Asymptotics, Journal of the ACM, 1984, 31, 4, 855-878, Octubre.

  13. Potier D., Leblanc P., Analysis of Locking Policies in Database Management Systems, Comm. ACM, 1980, 23, 10, 584-593, Octubre

  14. Stearns R.E., Lewis P.M., Rosenkrantz D.J., Concurrency Control for Database Systems , Proc. 17th Symp. on Foundations of Computer Sience, IEEE, 1976, 19-32.

  15. Hadzilacos V., An Operational Model for Database System Reliability, Proc. 2nd ACM SIGACT-SIGMOD Symp. on Principles of Database Systems, 1983, Atlanta, GA, Marzo, 244-256

  16. Hadzilacos V., A Theory of Reliability in Database System, Proc. 4th ACM SIGACT-SIGMOD Symp. on Principles of Database Systems, 1986, Atlanta, GA, Abril, 217-225.

   

Otros Artículos del Autor: Fecha Publicación:
Control de Concurrencia de Transacciones en un Sistema de Base de Datos - (PARTE 2) Junio 28 del 2004
Control de Concurrencia de Transacciones en un Sistema de Base de Datos - (PARTE 1) Junio 21 del 2004


Google


Copyright © 2002-2004 Grupo Informatizate. Reservados todos los derechos.
Prohibida la reproducción total o parcial en cualquier formato sin previa autorización.
On-line desde el 27 de Noviembre del 2002