手机网站建设用乐云seo,商业网站开发与设计,程序员在线咨询,企业建网站有这个必要吗MVCC#xff08;多版本并发控制#xff09;原理实现 一、实现组件二、数据可见性判断三、可见性描述 多版本并发控制#xff08;MVCC#xff0c;Multi-Version Concurrency Control#xff09;#xff0c;是一种并发控制的方法。 MySQL InnoDB巧妙地利用了隐藏列、事务ID… MVCC多版本并发控制原理实现 一、实现组件二、数据可见性判断三、可见性描述 多版本并发控制MVCCMulti-Version Concurrency Control是一种并发控制的方法。 MySQL InnoDB巧妙地利用了隐藏列、事务ID、Read View以及Undo Log等技术实现了多版本并发控制使得不同事务能够几乎同时访问数据库而不相互阻塞极大地提升了数据库系统的并发性能和用户体验。
一、实现组件
事务ID和系统版本号syscanf_version
每个事务都有一个全局唯一的事务IDtrx_id这个ID随着事务的开始而递增。
在InnoDB内部还有一个系统版本号syscanf_version也是随着事务的执行不断增长。
隐藏列与行格式
InnoDB表中每一行数据除了用户定义的列外还有额外的隐藏列 DB_TRX_ID 记录最后一次修改该行数据的事务ID。 DB_ROLL_PTR 回滚指针指向Undo Log中的相应条目用于撤销操作或者构建历史版本。 DB_ROW_ID可选 对于没有主键的表InnoDB会自动生成一个隐含的ROW ID作为聚簇索引的一部分。
Undo Log回滚日志
当事务对数据进行修改时InnoDB不仅在当前的数据页上更新数据还会在Undo Log两种类型中记录旧值以及修改前的状态。
INSERT Undo Log 记录插入操作主要用于事务回滚时删除新插入的行。
UPDATE/DELETE Undo Log 记录更新或删除操作包含被修改前的行数据用于事务回滚时恢复原状同时也为其他事务提供历史版本的数据。
Read View读视图
在“可重复读”隔离级别下每个事务启动时会创建并固定一个Read View之后的所有一致性非锁定读都会基于这个视图来判断数据可见性Read View包含了以下关键信息。
m_ids[] 数组存储了所有未提交且活跃的事务ID列表。low_limit_id 所有小于等于这个值的事务ID已经提交完成。up_limit_id 下一个即将分配给事务的ID表示尚未分配事务ID的最大值。creator_trx_id 创建此Read View的事务自身的事务ID。
二、数据可见性判断
当事务执行SELECT查询时针对每行数据根据Read View和该行的DB_TRX_ID来判断是否可见 DB_TRX_ID小于low_limit_id 说明该行是在当前事务开始之前就已经提交的因此对该事务是可见的。 DB_TRX_ID大于等于up_limit_id 说明该行是由在ReadView之后才开启的事务修改或插入的因此对当前事务不可见。 DB_TRX_ID位于low_limit_id和up_limit_id之间 若DB_TRX_ID不在ReadView的m_ids列表中则该事务已提交数据行对当前事务可见。若DB_TRX_ID在ReadView的m_ids列表中则该事务尚未提交数据行对当前事务不可见。
对于不可见的行通过DB_ROLL_PTR找到对应的Undo Log并从中获取在Read View创建时刻该行的最新已提交版本以便当前事务查看。
在“可重复读”隔离级别下由于Read View在事务开始时就固定了所以即使后续有新的事务插入满足查询条件的新行这些新行也不会影响当前事务的查询结果从而避免了幻读问题。
三、可见性描述
一个代码片段用于简单演示Read View与事务ID的对比逻辑。
// 假设Transaction类代表一个MySQL InnoDB中的事务它有trxId属性表示当前事务ID
class Transaction {long trxId; // 当前事务ID// 创建一个新的读视图ReadView createReadView() {return new ReadView(this.trxId);}
}// 代表InnoDB中的一行记录包含DB_TRX_ID等隐藏列
class InnodbRow {long dbTrxId; // 最后修改该行的事务IDObject[] data; // 用户数据RollbackPointer rollbackPtr; // 回滚指针// 判断此行对于给定ReadView是否可见boolean isVisibleTo(ReadView readView) {if (dbTrxId readView.lowLimitId) {// 已提交事务修改对当前事务可见return true;} else if (readView.isTrxIdInRange(dbTrxId)) {// 未提交事务或已提交但在此视图创建后对当前事务不可见return false;} else {// 不可能出现的情况理论上应该为已提交事务throw new IllegalStateException(Invalid transaction ID state);}}
}// ReadView类存储了事务在可重复读隔离级别下看到的数据版本范围
class ReadView {long lowLimitId; // 已提交事务的最小ID界限SetLong activeTransactionIds; // 当前未提交的事务ID集合long upLimitId; // 下一个待分配的事务ID即活跃事务的最大值long creatorTrxId; // 创建此ReadView的事务ID// 检查给定的事务ID是否在当前活跃的未提交事务范围内boolean isTrxIdInRange(long trxId) {return activeTransactionIds.contains(trxId);}
}// 假设有两个事务tx1和tx2以及一行数据row
Transaction tx1 new Transaction(); // 初始化事务tx1并获取其事务ID
Transaction tx2 new Transaction(); // 初始化事务tx2并获取其事务ID
InnodbRow row getRowFromDatabase(); // 获取数据库中一行记录// 在tx1中创建读视图并检查row的可见性
ReadView readView1 tx1.createReadView();
boolean isVisibleToTx1 row.isVisibleTo(readView1);// 如果isVisibleToTx1为true则tx1可以查看该行数据否则根据MVCC规则tx1应查找undo log中的历史版本。