Anda di halaman 1dari 8

Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

Objetivos:
o Transacciones
o Gestión de concurrencia

Preparación de la práctica: si en el boletín 04, ejercicio 6, se llegaron a crear las tablas de marcas y
departamentos así como los usuarios y roles, hay que borrarlo todo, si no se sabe como, se puede
ejecutar el script "b05_bdborrar.sql" proporcionado, posteriormente hay que ejecutar el script
"b05_bdcrear.sql" desde el psql. Este último es un ejemplo de script que invoca a otros.

$ psql template1 –f b05_b99_bdborrar.sql;


$ psql template1 –f b05_bd00.sql 2>&1 | tee resultado.txt

1. Reglas ACID: comprobar la propiedad “Atomicidad, lo que se ejecuta en una transacción se


ejecuta todo o nada”, usando la tabla “clientes”.

por si acaso, descartamos cualquier transacción pendiente


ROLLBACK

iniciamos una transacción de dos operaciones:


BEGIN;
INSERT INTO clientes VALUES ('0007','JOSE','ALARCON','VALENCIA');
INSERT INTO clientes VALUES ('0008','PEPE','MEDINA','VALENCIA');

-- descartamos la transacción, si consultamos la tabla, no se han introducido las filas


ROLLBACK;

-- iniciamos una nueva transacción y esta vez la confirmamos:


BEGIN;
INSERT INTO clientes VALUES ('0009','JOSE','ALARCON','VALENCIA');
INSERT INTO clientes VALUES ('0010','PEPE','MEDINA','VALENCIA');
COMMIT;

vemos como ahora si que están los datos.

2. Reglas ACID: comprobar la propiedad “Consistencia - Integridad”, usando la tabla “clientes”.

Cuando se producen errores de consistencia, las transacciones se quedan latentes hasta que se termina la
transacción (con commit o rollback), si introducimos un valor repetido:

BEGIN;
INSERT INTO clientes VALUES ('0010','JUAN','PALOMO','VALENCIA');

responde que hay valores duplicados, si intentamos otra operación, en este caso correcta:
INSERT INTO clientes VALUES ('0012','JUAN12','PALOMO12','ALICANTE');

responde que esta transacción está abortada y que se ignora todo hasta el fin de la transacción, debemos marcar el
fin de la transacción para poder seguir:

ROLLBACK; (si ejecutamos COMMIT, el hace ROLLBACK)

Comprobamos que no está ninguna de las filas anteriores, iniciamos otra

BEGIN;
INSERT INTO clientes VALUES ('0012','JUAN12','PALOMO12','ALICANTE');
COMMIT;
ahora ya están los datos.

1 José Manuel Alarcón Medina


Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

3. Reglas ACID: comprobar la propiedad “Aislamiento, los cambios en una transacción no


terminada no se ven en otra sesión”, usando la tabla “clientes”. Nota, para ejecutar dos sesiones,
basta con el pgAdmin3 abrir dos ventanas SQL distintas, aunque sea con el mismo usuario.
¡Cuidado con las ventanas que abrimos de SQL o de ver los datos en pgAdmin3 porque cada
una ocupa una sesión!.

se tratará de trabajar con dos sesiones, en una se inicia una transacción donde insertamos datos pero no
confirmamos la transacción, en la otra, miramos los datos y vemos que no están, luego confirmamos la transacción y
consultamos los datos

Sesión 1:

BEGIN;
INSERT INTO clientes VALUES ('0020','NOMBRE20','APELLIDO20','CIUDAD2');
INSERT INTO clientes VALUES ('0030','NOMBRE30','APELLIDO30','CIUDAD3');

Sesión 2:

SELECT * FROM clientes WHERE dni IN ('0020', '0030');

y no salen datos

Sesión 1:

COMMIT;

Sesión 2:

SELECT * FROM clientes WHERE dni IN ('0020', '0030');

ahora si que salen.

4. Reglas ACID: comprobar la propiedad “Persistencia: se confirma una transacción, se para y


enciende el servidor y se ve si están los datos.”, usando la tabla “clientes”.

BEGIN;
INSERT INTO clientes VALUES ('0040','NOMBRE40','APELLIDO40','CIUDAD4');
INSERT INTO clientes VALUES ('0050','NOMBRE50','APELLIDO50','CIUDAD5');
\q

$ pg_ctl –D $PGDATA stop


$ pg_ctl –D $PGDATA –l $PGLOG start
$ psql –U mgrcoches curso –c “SELECT * FROM clientes WHERE dni IN ('0040', '0050')”

deben salir dos filas.

2 José Manuel Alarcón Medina


Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

5. Vamos a comprobar el funcionamiento del Control de Concurrencia Multiversión. Ahora lo


probaremos sobre la tabla “Distribución” que indica la cantidad de coches distribuidos por cada
concesionario. Necesitamos, para verlo, varias sesiones, una con el usuario mgrcoches y otra
con el usuarios modcoches. El proceso sería:

a. sesión 1: iniciamos una transacción, vemos los datos que hay, actualizamos los datos y
no confirmamos la transacción.
b. sesión 2: iniciamos la transacción y consultamos la información de distribución.
c. sesión 2: iniciar una transacción e intentar sumar 5 a la cantidad.
d. sesión 1: confirmamos los cambios y vemos qué pasa con los datos vistos desde esta
sesión y luego vamos a ver los datos vistos desde la sesión 2.
e. sesión 2: confirmamos los cambios

Antes de empezar, si nos conectamos con pgAdmin, vamos a ver las sesiones, nos fijamos sobre todo en las sesiones
de los usuarios que hemos conectado:

6075 1 0 12:48 /home/postgres/pgsql/bin/postmaster -D /home/postgres/data


6077 6075 0 12:48 postgres: writer process
6078 6075 0 12:48 postgres: stats buffer process
6079 6078 0 12:48 postgres: stats collector process
6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle
6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) idle
6096 6075 0 13:01 postgres: postgres postgres 192.168.159.1(4976) idle
6097 6075 0 13:01 postgres: postgres curso 192.168.159.1(4977) idle
6098 6075 0 13:01 postgres: postgres curso 192.168.159.1(4978) idle
6100 6075 0 13:04 postgres: postgres postgres 192.168.159.1(4986) idle

vemos que hay 2 sesiones por usuario, la propia al conectar, y la que se ha abierto al abrir el SQL interactivo.

Paso a)
Sesión 1:

COMMIT ó ROLLBACK; -- por si hemos dejado algo pendiente


BEGIN;
SELECT * FROM distribucion;

CIFC;CODCOCHE;CANTIDAD
"0001";"001";3
"0001";"005";7
"0001";"006";7
"0002";"006";5
"0002";"008";10
"0002";"009";10
"0003";"010";5
"0003";"011";3
"0003";"012";5
"0004";"013";10
"0004";"014";5
"0005";"015";10
"0005";"016";20
"0005";"017";8
"0006";"019";3

3 José Manuel Alarcón Medina


Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

UPDATE distribucion SET cantidad = cantidad * 2;

si vemos los datos, los veremos multiplicados por 2.

Paso b)
Sesión 2:

ROLLBACK;
SELECT * FROM distribucion;

y vemos los datos antiguos, lo cual es correcto, no hay bloqueo y se han preservado los cambios. Vamos a ver las
sesiones:

$ ps –ef | grep postgres

6075 1 0 12:48 /home/postgres/pgsql/bin/postmaster -D /home/postgres/data


6077 6075 0 12:48 postgres: writer process
6078 6075 0 12:48 postgres: stats buffer process
6079 6078 0 12:48 postgres: stats collector process
6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle
6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle in transaction
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) idle
6096 6075 0 13:01 postgres: postgres postgres 192.168.159.1(4976) idle
6097 6075 0 13:01 postgres: postgres curso 192.168.159.1(4977) idle
6098 6075 0 13:01 postgres: postgres curso 192.168.159.1(4978) idle
6100 6075 0 13:04 postgres: postgres postgres 192.168.159.1(4986) idle

postgres=> SELECT transaction,pid,mode FROM pg_locks;

transaction | pid | mode


-------------+------+------------------
9630 | 6082 | ExclusiveLock
9630 | 6082 | AccessShareLock
9630 | 6082 | RowExclusiveLock

la sesión donde estamos modificando, está a la espera de terminar la transacción.

Paso c)
Sesión 2:

BEGIN;
UPDATE distribucion SET cantidad = cantidad + 5;

parece que no da error, pero si nos fijamos, se está ejecutando la consulta, esto es debido a que hay un bloqueo
porque intentamos modificar los mismos registros que en la sesión 1 que no están confirmados.

6075 1 0 12:48 /home/postgres/pgsql/bin/postmaster -D /home/postgres/data


6077 6075 0 12:48 postgres: writer process
6078 6075 0 12:48 postgres: stats buffer process
6079 6078 0 12:48 postgres: stats collector process
6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle
6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle in transaction
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) UPDATE waiting
6096 6075 0 13:01 postgres: postgres postgres 192.168.159.1(4976) idle
6097 6075 0 13:01 postgres: postgres curso 192.168.159.1(4977) idle
6098 6075 0 13:01 postgres: postgres curso 192.168.159.1(4978) idle
6100 6075 0 13:04 postgres: postgres postgres 192.168.159.1(4986) idle

transaction | pid | mode


-------------+------+------------------
11124 | 6086 | ExclusiveLock
11124 | 6086 | RowExclusiveLock
11124 | 6086 | ExclusiveLock
9630 | 6082 | ExclusiveLock
11124 | 6086 | ShareLock
9630 | 6082 | AccessShareLock
9630 | 6082 | RowExclusiveLock
11124 | 6086 | AccessShareLock
11124 | 6086 | RowExclusiveLock

4 José Manuel Alarcón Medina


Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

Paso d)
Sesión 1: confirmamos los cambios

COMMIT;

si miramos los datos de esta sesión, son los esperados, la cantidad multiplicada por dos, y si nos fijamos, nada más
confirmar esta transacción, se ha terminado la que había en la sesión 2, si vamos a ver los datos de esta sesión,
veremos que nos sale los datos de la sesión 1 sumados con 5, es decir, se han ejecutado las 2 transacciones, una
detrás de la otra, como si hubiéramos hecho

update distribucion set cantidad = cantidad*2 + 5;

6075 1 0 12:48 /home/postgres/pgsql/bin/postmaster -D /home/postgres/data


6077 6075 0 12:48 postgres: writer process
6078 6075 0 12:48 postgres: stats buffer process
6079 6078 0 12:48 postgres: stats collector process
6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle
6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) idle in transaction
6096 6075 0 13:01 postgres: postgres postgres 192.168.159.1(4976) idle
6097 6075 0 13:01 postgres: postgres curso 192.168.159.1(4977) idle
6098 6075 0 13:01 postgres: postgres curso 192.168.159.1(4978) idle
6100 6075 0 13:04 postgres: postgres postgres 192.168.159.1(4986) idle

transaction | pid | mode


-------------+------+------------------
11124 | 6086 | ExclusiveLock
11124 | 6086 | AccessShareLock
11124 | 6086 | RowExclusiveLock

Paso e)
Sesion 2: confirmar

COMMIT;

Los datos quedan así y las sesiones y bloqueos quedan como al principio, no hay bloqueos ni sesiones con esperas.

CIFC;CODCOCHE;CANTIDAD
"0001";"001";11
"0001";"005";19
"0001";"006";19
"0002";"006";15
"0002";"008";25
"0002";"009";25
"0003";"010";15
"0003";"011";11
"0003";"012";15
"0004";"013";25
"0004";"014";15
"0005";"015";25
"0005";"016";45
"0005";"017";21
"0006";"019";11

Este comportamiento es el que soporta por defecto PostgreSQL, nivel de aislamiento de lecturas confirmadas,
“read_commited”. El valor por defecto de este nivel se ve en el parámetro “default_transaction_isolation”, aunque
se puede cambiar en una sesión ejecutando:

SET TRANSACTION ISOLATION LEVEL <NIVEL>;

donde nivel se puede poner como “read_commited” o “serializable”.

5 José Manuel Alarcón Medina


Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

6. Vamos a probar la Serialización. Vamos a trabajar sobre la tabla clientes, para el cliente cuyo
dni es ‘0001’. Intentaremos cambiar su nombre de ‘LUIS’ a ‘LUISA’ en ambas sesiones, el
proceso sería, antes de empezar las transacciones, activar el modo de transacciones a
‘serializable’:

a. sesión 1: iniciamos una transacción, vemos los datos que hay.


b. sesión 2: hacemos lo mismo.
c. sesión 1: actualizamos el nombre y no confirmamos la transacción.
d. sesión 2: hacemos lo mismo.
e. sesión 1: confirmamos la transacción y vemos los datos
f. sesión 2: hacemos lo mismo.

Paso a)
Sesión 1:

COMMIT ó ROLLBACK; -- por si hemos dejado algo pendiente


SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT nombre FROM clientes WHERE dni = '0001';

devuelve ‘LUIS’

6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle


6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle in transaction
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) idle

transaction | pid | mode


-------------+------+-----------------
12863 | 6082 | ExclusiveLock
12863 | 6082 | AccessShareLock
12863 | 6082 | AccessShareLock

simplemente por SELECT ya se hace un bloqueo compartido

Paso b) Hacemos lo mismo que en paso “a”


Sesión 2:

COMMIT ó ROLLBACK; -- por si hemos dejado algo pendiente


SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT nombre FROM clientes WHERE dni = '0001';

devuelve ‘LUIS’

6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle


6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle in transaction
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) idle in transaction

transaction | pid | mode


-------------+------+-----------------
12863 | 6082 | ExclusiveLock
12867 | 6086 | AccessShareLock
12863 | 6082 | AccessShareLock
12867 | 6086 | ExclusiveLock
12863 | 6082 | AccessShareLock
12867 | 6086 | AccessShareLock

ahora son las dos sesiones las que están esperando a terminar la transacción, ambas realizan un bloqueo
compartido.

paso c) Intentamos actualizar el nombre en la sesión 1


UPDATE clientes SET nombre = 'LUISA' WHERE dni = '0001';

6 José Manuel Alarcón Medina


Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

los procesos muestran lo mismo que antes, y los bloqueos:

transaction | pid | mode


-------------+------+------------------
12863 | 6082 | ExclusiveLock
12867 | 6086 | AccessShareLock
12863 | 6082 | AccessShareLock
12863 | 6082 | RowExclusiveLock
12867 | 6086 | ExclusiveLock
12863 | 6082 | AccessShareLock
12867 | 6086 | AccessShareLock

paso d) hacemos lo mismo en la sesión 2

UPDATE clientes SET nombre = 'LUISA' WHERE dni = '0001';

6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle


6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle in transaction
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) UPDATE waiting

transaction | pid | mode


-------------+------+------------------
12863 | 6082 | ExclusiveLock
12867 | 6086 | AccessShareLock
12867 | 6086 | RowExclusiveLock
12863 | 6082 | AccessShareLock
12863 | 6082 | RowExclusiveLock
12867 | 6086 | ShareLock
12867 | 6086 | AccessShareLock
12867 | 6086 | RowExclusiveLock
12867 | 6086 | ExclusiveLock
12867 | 6086 | ExclusiveLock
12863 | 6082 | AccessShareLock
12867 | 6086 | AccessShareLock

paso e) la sesión 2 se queda en espera y ya tenemos ¡3 bloqueos de fila exclusivos! estamos en peligro de que haya
un bloqueo mortal (deadlock).

Confirmamos en la sesión 1:

COMMIT;

6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle


6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) idle in transaction

transaction | pid | mode


-------------+------+------------------
12867 | 6086 | AccessShareLock
12867 | 6086 | RowExclusiveLock
12875 | 6170 | ExclusiveLock
12867 | 6086 | ExclusiveLock
12875 | 6170 | AccessShareLock
12867 | 6086 | AccessShareLock

paso f) y confirmamos en la sesión 2:

COMMIT;

6081 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4946) idle


6082 6075 0 12:48 postgres: mgrcoches curso 192.168.159.1(4947) idle
6085 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4963) idle
6086 6075 0 12:56 postgres: modcoches curso 192.168.159.1(4964) idle

Y no hay bloqueos => ¡no se ha producido el bloqueo mortal!.

7 José Manuel Alarcón Medina


Prácticas de Administración de PostgreSQL Boletín 05 - Soluciones

7. Ahora pasamos al esquema “empresa”, donde los departamentos pueden tener un jefe que es un
empleado, y los empleados tienen un jefe que también es un empleado, así como pertenecen a
un departamento. Normalmente, los jefes de los departamentos, son empleados que ya existen,
pero imaginemos que demos de alta un departamento que tiene un empleado nuevo, con lo que
hay que darlo de alta también, realizar esta operación para estos datos:

numdept nomdept localidad jefe


50 ‘INFORMATICA’ ‘VALENCIA’ 5000

numemp nomemp trabajo jefe f_alta salario comision numdept grado


5000 ‘JEFE50’ RESPONSABLE '2006-10-26' 21300.00 NULL 50 1

¿Es posible realizar esto en una transacción, dando de alta primero el departamento y luego el
empleado? ¿Qué tendríamos que hacer para poder realizar la transacción?.

BEGIN;
INSERT INTO departamentos VALUES ('50','INFORMATICA','VALENCIA','5000');

y no nos deja porque nos dice:.

ERROR: insert or update on table "departamentos" violates foreign key constraint


"fk_jefedepartamentos"
DETAIL: Key (jefe)=(5000) is not present in table "empleados".

luego, con la estructura de las tablas no se puede hacer, la transacción falla y se deshace. Si no iniciamos la
transacción, ejecutamos el INSERT directamente, también nos da el mensaje de error.

Para arreglar el tema, debemos de redifinir las constraints de departamentos para que sean “DEFERRABLE”:

ALTER TABLE departamentos DROP CONSTRAINT fk_jefedepartamentos;


ALTER TABLE departamentos ADD CONSTRAINT fk_jefedepartamentos
FOREIGN KEY (jefe) REFERENCES empleados
DEFERRABLE INITIALLY DEFERRED;

y ahora probamos lo que queremos que funcione, empezamos otra vez:


ROLLBACK;
BEGIN;
INSERT INTO departamentos VALUES ('50','INFORMATICA','VALENCIA','5000');

ahora nos ha dejado hacer la inserción e incluso podemos consultar que está en la tabla, seguimos:

INSERT INTO empleados (numemp, nomemp, trabajo, jefe, f_alta, salario, comision, numdept, grado)
VALUES (5000, 'DARIO ROIG', 'RESPONSABLE', NULL, '2006-10-26', 35000.00, NULL, 50, 1);

COMMIT;

8. Si la clave ajena la hubiésemos definido como “DEFERRABLE INITIALLY IMMEDIATE”,


¿Qué tendríamos que hacer para poder ejecutar la transacción?

Es muy simple, como se puede cambiar dinámicamente si es “Deferrable”, ejecutamos, antes de hacer los cambios y
después de empezar la transacción:

BEGIN;
SET CONSTRAINTS fk_jefedepartamentos DEFERRED;

y así, este cambio permanece mientras dura la transacción.

8 José Manuel Alarcón Medina

Anda mungkin juga menyukai