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 同步)
  • 等待锁
  • 怀疑是每行开销(索引/触发器)
  • 磁盘写入延迟高

是提交模式问题还是 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。

根本原因:重型触发器 / 约束

每次写入时触发的触发器或正在校验的约束(尤其是没有索引的外键检查)会给每一行增加额外开销。

在 DBAtlas 中打开 →