DBAtlas · Атлас эксплуатации баз данных

Медленная запись (задержка INSERT/UPDATE)

Операции INSERT/UPDATE/DELETE медленнее ожидаемого. Сначала определим, чего ждёт запись (commit/redo, блокировки, диск или накладные расходы на строку), затем пошагово дойдём до первопричины.

Чего ждёт запись?

Классифицируйте узкое место по событиям ожидания: преобладает синхронизация commit/redo (WAL), блокировки, задержка записи на диск или накладные расходы на индексы/триггеры для каждой строки?

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;
  • Ожидание commit / redo (синхронизация WAL)
  • Ожидание блокировки
  • Подозрение на накладные расходы на строку (индексы/триггеры)
  • Высокая задержка записи на диск

Паттерн commit или fsync?

Отдельный commit на каждую строку (autocommit) вызывает синхронизацию redo/журнала столько раз, сколько строк. Напротив, если число commit разумно, но каждый commit ждёт fsync, проблема в задержке диска журнала.

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 / commit на каждой строке
  • Commit ждёт синхронизации журнала (fsync)

Кто удерживает блокировку?

Если запись ждёт блокировку: другая сессия может удерживать ту же строку/индекс, или множество сессий пытаются писать в одну и ту же «горячую» строку.

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;
  • Блокировку удерживает другая сессия
  • Много сессий пишут в одну горячую строку

Откуда берутся накладные расходы на строку?

Каждый INSERT/UPDATE также обновляет все индексы таблицы и запускает триггеры/проверку ограничений. Избыточное число индексов или тяжёлый триггер значительно замедляют запись.

SELECT index_name, uniqueness FROM user_indexes WHERE table_name='T';
SELECT trigger_name, triggering_event FROM user_triggers WHERE table_name='T';
  • У таблицы слишком много / ненужные индексы
  • Есть затраты на тяжёлый триггер или ограничение

Первопричина: autocommit / commit на каждую строку

Отдельный commit для каждой строки повторяет синхронизацию redo/журнала столько раз, сколько строк. Группируя операции, можно резко сократить число commit (а значит, и fsync).

Первопричина: медленный диск redo/журнала (задержка fsync)

Каждый commit ждёт, пока запись redo/WAL не будет записана на надёжный носитель (fsync). Если диск журнала медленный (высокая задержка записи, недостаточно IOPS), каждый commit платит эту задержку.

Первопричина: блокировку удерживает другая сессия

Запись ждёт блокировку строки/индекса, удерживаемую другой сессией. Блокирующая сессия обычно — долгая транзакция или та, где забыли выполнить commit.

Первопричина: конкуренция за горячую строку

Множество сессий пытаются писать в одну строку (например, счётчик, порядковый номер, баланс); все они встают в очередь за одной блокировкой, и запись сериализуется.

Первопричина: избыточные накладные расходы на индексы

Каждый индекс таблицы обновляется при каждой записи. Множество или неиспользуемые индексы напрямую замедляют INSERT/UPDATE.

Первопричина: тяжёлый триггер / ограничение

Триггер, срабатывающий при каждой записи, или проверяемое ограничение (особенно проверка внешнего ключа без индекса) добавляют дополнительные затраты к каждой строке.

Открыть в DBAtlas →