Oracle 11g 中的 UPDATE
操作是数据库修改数据的关键机制,其核心原理涉及事务管理、多版本并发控制(MVCC)、Undo/Redo 日志、锁机制等
1. 执行前的准备
-
SQL 解析与执行计划:
Oracle 解析UPDATE
语句,生成执行计划,确定目标数据的位置(通过索引或全表扫描)。 -
缓冲区缓存(Buffer Cache)检查:
Oracle 首先在内存的缓冲区缓存中查找目标数据块。-
若数据块已在缓存中,直接修改内存中的副本。
-
若不在缓存中,从磁盘读取数据块到缓冲区缓存。
-
2. 事务启动与锁机制
-
行级锁(Row-Level Lock):
Oracle 对目标行加排他锁(Exclusive Lock),阻止其他事务修改同一行,但允许其他事务通过SELECT
读取(读一致性由 MVCC 保证)。 -
事务槽分配:
在回滚段(Undo 段)中分配事务槽,记录事务 ID(XID)和系统变更号(SCN)。
3. 生成 Undo 数据
-
旧版本数据保存:
在修改数据前,Oracle 将原始数据(即修改前的值)写入 Undo 表空间,用于支持事务回滚和读一致性。-
例如,若执行
UPDATE employees SET salary=10000 WHERE id=1
,原salary=8000
会被写入 Undo 段。
-
-
Undo 的作用:
-
事务回滚时恢复原始数据。
-
其他事务查询时,若数据已被修改但未提交,通过 Undo 数据提供一致性视图
-
4. 修改数据块
-
更新缓冲区缓存中的数据块:
在内存中修改目标行的数据,标记数据块为“脏块”(Dirty Block)。 -
行迁移(Row Migration)处理:
如果更新后的行长度超出原数据块的空闲空间,Oracle 可能将整行迁移到新数据块,并在原位置保留指向新块的指针(称为行链接或行迁移)
5. 生成 Redo 日志
-
记录变更操作:
所有修改操作(包括数据块和 Undo 段的变更)会生成 Redo 日志条目,写入日志缓冲区(Log Buffer)。-
Redo 日志确保事务的持久性(Durability),即使系统崩溃也能恢复。
-
-
日志写入策略:
-
提交事务时,日志缓冲区中的 Redo 条目由
LGWR
进程写入磁盘的 Redo 日志文件。 -
未提交的事务也可能部分写入 Redo 日志(由
LGWR
定期刷新)。
-
6. 提交事务
-
事务提交(COMMIT):
-
释放行级锁,但 Undo 数据仍保留(直到不再被读一致性查询需要)。
-
LGWR
将剩余 Redo 日志写入磁盘,确保事务持久化。 -
更新数据块的 SCN(System Change Number),标记为已提交。
-
-
快速提交优化:
Oracle 使用延迟块清除(Delayed Block Cleanout)机制,将部分清理工作推迟到后续访问数据块时执行,减少提交时的开销。
7. 数据持久化到磁盘
-
脏块写入数据文件:
由DBWn
(Database Writer)后台进程将缓冲区缓存中的脏块异步写入磁盘数据文件。-
写入时机:检查点触发、缓冲区缓存不足、或定期刷新。
-
8. 多版本并发控制(MVCC)
-
读一致性(Read Consistency):
其他事务查询时,若目标数据正在被修改(未提交),Oracle 通过 Undo 数据构造该事务开始时的数据版本(基于查询的 SCN)。 -
避免脏读:
未提交的修改对其他事务不可见,确保隔离性。
关键组件协同
组件 | 作用 |
---|---|
Undo 表空间 | 存储旧数据,支持回滚和读一致性。 |
Redo 日志 | 记录所有变更操作,用于崩溃恢复。 |
Buffer Cache | 内存缓存数据块,减少物理 I/O。 |
DBWn | 将脏块写入磁盘数据文件。 |
LGWR | 将 Redo 日志写入磁盘日志文件。 |
UPDATE employees SET salary = 10000 WHERE employee_id = 101;
COMMIT;
-
定位
employee_id=101
的数据块(通过索引或全表扫描)。 -
获取该行的排他锁。
-
将原薪资(如
8000
)写入 Undo 表空间。 -
在 Buffer Cache 中修改该行薪资为
10000
。 -
生成 Redo 日志:“将
employee_id=101
的薪资从8000
改为10000
”。 -
提交事务,
LGWR
将 Redo 日志写入磁盘,释放锁。 -
最终
DBWn
将脏块写入磁盘数据文件(可能延迟)。