Bloqueos causados por transacciones no confirmadas de Oracle

La empresa del autor instaló recientemente un sistema que utiliza middleware para conectarse a la base de datos de Oracle. Después de un período de uso, el sistema deja de responder. Encontró que el problema radica en 2 puntos:

1. Después de que un subproceso en el middleware ejecuta la instrucción Eliminar, ha estado en un estado de espera. No hay COMMIT para enviar la transacción. Se impone un bloqueo de fila en la tabla y el subproceso no se puede reutilizar (el número total de subprocesos en el middleware es limitado)

2. Después de aplicar un bloqueo de fila a la tabla, los subprocesos de middleware posteriores se bloquearán si necesitan modificar la fila.

Los dos factores anteriores continúan ocurriendo, lo que eventualmente conduce al agotamiento de la cantidad de subprocesos de middleware y el sistema deja de responder.

Lo siguiente se dividirá en dos partes, la primera parte es dar directamente la declaración SQL para juzgar las fallas anteriores, la segunda parte es reproducir el problema haciendo experimentos.

1. Juzgue directamente el bloqueo de fila de la tabla causado por la transacción no comprometida

1.1 Determine qué SESIÓN ejecuta DML (Insertar/Actualizar/Eliminar) pero no envía (Confirmar), lo que provoca bloqueos de fila

--找到修改了数据,但是未提交的Session,选择WAIT_CALSS='Idle',也就是Session处于休息状态,但是有锁定的表
----找到修改了数据,但是未提交的Session,选择WAIT_CALSS='Idle',也就是Session处于休息状态,但是有锁定的表
 SELECT A.SID,
       A.SERIAL#,
       A.USERNAME,
       A.EVENT,
       A.WAIT_CLASS,
       A.SECONDS_IN_WAIT,
       A.PREV_EXEC_START,
       b.LOCKED_MODE,
       C.OWNER,
       C.OBJECT_NAME,
       C.OBJECT_TYPE
  FROM V$SESSION A
 INNER JOIN V$LOCKED_OBJECT B ON A.SID = b.SESSION_ID
 INNER JOIN DBA_OBJECTS C ON B.OBJECT_ID = c.OBJECT_ID
 WHERE A.WAIT_CLASS = 'Idle'
   AND A.SECONDS_IN_WAIT > 10--/*SESSION空闲后一段时间还锁定的才算有问题,这里随便给了个数值10秒*/

Si confirma que efectivamente hay un problema con estas SESIONES, puede eliminarlas directamente y pasar el SID y el N.° DE SERIE de la instrucción anterior.

ALTER SYSTEM KILL SESSION 'SID,SERIAL#'

Nota: ORACLE tiene un lugar muy interesante, es decir, debe completar "# DE SERIE" cuando ELIMINAR SESIÓN, para evitar obtener un SID cuando acaba de consultar, y luego planea matarlo. La sesión perdida termina la desconexión, y luego la nueva sesión reutiliza este SID, es decir, para evitar el homicidio involuntario, el SERVIDOR SQL puede MATAR directamente.

1.2 Determinar qué SESIÓN está bloqueada y ubicar quién está bloqueado, qué tabla y qué fila causó el bloqueo

----当SESSION被阻塞,通过ROW_WAIT_OBJ#,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#,ROW_WAIT_ROW#这几个字段找到ROWID,然后通过ROWID找到被锁定的记录
SELECT BLOCKING_SESSION,
       SID,
       SERIAL#,
       AUDSID,
       PADDR,
       USER#,
       USERNAME,
       EVENT,
       WAIT_CLASS,
       SECONDS_IN_WAIT,
       ROW_WAIT_OBJ#,
       ROW_WAIT_FILE#,
       ROW_WAIT_BLOCK#,
       ROW_WAIT_ROW#,
       BLOCKING_SESSION_STATUS,
       BLOCKING_INSTANCE,
       C.OWNER,
       C.OBJECT_NAME,
       C.OBJECT_TYPE,
       dbms_rowid.rowid_create(1,
                               ROW_WAIT_OBJ#,
                               ROW_WAIT_FILE#,
                               ROW_WAIT_BLOCK#,
                               ROW_WAIT_ROW#)
  FROM V$SESSION A
 INNER JOIN V$LOCKED_OBJECT B ON A.SID = b.SESSION_ID
 INNER JOIN DBA_OBJECTS C ON B.OBJECT_ID = c.OBJECT_ID
 WHERE BLOCKING_SESSION IS NOT NULL;

A través de OBJECT_NAME y ROWID devueltos arriba, se sabe que el bloqueo es causado por esa fila.

SELECT * FROM 前面返回的表名称
where ROWID=前面返回的ROWID

ejemplo:

SQL> select * from test.TESTLOCK where rowid='AAASbJAAEAAAACvAAB';
        ID NAME
---------- --------------------------------------------------------------------------------
         2 kkkkkkkkkkkkkk

1.3 Qué sesiones están bloqueadas, cuáles están bloqueadas y la instrucción Sql ejecutada

SELECT s.BLOCKING_SESSION,
       l.session_id sid,
       s.serial#,
       l.locked_mode,
       l.oracle_username,
       s.user#,
       l.os_user_name,
       s.machine,
       s.terminal,
       a.sql_text,
       a.action
  FROM v$sqlarea a, v$session s, v$locked_object l
 WHERE l.session_id = s.sid
   AND s.prev_sql_addr = a.address
 ORDER BY sid, s.serial#;

1.4 Consultar qué mesa está bloqueada y quién la bloqueó

select s.sid,
           s.serial#,
           lo.oracle_username,
           lo.os_user_name,
           ao.object_name  as 被锁表名称table_locked_name,
           s.username,
           s.schemaname,
           s.osuser,
           s.process,
           s.machine,
           s.terminal,
           lo.locked_mode
      from v$locked_object lo, all_objects ao, v$session s
     where ao.object_id = lo.object_id
       and lo.session_id = s.sid
     order by s.sid asc;

2. Reproducir todo el proceso a través del proceso experimental.

En primer lugar, la base de datos Oracle es de bloqueo de escritura, y la lectura y la escritura no son de bloqueo, es decir, si las siguientes dos declaraciones se ejecutan en ventanas diferentes (SESSION), ya que ambas actualizan la misma fila, si la primera uno se ejecuta Sin COMMIT, la ejecución posterior siempre estará bloqueada:

--第一个窗口执行如下语句
UPDATE TESTLOCK
SET AAA=11
WHERE AAA=1

--第二个窗口执行以下语句
UPDATE TESTLOCK
SET AAA=12
WHERE AAA=1

Comencemos nuestro experimento, la versión de Oracle es 11G, con las siguientes preguntas:

1) ¿Cómo sabe que una conexión ha modificado datos, pero no se ha enviado, lo que da como resultado un bloqueo en la tabla?
2) Para la pregunta 1, ¿puede saber qué registro en qué tabla está bloqueado específicamente?

Primero, creamos tres ventanas en el cliente PS/SQL, que son: ventana de prueba 1, ventana de prueba 2 y ventana de observación. El experimento se registra con la cuenta "TEST". Para evitar que otras SESIONES interfieran con nuestro experimentos, muchas declaraciones agregan "WHERE USERNAME='TEST'" como una limitación, si desea repetir el proceso del experimento, debe modificar esta parte de la declaración.

2.1 Cree una tabla de prueba en la ventana de prueba 1 e inserte 10 registros

/*初始化测试表*/

--建立一个测试表
create table TESTLOCK
(
  aaa number not null,
  bbb nvarchar2(10) not null,
  ccc nvarchar2(10) not null
);
create INDEX PK_TESTLOCK on TESTLOCK (aaa);

 
--随便插入点数据
INSERT INTO TESTLOCK VALUES ('1','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('2','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('3','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('4','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('5','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('6','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('7','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('8','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('9','BBB','CCC');
INSERT INTO TESTLOCK VALUES ('10','BBB','CCC');

 
--然后我们的表里面就有了一些数据
SELECT * FROM TESTLOCK;
COMMIT;

2.2 En la ventana de prueba 2, consulte la tabla de prueba, puede ver los 10 registros agregados en el paso 1

SELECT * FROM TESTLOCK;

inserte la descripción de la imagen aquí

2.3 Observar la sesión actual y los bloqueos de mesa en la ventana de observación

Aquí usamos tres intentos del sistema Oracle:
V$SESSION, V$LOCK, V$LOCKED_OBJECT

--当前的Session情况
SELECT SID,SERIAL#,AUDSID,PADDR,USER#,USERNAME,EVENT,WAIT_CLASS,SECONDS_IN_WAIT,ROW_WAIT_OBJ#,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#,ROW_WAIT_ROW#,      BLOCKING_SESSION_STATUS,BLOCKING_INSTANCE,BLOCKING_SESSION
FROM V$SESSION
WHERE USERNAME='TEST';

--当前TESTLOCK表锁的情况
SELECT * FROM V$LOCK
WHERE TYPE='TM'
AND ID1=(SELECT OBJECT_ID FROM DBA_OBJECTS
                 WHERE OBJECT_NAME='TESTLOCK');

SELECT XIDUSN,XIDSLOT,XIDSQN,OBJECT_ID,SESSION_ID,ORACLE_USERNAME,PROCESS,LOCKED_MODE
FROM V$LOCKED_OBJECT
WHERE OBJECT_ID=(SELECT OBJECT_ID FROM DBA_OBJECTS
                 WHERE OBJECT_NAME='TESTLOCK');

Se puede ver en la figura a continuación que la cuenta TEST ha generado un total de 4 sesiones, que son el propio PLSQL conectado a la base de datos y las tres ventanas que creamos: nos
inserte la descripción de la imagen aquí
enfocamos en varios campos:

EVENTO: el dato o evento que la sesión de Oracle está esperando

WAIT_CLASS: el nombre del evento de espera

blocking_session_status: Si el campo blocking_session_status es VÁLIDO, significa que la SESIÓN está bloqueada

blocking_session: Qué sesión está bloqueada por

A través de las cuatro observaciones anteriores, ninguna sesión está bloqueada, las tres sesiones actuales están esperando mensajes del cliente (EVENTO = mensaje de red SQL del cliente, WAIT_CLASS = inactivo) y la sesión restante está enviando mensajes al cliente (EVENTO = mensaje de red SQL del cliente, Mensaje de red al cliente, WAIT_CLASS=Network), es nuestra ventana de observación actual

Las siguientes dos declaraciones que consultan V$LOCK y V$LOCKED_OBJECT no devuelven nada, lo que indica que la tabla TESTLOCK actual no está bloqueada

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

2.4 Actualice la tabla TESTLOCK en la ventana de prueba 1, pero no envíe (Commit)

UPDATE TESTLOCK
SET AAA=11
WHERE AAA=1

Los iconos de confirmación y reversión se mostrarán en la esquina superior izquierda de PL/SQL, lo que indica que la transacción no se ha confirmado.
inserte la descripción de la imagen aquí

2.5 Ejecute la declaración en el paso 3 nuevamente en la ventana de observación:

A través de la consulta de V$LOCK y V$LOCKED_OBJECT, podemos saber que SID=1947 bloquea la tabla TESTLOCK, donde LMODE=3 (bloqueo exclusivo a nivel de fila, que es generado por UPDATE aquí)
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

2.6 Volviendo a la primera pregunta anterior, cómo saber que una SESSION tiene datos modificados pero no tiene COMMIT, y ejecuta la siguiente declaración en la ventana de observación:

--找到修改了数据,但是未提交的Session,选择WAIT_CALSS='Idle',也就是Session处于休息状态,但是有锁定的表
SELECT A.SID,A.SERIAL#,A.USERNAME,A.EVENT,A.WAIT_CLASS,A.SECONDS_IN_WAIT,A.PREV_EXEC_START,b.LOCKED_MODE,C.OWNER,C.OBJECT_NAME,C.OBJECT_TYPE
FROM V$SESSION A
INNER JOIN V$LOCKED_OBJECT B
ON A.SID=b.SESSION_ID
INNER JOIN DBA_OBJECTS C
ON B.OBJECT_ID=c.OBJECT_ID
WHERE A.WAIT_CLASS='Idle'
AND A.SECONDS_IN_WAIT>10/*SESSION空闲后一段时间还锁定的才算有问题,这里随便给了个数值10秒*/
AND USERNAME='TEST';

Solo necesita juzgar que WAIT_CLASS='Idle', y al mismo tiempo hay un objeto bloqueado en V$LOCKED_OBJECT y la SESIÓN ha estado inactiva durante un período de tiempo, como se muestra en la figura, puede juzgar que SID= 1947 ha bloqueado la tabla TESTLOCK, SECONDS_IN_WAIT puede considerarse como la longitud del bloqueo, y la unidad es Second
inserte la descripción de la imagen aquí

2.7 Ejecute las siguientes declaraciones en la ventana de observación para observar desde la perspectiva de las transacciones

--从事务角度观察,连接v$session和v$transaction
SELECT A.SID,A.SERIAL#,A.USERNAME,A.EVENT,A.WAIT_CLASS,A.SECONDS_IN_WAIT,A.PREV_EXEC_START,b.START_DATE
FROM v$session a
INNER JOIN v$transaction b
ON a.taddr=b.addr
WHERE USERNAME='TEST'

Después de conectar las dos vistas, puede saber que SID=1947 inició la transacción
inserte la descripción de la imagen aquí

2.8 A continuación, verifiquemos que el registro se actualice y elimine en la ventana de prueba 2, pero el registro actualizado y eliminado no es el mismo registro que el de la ventana de prueba 1. El registro actualizado en la ventana de prueba 1 es AAA=1 y es enviado inmediatamente después de la actualización y eliminación (COMMIT)

--更新和测试窗口1不同的记录
UPDATE TESTLOCK
SET AAA=100
WHERE AAA=2;
COMMIT ;

--删除和测试窗口1不同的记录
DELETE FROM TESTLOCK
WHERE AAA=3;
COMMIT;

SELECT * FROM TESTLOCK;

Se puede ver que la actualización y eliminación no están bloqueadas, el resultado de la tabla de consulta se muestra en la figura, se puede ver que los datos de AAA=2 se actualizan, los datos de AAA=3 se eliminan y los Los datos de AAA=1 siguen siendo los mismos que antes, es decir, no se pueden ver. Los datos se actualizan a la ventana de prueba 1, es decir, no se pueden ver los datos que no se han enviado.
inserte la descripción de la imagen aquí

2.9 Verifiquemos: ¿qué sucede si otra conexión modifica el mismo registro de TESTLOCK cuando no se envía la ACTUALIZACIÓN anterior?

Ejecute la siguiente declaración en la ventana de prueba 2: pero no confirme (COMMIT)

--更新和测窗口1相同的记录
UPDATE TESTLOCK
SET AAA=12
WHERE AAA=1

La declaración siempre estará en el estado "ejecutando". De hecho, hay un bloqueo de fila en TESTLOCK, y SESSION ha estado esperando que se libere el bloqueo de fila anterior.
inserte la descripción de la imagen aquí

2.10 Vuelva a ejecutar la sentencia del paso 3 en la ventana de observación:

Observe SID=9, muestre EVENT='enq: TX - contención de bloqueo de fila', lo que significa esperar a que se libere un bloqueo de fila, BLOCKING_SESSION indica que la SESIÓN está bloqueada por SID=1947, es decir, la SESIÓN de la ventana de prueba 1 observa V$LOCK para
inserte la descripción de la imagen aquí
distinguir No importa qué LOCK no se envíe, cuál está bloqueado, la visualización de los dos LOCK es la misma

inserte la descripción de la imagen aquí
Observe V$LOCKED_OBJECT, que puede juzgarse por XINUSN/XIDSLOT/XIDSQN. Estos tres campos están relacionados con la reversión. Si todos son 0, puede juzgarse como bloqueado.
inserte la descripción de la imagen aquí

2.11 Encuentra la fila específica que está bloqueada

Ejecute la siguiente instrucción en la ventana de visualización:

--当SESSION被阻塞,通过ROW_WAIT_OBJ#,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#,ROW_WAIT_ROW#这几个字段找到ROWID,然后通过ROWID找到被锁定的记录
SELECT SID,SERIAL#,AUDSID,PADDR,USER#,USERNAME,EVENT,WAIT_CLASS,SECONDS_IN_WAIT,ROW_WAIT_OBJ#,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#,ROW_WAIT_ROW#,
        BLOCKING_SESSION_STATUS,BLOCKING_INSTANCE,BLOCKING_SESSION,C.OWNER,C.OBJECT_NAME,C.OBJECT_TYPE
        ,dbms_rowid.rowid_create(1,ROW_WAIT_OBJ#,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#,ROW_WAIT_ROW#)
FROM V$SESSION A
INNER JOIN V$LOCKED_OBJECT B
ON A.SID=b.SESSION_ID
INNER JOIN DBA_OBJECTS C
ON B.OBJECT_ID=c.OBJECT_ID
WHERE USERNAME='TEST'
AND BLOCKING_SESSION IS NOT NULL ;

Para obtener el ROWID de la tabla bloqueada específica, la instrucción es dbms_rowid.rowid_create(1,ROW_WAIT_OBJ#,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#,ROW_WAIT_ROW#)
inserte la descripción de la imagen aquí
consulta para obtener el registro bloqueado específico, que resulta ser el registro que actualizamos en la prueba ventana 1

--通过前面的函数rowid_create获得具体的ROWID,然后在锁定表中查询记录
SELECT * FROM TESTLOCK
where ROWID='AAJ2QDAAnAAGrwnAAA'

inserte la descripción de la imagen aquí

2.12 Matar la SESIÓN que bloquea otras conexiones

Se puede ver desde el frente que SESSION 1947 bloquea el subproceso actual, consulta SID = 1947, consulta su número de SERIE y luego ejecuta SQL para eliminar la SESIÓN
inserte la descripción de la imagen aquí

ALTER SYSTEM KILL SESSION '1947,63353'

2.13 Vuelva a observar la SESIÓN actual, SID=9, ya no bloqueada

SELECT SID,SERIAL#,AUDSID,PADDR,USER#,USERNAME,EVENT,WAIT_CLASS,SECONDS_IN_WAIT,ROW_WAIT_OBJ#,ROW_WAIT_FILE#,ROW_WAIT_BLOCK#,ROW_WAIT_ROW#,
        BLOCKING_SESSION_STATUS,BLOCKING_INSTANCE,BLOCKING_SESSION
FROM V$SESSION
WHERE USERNAME='TEST';

inserte la descripción de la imagen aquí
La explicación de campo de V$SESSION se puede encontrar en:
https://docs.oracle.com/cd/E18283_01/server.112/e17110/dynviews_3016.htm

Supongo que te gusta

Origin blog.csdn.net/Ruishine/article/details/129184021
Recomendado
Clasificación