写入缓慢(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 同步)
- 等待锁
- 怀疑是每行开销(索引/触发器)
- 磁盘写入延迟高
是提交模式问题还是 fsync?
每行单独提交(autocommit)会触发与行数相同次数的 redo/日志同步。反之,如果提交次数合理但每次提交都在等待 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 / 每行都提交
- 提交在等待日志同步(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 / 每行提交
每行单独提交会重复执行与行数相同次数的 redo/日志同步。通过对操作进行分组,可以大幅减少提交次数(从而减少 fsync 次数)。
根本原因:redo/日志磁盘缓慢(fsync 延迟)
每次提交都要等到 redo/WAL 记录写入持久存储(fsync)。如果日志磁盘缓慢(写入延迟高、IOPS 不足),每次提交都要承受这个延迟。
根本原因:另一个会话持有锁
写入正在等待另一个会话持有的行/索引锁。阻塞者通常是一个长时间运行的事务,或忘记提交的事务。
根本原因:热行争用
许多会话试图写入同一行(例如计数器、序号、余额);它们都在排队等待同一个锁,写入被串行化。
根本原因:索引开销过大
每次写入都会更新表上的每个索引。索引过多或存在未使用的索引会直接拖慢 INSERT/UPDATE。
根本原因:重型触发器 / 约束
每次写入时触发的触发器或正在校验的约束(尤其是没有索引的外键检查)会给每一行增加额外开销。