DBAtlas · Atlas de operaciones de bases de datos

Escrituras lentas (latencia de INSERT/UPDATE)

Las operaciones INSERT/UPDATE/DELETE son más lentas de lo esperado. Primero determinamos en qué espera la escritura (commit/redo, bloqueos, disco o sobrecarga por fila) y luego profundizamos paso a paso hasta la causa raíz.

¿En qué espera la escritura?

Clasifique el cuello de botella observando los wait events: ¿predomina la sincronización de commit/redo (WAL), los bloqueos, la latencia de escritura en disco o la sobrecarga por fila de índices/triggers?

SELECT event, total_waits, ROUND(time_waited/100,1) AS sec_waited
FROM v$system_event
WHERE event IN ('log file sync','log file parallel write','enq: TX - row lock contention','db file sequential read')
ORDER BY time_waited DESC;
  • Espera en commit / redo (sincronización del WAL)
  • Espera en un bloqueo
  • Se sospecha de sobrecarga por fila (índices/triggers)
  • La latencia de escritura en disco es alta

¿Patrón de commit o fsync?

Un commit separado por fila (autocommit) dispara la sincronización de redo/log tantas veces como filas haya. En cambio, si el número de commits es razonable pero cada commit espera en fsync, el problema es la latencia del disco de log.

SELECT name, value FROM v$sysstat WHERE name IN ('user commits','redo writes','redo size');
SELECT ROUND(time_waited_micro/GREATEST(total_waits,1)/1000,2) AS ms_per_sync FROM v$system_event WHERE event='log file sync';
  • Autocommit / un commit en cada fila
  • El commit espera la sincronización del log (fsync)

¿Quién retiene el bloqueo?

Si la escritura espera en un bloqueo: otra sesión puede estar reteniendo la misma fila/índice, o muchas sesiones pueden estar intentando escribir en la misma fila «caliente».

SELECT s.sid, s.serial#, s.username, s.blocking_session, s.event, s.seconds_in_wait
FROM v$session s WHERE s.blocking_session IS NOT NULL;
  • Otra sesión retiene el bloqueo
  • Muchas sesiones escriben en la misma fila caliente

¿De dónde viene la sobrecarga por fila?

Cada INSERT/UPDATE actualiza también todos los índices de la tabla y dispara los triggers/restricciones existentes. Un número excesivo de índices o un trigger pesado ralentizan considerablemente las escrituras.

SELECT index_name, uniqueness FROM user_indexes WHERE table_name='T';
SELECT trigger_name, triggering_event FROM user_triggers WHERE table_name='T';
  • La tabla tiene demasiados índices / índices innecesarios
  • Hay un costo elevado de triggers o restricciones

Causa raíz: autocommit / commit por fila

Un commit separado para cada fila repite la sincronización de redo/log tantas veces como filas haya. Agrupando las operaciones puede reducir drásticamente el número de commits (y por tanto de fsync).

Causa raíz: disco de redo/log lento (latencia de fsync)

Cada commit espera hasta que el registro de redo/WAL se escribe en almacenamiento duradero (fsync). Si el disco de log es lento (alta latencia de escritura, IOPS insuficientes), cada commit paga esa latencia.

Causa raíz: otra sesión retiene el bloqueo

La escritura espera un bloqueo de fila/índice retenido por otra sesión. El bloqueador suele ser una transacción de larga duración o una cuyo commit se olvidó.

Causa raíz: contención en una fila caliente

Muchas sesiones intentan escribir en la misma fila (p. ej. un contador, un número de secuencia, un saldo); todas hacen cola por el mismo bloqueo y las escrituras se serializan.

Causa raíz: sobrecarga excesiva de índices

Cada índice de la tabla se actualiza en cada escritura. Muchos índices, o índices no utilizados, ralentizan directamente INSERT/UPDATE.

Causa raíz: trigger / restricción pesados

Un trigger que se dispara en cada escritura o una restricción que se valida (sobre todo una comprobación de clave foránea sin índice) añade un costo adicional a cada fila.

Abrir en DBAtlas →