Estudio de traza de un Deadlock

Hoy vamos a ver como obtener algo mas de información de una incidencia que probablemente tengamos a menudo.

Los deadlocks no implican mal funcionamiento de la base de datos

Lo primero que tenemos que tener muy claro es que un deadlock no es un mal funcionamiento de la base de datos, un deadlock (la traducción posiblemente sea interbloqueo) es una situación en la que dos o mas usuarios están esperando cada uno a un recurso bloqueado por el otro.
La manera en la que Oracle soluciona esta situación es rolling back una de las sentencias implicadas en el deadlock, al liberar uno de estos bloqueos la otra finaliza su solicitud.
Cuando esta situación ocurre, Oracle deja un fichero de traza en el $DIAG_DEST , que nos indica cuales eran los procesos y sentencias implicados. El análisis de esa traza es lo que vamos a mirar hoy.

Deadlock Graph

Seguramente el apartado mas importante de la traza sea el llamado «deadlock graph», estas dos líneas que parecen tan crípticas son las que mas información nos van a dar sobre el bloqueo.

Deadlock graph:
--------Blocker(s)------- --------Waiter(s)--------
Resource Name           process session_holds waits process session_holds waits
TX-000e001a-002dd880     65               414    X       24          9       X
TX-00090006-01e831ca     24                 9    X        65        414      X

Viendo el tipo de bloqueo en «resource_name» y los distintos waits podremos obtener el tipo de bloqueo que ha sido, para ello tenemos una tabla maestra en la nota de soporte How to Identify ORA-00060 Deadlock Types Using Deadlock Graphs in Trace (Doc ID 1507093.1).
En nuestro caso, por ejemplo,tendríamos según soporte un claro caso de bloqueo de aplicación.
Bloqueo TX X X

Información de la sesión

Otro apartado interesante es el de la información de la sesión. En este apartado nos indica de manera mas sencilla cuales son las sesiones implicadas y cuales son los objectos en los que hemos tenido el problema .
Mediante esta información podemos buscar los objetos por los que se han causado el interbloqueo

session 414: DID 0001-0041-000592FB session 9: DID 0001-0018-00004FD9 
session 9: DID 0001-0018-00004FD9 session 414: DID 0001-0041-000592FB
Rows waited on:
Session 414: obj - rowid = 001272E0 - AAExowAQAAAACF5AAUv (dictionary objn - 1209056, file - 1024, block - 8569, slot - 20)
Session 9:   obj - rowid = 001272E0 - AAExowAQAAAACMNAAJ  (dictionary objn - 1209056, file - 1024, block - 8973, slot - 9)

SQL implicado

Finalmente llegamos al apartado que puede ser mas clarificador de cara al equipo de desarrollo encargado de depurar el código.
Este tercer bloque nos amplia la información de las sesiones implicadas, contándonos esquema, terminal y las consultas implicadas en el deadlock

----- Information for the OTHER waiting sessions -----
Session 9:
sid: 9 ser: 37801 audsid: 206118217 user: 103/SCHEMA1    flags: (0x45) USR/- flags_idl: (0x1) BSY/////- flags2: (0x40009) //INC  pid: 24 O/S info: user: SYSTEM, term: SERVERTEST, ospid: 12260  image: ORACLE.EXE (SHAD)

client details:
O/S info: user: launcherusr, term: , ospid: 13041768  machine: client2 program: schema2@client2 (TNS V1-V3)  application name: schema2@client2 (TNS V1-V3), hash  alue=54028978

current SQL:
UPDATE SCOTT2.TIPO_COCHE  SET COLOR = :1, CILINDRADA = :2 WHERE MATRICULA = :3 AND ANCHO = :4
----- End of information for the OTHER waiting sessions -----

Information for THIS session:
----- Current SQL Statement for this session (sql_id=8zqxt1a6d7ts1) -----
UPDATE SCOTT2.TIPO_COCHE SET TIPO = :1, PERSONA = :2 WHERE MATRICULA = :3 AND LARGO = :4

El fichero de traza es mucho mas amplio, pero, como habéis podido ver, mediante el estudio de la cabecera de la traza podemos recopilar mucha información para poder depurara el código de aplicación para que no vuelva a ocurrir

Como siempre, tenemos mas información en soporte, en las notas

  • Master Note for Database Error ORA-00060 «deadlock detected while waiting for resource» (Doc ID 1509919.1)
  • Master Note: Locks, Enqueues and Deadlocks (Doc ID 1392319.1)
  • How to Identify ORA-00060 Deadlock Types Using Deadlock Graphs in Trace (Doc ID 1507093.1)

Creando grupos de consumidores. A la caza del bloqueo II

Hasta el momento, en la entrada   A la caza del bloqueo I   teníamos  un  plan de recursos llamado NO_LOCKS que mataba aquellos procesos que estaban mas de 5 segundos bloqueando otra consulta. Este plan de consumidores no era muy util , ya que podía provocar estragos matando indiscriminadamente cualquier bloqueo de mas de 5 segundos, con lo que hoy daremos un paso mas para hacer de ese plan de recursos algo mas útil.

En esta entrada vamos a crear un grupo de consumidores, este grupo de consumidores nos permitirá afinar el perfil de los usuarios sobre los que queremos aplicar nuestro pal de recursos.

El grupo de consumidores lo vamos a llamar USER_NO_LOCK_ALLOW y lo crearemos de la siguiente manera:

BEGIN
 dbms_resource_manager.clear_pending_area();
 dbms_resource_manager.create_pending_area();
 dbms_resource_manager.create_consumer_group(
consumer_group =>'USER_NO_LOCK_ALLOW','Grupo de consumidores alos que no permitiremos bloqueos '
);
 dbms_resource_manager.submit_pending_area();
END;

 

Una vez tenemos nuestro grupo de consumidores creado, es el momento de decidir que usuarios queremos tener dentro de el y cuales no. Oracle 11g nos da muchas opciones para seleccionar este grupo de usuarios, algunas de ellas son:

Por esquema

Si quisieramos añadir a los usuarios del esquema «esquema1» a este grupo de consumidores usariamos el parámetro dbms_resource_manager.oracle_user  del paquete dbms_resource_manager

BEGIN
dbms_resource_manager.clear_pending_area();
dbms_resource_manager.create_pending_area();
dbms_resource_manager.set_consumer_group_mapping(
dbms_resource_manager.oracle_user,'esquema1','USER_NO_LOCK_ALLOW'
);
dbms_resource_manager.submit_pending_area();
 END;

Por maquina cliente

Supongamos que queramos aplicar incluir en nuestro grupo de consumidores solamente las sesiones que se ejecutan desde el servidor cliente «WORKGROUP\client1», para ello  usaríamos dbms_resource_manager.client_machine

BEGIN
dbms_resource_manager.clear_pending_area();
dbms_resource_manager.create_pending_area();
dbms_resource_manager.set_consumer_group_mapping(
dbms_resource_manager.client_machine,'WORKGROUP\MAQUINA1','USER_NO_LOCK_ALLOW'
);
dbms_resource_manager.submit_pending_area();
END;

Por programa

Para separar por programa usaremos la llamada dbms_resource_manager.client_program

BEGIN
dbms_resource_manager.clear_pending_area();
dbms_resource_manager.create_pending_area();
dbms_resource_manager.set_consumer_group_mapping(
dbms_resource_manager.client_program,'PROGRAMA1','USER_NO_LOCK_ALLOW'
);
dbms_resource_manager.submit_pending_area();
END;

Como podeis ver, las posibilidades son muy grandes, en esta entrada nos hemos centrado en capturar sesiones por parámetros de login, pero la funcion dbms_resource_manager.set_consumer_group_mapping permite también seleccionar usuarios por atributos de runtime-.

La lista de las opciones la podeis encontrar en la documentación del paquete
dbms_resource_manager.set_consumer_group_mapping, pero a groso modo se puede resumir en:
Login Attributes

  • oracle_user
  • service_name
  • client_os_user
  • client_program
  • client_machine

 Runtime Attributes

  • module_name
  • module_name_action
  • service_module
  • service_module_action

Ahora solamente nos quedará el incluir este grupo de consumidores en nuestro plan de recursos, indicándole que son los consumidores de este grupo a los que no se les debe permitir el bloquear al resto de los usuarios . Pero esto será en otra entrada.

Gestor de recursos. A la caza del bloqueo I

Hoy  vamos a abordar los problemas de bloqueos desde otro punto de vista.

A pesar de que los bloqueos sonun problema propio de  la aplicación y debe de ser desde esta donde se solucione el evitar sesiones bloqueantes, un código de  aplicacion defectuoso  puede llegar a tirar abajo nuestra instancia , con lo que deberemos de acabar con este tipo de sesiones antes de que afecten a la disponibilidad del sistema.

Oracle nos da la opcion de limitar el tiempo en el que un usuario está IDLE a la vez que  bloquea otra sesion con el parámetro NEW_MAX_IDLE_BLOCKER.

Esta es la primera de un grupo de entradas en las que utilizaremos el gestor de recursos de Oracle para llevar acabo workarrounds que nos solucionen este problema.

Lo primero que haremos es crear un plan al que llamaremos «NO_LOCKS», para ello lo mas sencilllo es copiar el DEFAULT_PLAN desde el EM, si por el contrario queremos hacerlo desde linea de sqlplus tenemos que:

  1. Crear una pending area (si lo hacemos desde sqlplus)
  2. Crear el plan de recursos
  3. Validar el pending area
  4. Enviar/presentar  (submit) esta pending area
BEGIN
dbms_resource_manager.clear_pending_area();
dbms_resource_manager.create_pending_area();
dbms_resource_manager.create_plan( plan => 'NO_LOCKS',
 comment => 'Plan para eliminar bloqueos desde el gestor de recursos, copiado de DEFAULT_PLAN'
);
dbms_resource_manager.create_plan_directive(
    plan => 'NO_LOCKS',
    group_or_subplan => 'SYS_GROUP',   
    comment => 'Asignamos 75% de cpu al grupo SYS ',
    mgmt_p1 => 75
);
dbms_resource_manager.create_plan_directive(
    plan => 'NO_LOCKS',
    group_or_subplan => 'DEFAULT-CONSUMER_GROUP',   
    comment => 'Asignaremos el 90% de CPU al grupo de consumidores por defecto ',
    mgmt_p2 => 90
);
dbms_resource_manager.create_plan_directive(
    plan => 'NO_LOCKS',
    group_or_subplan => 'OTHER_GROUPS',   
    comment => 'Este grupo debe de aparecer ',
    mgmt_p2 => 5
);
dbms_resource_manager.create_plan_directive(
    plan => 'NO_LOCKS',
    group_or_subplan => 'ORA$DIAGNOSTICS',   
    comment => 'Asignamos 5% de cpu al grupo ORA$DIAGNOSTICS en el segundo nivel ',
    mgmt_p2 => 5
 );
 DBMS_RESOURCE_MANAGER.VALIDATE_PENDING_AREA();
DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA();
END;

Con esto tenemos  un nuevo plan llamado «NO_LOCKS» que es similar al DEFAULT_PLAN.

Lo que vamos ha hacer ahora es modificar el subplan que se aplica a todos los usuarios (DEEFAULT_CONSUMER_GROUP) para que no los deje bloquear recursos mas de 5 segundos.

BEGIN
dbms_resource_manager.clear_pending_area();
dbms_resource_manager.create_pending_area();
dbms_resource_manager.update_plan_directive(
    plan => 'NO_LOCKS',
    group_or_subplan => 'DEFAULT_CONSUMER_GROUP',   
    new_comment => 'Subplan para bloqueadores, similar a DEFAULT_CONSUMER_GROUP pero  limitado',
    new_max_idle_blocker_time=>5
    );
DBMS_RESOURCE_MANAGER.VALIDATE_PENDING_AREA;
DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA;
END;

Si miramos los cambios desde el EM podremos ver como   esta el parámetro

Plan NO_LOCK

Ahora solamente nos quedará activar el nuevo plan y probarlo

Esto lo comprobamos con

SQL>ALTER SYSTEM SET RESOURCE_MANAGER_PLAN = 'NO_LOCKS';

SQL> SELECT VALUE FROM V$PARAMETER  WHERE name = 'resource_manager_plan';

VALUE
--------------------------------------------------------------------
NO_LOCKS

Ahora vamos ha hacer la prueba de que funciona

Veamos en que grupo de consumidores está el usuario HR

SQL> select initial_rsrc_consumer_group from dba_users where username='HR';

INITIAL_RSRC_CONSUMER_GROUP
------------------------------
DEFAULT_CONSUMER_GROUP

 

Nos conectamos con el usuario HR y bloquearemos la tabla REGIONS

SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'HH24:MI:SS';
Session altered.
SQL>  select sysdate from dual;
SYSDATE
--------
12:08:13
SQL> select * from  regions where  REGION_ID=1 for update;
 REGION_ID REGION_NAME
---------- -------------------------
         1 Europe
SQL>  select sysdate from dual;
SYSDATE
--------
12:30:18

En el segundo select sysdate vemos como,  pesar de que la tabla esta bloqueada, y que han pasado 15 segundos el gestor de recursos no nos ha hechado.¿habremos hecho algo mal?

La respuesta es que no hemos hecho nada mal, lo que está ocurriendo es que la consulta que hemos ejecutado esta bloqueando algo, pero no esta bloqueando  a nadie .

Que ocurre si intentamos ejecutar una consulta en otra sesion que  se vea bloqueada por esta?

En el momento en l que el gestor de recursos detecta que nuestra primera sesion esta bloqueando a otra empieza a contar los segundos que nuestra primera consulta esta «idle».Al llegar a los 5 segundos matará nuestra consulta bloqueante y liberará sus recursos.

Si tras esos 5 segundos  volvemos a recargar la ventana del EM  veremos que no hay bloqueos, y  si nos vamos a la ventana donde hemos ejecutado nuestra consulta bloquante nos encontraremos con el error:

ORA-02396: ha excedido el tiempo máximo de inactividad, vuelva a conectarse

Con lo que , de manera algo brusca y desde la base de datos hemos conseguido acabar con todos los bloqueos de usuario que estén inactivas mas de 5 segundos.

 


LLegados a este punto tenemos a TODOS los usuarios de la base de datos sin poder llevar a cabo un bloqueo de mas de 5 segundos en cualquier hora del día.

Esto nos va a generar muchos problemas a la hora de los procesos batch e incluso en alguna transaccion pesada, con lo que lo mejor que podemos hacer el volverlo atrás .

El único valor de esta entrada es como ejercicio didactico, asi que, mas nos valdría volver a activar en la base de datos el DEFAULT_PLAN o el INTERNAL_PLAN

En las siguientes entradas afinaremos este plan NO_LOCKS para limitarlo por esquemas y por franja horaria, haciendo de la limitación NEW_MAX_IDLE_BLOCKER_TIME  algo util.