Медленная запись (задержка 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.
Первопричина: тяжёлый триггер / ограничение
Триггер, срабатывающий при каждой записи, или проверяемое ограничение (особенно проверка внешнего ключа без индекса) добавляют дополнительные затраты к каждой строке.