Rastreo, depuración y reparación de contenciones de bloqueo de fila


12

Tarde, me he enfrentado a muchas disputas de bloqueo de fila. La tabla en disputa parece ser una tabla particular.

Esto es generalmente lo que sucede:

  • El desarrollador 1 inicia una transacción desde la pantalla frontal de Oracle Forms
  • El desarrollador 2 inicia otra transacción, desde una sesión diferente usando la misma pantalla

~ 5 minutos, el extremo frontal parece no responder. La comprobación de sesiones muestra la contención de bloqueo de fila. La "solución" que todos arrojan es matar sesiones: /

Como desarrollador de bases de datos

  • ¿Qué se puede hacer para eliminar las contiendas de bloqueo de fila?
  • ¿Sería posible averiguar qué línea de un procedimiento almacenado está causando estas contenciones de bloqueo de fila?
  • ¿Cuál sería la directriz general para reducir / evitar / eliminar tales problemas que codifican?

Si esta pregunta tiene demasiada información abierta / insuficiente, no dude en editar / hágamelo saber. Haré todo lo posible para agregar información adicional.


La tabla en cuestión está bajo muchas inserciones y actualizaciones, diría que es una de las tablas más ocupadas. El SP es bastante complejo, para simplificar, obtiene datos de varias tablas, lo completa en tablas de trabajo, se producen muchas operaciones aritméticas en la tabla de trabajo y el resultado de la tabla de trabajo se inserta / actualiza en la tabla en cuestión.


La versión de la base de datos es Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit. El flujo de la lógica se ejecuta en el mismo orden en ambas sesiones, la transacción no se mantiene abierta durante demasiado tiempo (o al menos eso creo ), y los bloqueos se producen durante la ejecución activa de las transacciones.


Actualización: el recuento de filas de la tabla es mayor de lo que esperaba, aproximadamente 3.1 millones de filas. Además, después de rastrear una sesión, descubrí que un par de declaraciones de actualización de esta tabla no están utilizando el índice. ¿Por qué es así? No estoy seguro. La columna a la que se hace referencia en la cláusula where está indexada. Actualmente estoy reconstruyendo el índice.


1
@Sathya: ¿puede explicar la complejidad del procedimiento almacenado? ¿Se sospecha que la tabla sospechosa está bajo una actualización o inserción rigurosa?
CoderHawk

¿Las claves foráneas juegan un papel aquí? (A veces esto necesita un índice) ¿Qué versión de la base de datos existe? ¿Se ejecuta el flujo de la lógica en el mismo orden en ambas sesiones? ¿La transacción se mantiene 'abierta' durante mucho tiempo? ¿Se produce el bloqueo durante el tiempo de reflexión de los usuarios o durante la ejecución activa de la transacción?
ik_zelf

@ Sandy He actualizado la pregunta
Sathyajith Bhat

@ik_zelf He actualizado la pregunta
Sathyajith Bhat

1
No me queda claro por qué esto es un problema: Oracle está haciendo exactamente lo que se supone que debe hacer, que es serializar el acceso a una sola fila. Si alguien tiene esa fila, puede leer la versión anterior, pero para escribir debe esperar a que se libere el bloqueo. La única "solución" para esto es a) no perder el tiempo y / COMMITo ROLLBACKen un tiempo razonable ob) organizar de manera que las mismas personas no siempre quieran la misma fila al mismo tiempo.
Gaius

Respuestas:


10

¿Sería posible averiguar qué línea de un procedimiento almacenado está causando estas contiendas de bloqueo de fila?

No exactamente, pero puede obtener la instrucción SQL que causa el bloqueo y, a su vez, identificar las líneas relacionadas en el procedimiento.

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

¿Cuál sería la pauta general para reducir / evitar / eliminar tales problemas con la codificación?

La sección de la Guía de Conceptos de Oracle sobre bloqueos dice: "Una fila está bloqueada solo cuando es modificada por un escritor". Otra sesión que actualice la misma fila esperará la primera sesión COMMITo ROLLBACKantes de que pueda continuar. Para eliminar el problema, puede serializar a los usuarios, pero aquí hay algunas cosas que pueden reducir el problema tal vez al nivel de que no es un problema.

  • COMMITmás frecuentemente. Cada COMMITlanzamiento se bloquea, por lo que si puede realizar las actualizaciones en lotes, se reduce la probabilidad de que otra sesión necesite la misma fila.
  • Asegúrese de no actualizar ninguna fila sin cambiar sus valores. Por ejemplo, UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);debe reescribirse como el más selectivo (leer menos bloqueos) UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. Por supuesto, si el cambio de la declaración aún bloqueará la mayoría de las filas en la tabla, entonces el cambio solo tendrá un beneficio de legibilidad.
  • Asegúrese de utilizar secuencias en lugar de bloquear una tabla para agregar una al valor actual más alto.
  • Asegúrese de no estar usando una función que esté causando que no se use un índice. Si la función es necesaria, considere convertirla en un índice basado en funciones.
  • Piensa en sets. Considere si un bucle que ejecuta un bloque de PL / SQL que realiza actualizaciones podría reescribirse como una sola declaración de actualización. De lo contrario, tal vez se pueda utilizar el procesamiento masivo BULK COLLECT ... FORALL.
  • Reduzca el trabajo que se realiza entre el primero UPDATEy el COMMIT. Por ejemplo, si el código envía un correo electrónico después de cada actualización, considere poner en cola los correos electrónicos y enviarlos después de confirmar las actualizaciones.
  • Diseñe la aplicación para manejar la espera haciendo un SELECT ... FOR UPDATE NOWAITo WAIT 2. Luego puede detectar la imposibilidad de bloquear la fila e informar al usuario que otra sesión está modificando los mismos datos.

7

Proporcionaré una respuesta desde el punto de vista del desarrollador.

En mi opinión, cuando encuentra una disputa de filas como la que describe, es porque tiene un error en su aplicación. En la mayoría de los casos, este tipo de contención es un signo de una vulnerabilidad de actualización perdida. Este hilo en AskTom explica el concepto de una actualización perdida:

Una actualización perdida ocurre cuando:

sesión 1: lea el registro de empleado de Tom

sesión 2: lea el registro de empleado de Tom

sesión 1: actualizar el registro de empleados de Tom

sesión 2: actualizar el registro de empleados de Tom

La sesión 2 SOBRE ESCRIBIRÁ los cambios de la sesión 1 sin verlos, lo que resulta en una actualización perdida.

Ha experimentado un efecto secundario desagradable de la actualización perdida: la sesión 2 se puede bloquear porque la sesión 1 aún no se ha comprometido. Sin embargo, el problema principal es que la sesión 2 actualiza ciegamente el registro. Suponga que ambas sesiones emiten la declaración:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

Después de ambas declaraciones, las modificaciones de la sesión 1 se han sobrescrito, sin haber notificado a la sesión 2 que la fila había sido modificada por la sesión 1.


La actualización perdida (y el efecto secundario de contención) nunca deberían suceder, son 100% evitables. Debe usar el bloqueo para evitarlos con dos métodos principales: bloqueo optimista y pesimista .

1) Bloqueo pesimista

Desea actualizar una fila. En este modo, evitará que otros modifiquen esta fila solicitando un bloqueo en esa fila ( SELECT ... FOR UPDATE NOWAITdeclaración). Si la fila ya se está modificando, recibirá un mensaje de error, que puede traducir con gracia al usuario final (esta fila está siendo modificada por otro usuario). Si la fila está disponible, realice sus modificaciones (ACTUALIZACIÓN), luego confirme cada vez que se complete su transacción.

2) Bloqueo optimista

Desea actualizar una fila. Sin embargo, no desea mantener un bloqueo en esa fila, tal vez porque usa varias transacciones para actualizar la fila (aplicación sin estado basada en la web), o tal vez no desea que ningún usuario mantenga un bloqueo durante demasiado tiempo ( lo que puede provocar que otras personas sean bloqueadas). En ese caso, no solicitará un bloqueo de inmediato. Utilizará un marcador para asegurarse de que la fila no ha cambiado cuando se emitirá su actualización. Puede almacenar en caché el valor de todas las columnas, o puede usar una columna de marca de tiempo que se actualiza automáticamente, o una columna basada en secuencia. Cualquiera sea su elección, cuando esté a punto de realizar su actualización, se asegurará de que el marcador en esa fila no haya cambiado emitiendo una consulta como:

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

Si la consulta devuelve una fila, realice su actualización. Si no es así, esto significa que alguien ha modificado la fila desde la última vez que la consultó. Deberá reiniciar el proceso desde el principio.

Nota: Si tiene plena confianza en todas las aplicaciones que acceden a su base de datos, puede confiar en una actualización directa para el bloqueo optimista. Puede emitir directamente:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

Si la declaración no actualiza ninguna fila, sabe que alguien ha cambiado esta fila y debe comenzar de nuevo.

Si todas las aplicaciones están de acuerdo con este esquema, nunca sería bloqueado por otra persona y evitaría la actualización a ciegas. Sin embargo, si no bloquea la fila de antemano, aún es susceptible al bloqueo indefinido si otra aplicación, trabajo por lotes o actualización directa no implementa un bloqueo optimista. Es por eso que le aconsejo que siempre bloquee la fila, sea cual sea su opción de esquema de bloqueo (el impacto en el rendimiento puede ser insignificante ya que recupera todos los valores, incluido el rowid cuando bloquea la fila).

TL; DR

  • La actualización de una fila sin tener un bloqueo de antemano expone la aplicación a un posible "congelamiento". Esto se puede evitar si todos los DML de la base de datos implementan un bloqueo optimista o pesimista.
  • Verifique que la instrucción SELECT devuelva valores consistentes con cualquier SELECT anterior (para evitar cualquier problema de actualización perdido)

5

Esta respuesta probablemente calificaría para una entrada en The Daily WTF.

Correcto, después de rastrear las sesiones y buscar USER_SOURCE, rastreé la causa raíz

  • La causa, como era de esperar, era una lógica defectuosa
  • Recientemente, se agregó una declaración de actualización al SP. La declaración de actualización básicamente actualizaría toda la tabla. Aparentemente, el desarrollador en cuestión se olvidó de agregar las cláusulas where right para actualizar las declaraciones requeridas.
  • La tabla que se actualizó fue como se mencionó anteriormente, una de las tablas más negociadas y tenía una gran cantidad de registros. La actualización llevaría mucho tiempo, agonizante.
  • El resultado fue que otras sesiones no pudieron obtener un bloqueo en la mesa y se sentaron en contenciones de bloqueo de fila.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.