首页 > 数据库 > Oracle > ORACLE VS MySQL MVCC
处理长事务时的根本差异。
-- InnoDB行结构隐藏列+---------------+-----------------+---------------+| DB_ROW_ID | DB_TRX_ID | DB_ROLL_PTR || (隐藏主键) | (事务ID) | (回滚指针) |+---------------+-----------------+---------------+
版本链形成过程:
初始数据: Row(v1) → NULL 事务T1更新: Row(v2) → undo(v1) 事务T2更新: Row(v3) → undo(v2) → undo(v1) 事务T3更新: Row(v4) → undo(v3) → undo(v2) → undo(v1)
// 简化版Read View数据结构struct ReadView {
trx_id_t m_low_limit_id; // 高水位线
trx_id_t m_up_limit_id; // 低水位线
ids_t m_ids; // 活跃事务ID列表
// 可见性判断函数
bool changes_visible(trx_id_t id) {
if (id < m_up_limit_id) return true;
if (id >= m_low_limit_id) return false;
return !binary_search(m_ids, id); // O(log n)查找
}};# 模拟长事务导致的版本链遍历def find_visible_version(row, read_view): """MySQL可见性查找算法""" current = row traversed_nodes = 0 while current is not None: traversed_nodes += 1 trx_id = current.trx_id # 版本链可能极长! if read_view.changes_visible(trx_id): return current, traversed_nodes current = current.prev_version # 遍历前一个版本 return None, traversed_nodes# 正常情况:版本链长度=2-3,遍历几次即可# 长事务情况:版本链长度可能达到10000+,遍历成本激增
-- Oracle数据块结构示意/*数据块头: - ITL槽(事务槽) - SCN(系统变更号) - 行锁信息 数据区: - 行数据 - 行头(包含锁和SCN) */
查询开始: 1. 获取当前SCN(假设为1000) 2. 读取数据块,发现块SCN=2000 > 1000 3. 在Buffer Cache中创建块副本(CR Block) 4. 从Undo段应用Undo记录,将块回滚到SCN=1000 5. 返回一致性数据
无全局版本链 :每个查询独立构造一致性视图
基于SCN的快照 :无需维护全局事务状态列表
块级操作 :一次回滚整个数据块,而非逐行遍历
| 指标 | 正常情况 | 存在长事务(1000s) | 增幅 |
|---|---|---|---|
| 逻辑读次数 | 10-100 | 10,000-1,000,000 | 100-10000倍 |
| CPU使用率 | 5-10% | 80-100% | 8-10倍 |
| 查询响应时间 | 1-10ms | 100-10000ms | 100-1000倍 |
| Undo表空间 | 稳定 | 持续增长 | 可能撑爆 |
| 指标 | 正常情况 | 存在长事务 | 备注 |
|---|---|---|---|
| 逻辑读次数 | 基本不变 | 略有增加 | CR块构造开销 |
| CPU使用率 | 5-10% | 20-40% | 主要消耗在Undo应用 |
| 查询响应时间 | 1-10ms | 10-50ms | 增加但有限 |
| Undo表空间 | 循环使用 | 可能报ORA-01555 | 保护机制触发 |
// 伪代码展示MySQL可见性判断的O(n)复杂度for (version = current_row; version != NULL; version = version->prev) {
if (trx_id_is_visible(version->trx_id, read_view)) {
return version; // 找到可见版本
}}// 如果版本链有10000个节点,最坏情况需要遍历10000次!// Oracle的CR块构造(简化)BufferBlock* create_cr_block(Block* current, SCN query_scn) {
// 1. 分配新缓冲区
BufferBlock* cr_block = allocate_buffer();
// 2. 复制当前块
memcpy(cr_block, current, BLOCK_SIZE);
// 3. 应用Undo回滚到query_scn
while (cr_block->scn > query_scn) {
UndoRecord* undo = get_undo_for_block(cr_block);
apply_undo_to_block(cr_block, undo);
}
// 时间复杂度:与更新次数有关,但与并发事务数无关!
return cr_block;}-- 1. 监控长事务和版本链长度SELECT r.trx_id AS 事务ID, TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) AS 运行时间秒, r.trx_mysql_thread_id AS 线程ID, LEFT(p.INFO, 50) AS 执行语句, i.history_list_length AS 版本链长度FROM information_schema.innodb_trx rLEFT JOIN information_schema.processlist p ON r.trx_mysql_thread_id = p.IDCROSS JOIN (SELECT count FROM information_schema.innodb_metrics WHERE name = 'trx_rseg_history_len') iWHERE TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) > 60ORDER BY 运行时间秒 DESC;-- 2. 配置自动终止长事务SET GLOBAL max_execution_time = 60000; -- 查询超时60秒SET GLOBAL innodb_rollback_on_timeout = ON;-- 3. 优化Undo配置SET GLOBAL innodb_max_undo_log_size = 1073741824; -- 1GBSET GLOBAL innodb_undo_log_truncate = ON;SET GLOBAL innodb_purge_threads = 4; -- 增加Purge线程
-- Oracle Undo管理ALTER SYSTEM SET undo_retention = 900; -- 15分钟ALTER SYSTEM SET undo_tablespace = undotbs2;-- 监控长事务SELECT s.sid, s.serial#, s.username, t.start_time, ROUND((SYSDATE - t.start_date)*24*60, 2) AS 运行分钟, t.used_ublk AS 使用Undo块数FROM v$transaction tJOIN v$session s ON t.ses_addr = s.saddrWHERE (SYSDATE - t.start_date)*24*60 > 30; -- 超过30分钟
原子DDL :减少元数据锁持有时间
直方图统计 :优化查询计划,减少全表扫描
资源组 :限制长事务资源消耗
InnoDB并行Purge :加速版本清理
MySQL的MVCC实现决定了它对长事务极度敏感,而Oracle的块级重构架构使其更具韧性 。
关键差异总结:
数据组织 :MySQL行级链 vs Oracle块级重构
可见性判断 :MySQL全局Read View vs Oracle SCN快照
清理机制 :MySQL异步Purge vs Oracle循环Undo段
失败模式 :MySQL性能雪崩 vs Oracle优雅降级(报错)
对于MySQL DBA来说, 预防和快速发现长事务 是最重要的任务之一。而对于Oracle DBA,重点则是 合理配置Undo空间和监控ORA-01555 。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/7655508/viewspace-3117669/ ,如需转载,请注明出处,否则将追究法律责任。