用wordpress建立的网站吗,wordpress英文版安装教程,室内设计公司排名前100,杭州高端网站建设MySQL 之多版本并发控制 MVCC 1、MVCC 中的两种读取方式1.1、快照读1.2、当前读 2、MVCC实现原理之 ReadView2.1、隐藏字段2.2、ReadView2.3、读已提交和可重复读隔离级别下#xff0c;产生 ReadView 时机的区别 3、MVCC 解决幻读4、总结 MVCC#xff08;多版本并发控制… MySQL 之多版本并发控制 MVCC 1、MVCC 中的两种读取方式1.1、快照读1.2、当前读 2、MVCC实现原理之 ReadView2.1、隐藏字段2.2、ReadView2.3、读已提交和可重复读隔离级别下产生 ReadView 时机的区别 3、MVCC 解决幻读4、总结 MVCC多版本并发控制 没有正式的标准在不同的 DBMS 中MVCC的实现方式可能不同本文中讲解的是 InnoDB 中 MVCC 的实现机制MySQL 其它的存储引擎并不支持 MVCC. 1、MVCC 中的两种读取方式 MVCC 在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能用更好的方式去处理 读-写冲突做到即使有读写冲突也能做到 不加锁非阻塞并发读而这个读指的就是 快照读而非当前读。 当前读实际上是一种加锁的操作是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。 快照读和当前读是 MVCC 中的两种读取方式。MVCC 通过快照读和当前读两种方式在保持事务隔离性的同时提高了数据库的并发性能。 1.1、快照读 快照读又叫一致性读读取的是快照数据即不加锁的非阻塞读。不加锁的简单 select 都属于快照读。快照读的实现是基于 MVCC多版本并发控制在很多情况下快照读避免了加锁操作降低了开销。
由于快照读基于 MVCC多版本并发控制所以快照读可能读到的并不一定是数据的最新版本很有可能读的是之前的历史版本数据。快照读的前提是隔离级别不是串行级别串行级别下的快照读会退化成当前读。 1.2、当前读 当前读读取的记录是最新版本的数据读取时还要保证其他并发事务不能修改当前记录会对读取的记录进行加锁。加锁的 SELECT或者对数据进行增删改都会进行当前读。比如
# 共享锁
select * from tb_table lock in share mode;
# 排它锁
select * from tb_table for update;
# 排它锁
insert into tb_table values …
# 排它锁
delete from tb_table where …
# 排他锁
update tb_table set …2、MVCC实现原理之 ReadView MVCC 的实现依赖于隐藏字段、undo log、ReadView. 2.1、隐藏字段 对于InnoDB引擎来说每个行记录除了记录本身的数据之外还存在几个隐藏的列。
DB_ROW_ID 如果没有为表显式的定义主键并且表中也没有定义唯一索引那么InnoDB会自动为表添加一个 row_id 的隐藏列作为主键。DB_TRX_ID 每个事务都会分配一个事务ID当对某条记录发生变更时就会将这个事务的事务ID写入 trx_id 中。DB_ROLL_PTR 回滚指针本质上就是指向 undo log 的指针。
2.2、ReadView 在 MVCC 机制中多个事务对同一行记录A进行更新会产生多个历史快照这个历史快照保存在 undo log 中。当一个事务想要查询 记录A 这行数据时需要读取哪个版本的记录将是一个问题。ReadView 就可以解决以上 行可见性问题。 在读未提交的隔离级别下事务查询的数据都是最新版本所以不需要MVCC。在串行化隔离级别下的事务InnoDB 规定使用加锁的方式来访问记录也不需要MVCC。 ReadView 就是事务在使用 MVCC 机制进行快照读操作时产生的读视图。ReadView 主要包含了以下4个重要内容 ① creator_trx_id创建该 ReadView 的事务ID. 只要在对表中的记录做改动时执行 INSERT、DELETE、UPDATE 这些语句时才会为事务分配事务id否则在一个只读事务中的事务id只都默认为0. ② trx_ids表示生成 ReadView 时当前系统中活跃的读写事务的 事务id列表。 ③ up_limit_id活跃的事务中最小的事务ID. ④ low_limit_id表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。low_limit_id 是系统最大的事务ID值. low_limit_id 并不是trx_ids 中的最大值事务ID是递增分配的。比如现在有ID为1、2、3这三个事务之后ID为3的事务提交了那么一个新的读事务在生成 ReadView 时trx_ids 就包括1 和2up_limit_id 的值就是1low_limit_id的值就是4.
ReadView 的规则在访问某条记录时需要按照以下步骤ReadView 的规则判断记录的某个版本是否可见 如果被访问版本的 trx_id(db_trx_id) 属性值与 ReadView 中的 creator_trx_id 值相同意味着当前事务在访问它自己修改过的记录所以该版本可以被当前事务访问。如果被访问版本的 trx_id(db_trx_id) 属性值小于 ReadView 中的 up_limit_id 值表明生成该版本的事务在当前事务生成 ReadView 前已经提交所以该版本可以被当前事务访问。如果被访问版本的 trx_id(db_trx_id) 属性值大于或等于 ReadView 中的 low_limit_id 值表明生成该版本的事务在当前事务生成 ReadView 后才开启所以该版本不可以被当前事务访问。如果被访问版本的 trx_id(db_trx_id) 属性值在 ReadView 的 up_limit_id 和 low_limit_id 之间就需要判断一下 trx_id 属性值是否在 trx_ids 列表中。如果在说明创建 ReadView 时生成该版本的事务还是活跃的该版本不可以被访问。如果不在说明创建 ReadView时生成该版本的事务已经被提交该版本可以被访问。 MVCC 整体操作流程 查询一条记录时系统通过MVCC机制查找记录的流程 ① 首先获取事务自己的版本号也就是事务ID.② 获取 ReadView.③ 查询得到的数据然后与 ReadView 中的事务版本号进行比较。④ 如果不符合 ReadView 规则就需要从 undo log 中获取历史快照。⑤ 最后返回符合规则的数据。 如果某个版本的数据对当前事务不可见那就顺着版本链找到下一个版本的数据继续按照上边的步骤判断可见性依此类推直到版本链中的最后一个版本。 InnoDB 中MVCC 是通过 Undo Log ReadView 进行数据读取 Undo Log 保存了历史快照而 Read View 规则帮我们判断当前版本的数据是否可见。 2.3、读已提交和可重复读隔离级别下产生 ReadView 时机的区别 在隔离级别为读已提交Read Commit时一个事务中的每一次 SELECT 查询都会重新获取一次 ReadView. 步骤事务说明1begin;2select * from tb_table where id 2;获取一次 ReadView3……4select * from tb_table where id 2;获取一次 ReadView5commit 步骤2和步骤4一样的查询语句在读已提交的隔离级别下会分别获取一次 ReadView如果两次的ReadView 不同就可能产生不可重复读或者幻读的情况。 当隔离级别为可重复读的时候一个事务只在第一次 SELECT 时会获取一次 ReadView而后面所有的 SELECT 都会复用这个 ReadView所以可以避免不可重复读。 3、MVCC 解决幻读 使用例子说明 InnoDB 如何解决幻读以下示例是在InnoDB引擎的可重复读隔离级别下讨论。现在 tb_table 表中已存在一条 主键id 1 的数据该条记录隐藏的 db_trx_id 10。
db_trx_id 10数据id 1,name 张三NULL
假设现有 事务A 和 事务B 并发执行事务A 的事务id 为 20事务B 的事务id 为 30. 步骤 1事务A 开始第一次查询数据。 – 事务A 开始第一次查询数据 select * from tb_table where id 1; ① -- 在开始查询前MySQL 会为事务A产生一个 ReadView此时 ReadView 的内容如下trx_ids [20,30]up_limit_id 20low_limit_id 31creator_trx_id 20. ② -- 执行select * from tb_table where id 1;查出id1,db_trx_id 10的数据按ReadView机制判断该行数据是否可见。 ③ -- 按 ReadView 机制 该查询出的数据 db_trx_id 10 ,小于事务A的ReadView 的up_limit_id故该数据在事务A 开启之前其他事务已提交所以该行数据可见。 结论事务A 的第一次查询能读到一条数据id1name 张三。 步骤2接着事务B事务id30,往tb_table 插入两条数据并提交事务假设不考虑 gap lock。 insert into student(idname) values (2,李四)
insert into student(idname) values (3,王五)此时 tb_table 中就要三条数据了对应的 undo log 如下。 步骤3接着事务A开启第二次查询由于在可重复读隔离级别下事务A不会再次生成ReadView. – 事务A 开始第二次查询数据 select * from tb_table where id 1; ① -- 此时tb_table中的3条数据都满足 id1 的条件因此会先查出来然后根据 ReadView 机制判断每条数据是不是都可以被事务A可见。 ② -- 首先 id 1 这条数据同步骤1对事务A可见。 ③ -- 然后是 id 2 的数据它的 trx_id 30介于 ReadView 的 up_limit_id 20 和 low_limit_id 31 之间需要再次判断 trx_id 30 是否处于 ReadView 的 trx_ids[20,30] 数值内。结果在trx_ids数组内表示 id 2 这条数据是与事务A 在同一时刻启动的其他事务提交的所以这条数据不能被事务A可见。 ④ -- 同理id 3 这条数据trx_id 也为 30因此也不能被事务A 可见。 结论最终事务A 的第二次查询只能查询出 id 1 的这条数据这和事务A 的第一次查询的结果一样因此没有出现不可重复读现象
注意只靠MVCC不能完全解决幻读问题。虽然MVCC可以提供事务的一致性视图并保持数据的一致性版本但它本身并不能防止其他事务插入新的数据。通过结合 next-key locking 和 MVCCInnoDB 在可重复读隔离级别下解决了幻读问题确保了事务在多次读取相同范围数据时的一致性。 间隙锁是在 innodb的 可重复读级别才会生效且 Next-Key Locks 实际上是由间隙锁加行锁实现的。MySQL 在 InnoDB 存储引擎下可重复读的隔离级别下不存在幻读问题。
4、总结 MVCC多版本并发控制的核心点在于 ReadView 的原理READ COMMITTED、REPEATABLE READ 这两个隔离级别的一个很大不同就是生成 ReadView 的时机不同
READ COMMITTED 在每一次进行普通 select 操作前都会生成一个 ReadView.REPEATABLE READ 只在第一次进行普通 select 操作前生成一个 ReadView之后的查询操作都重复使用这个 ReadView 就好了。 MVCC多版本并发控制解决的问题
① 读写之间阻塞的问题。通过 MVCC 可以让读写相互不阻塞即读不阻塞写写不阻塞读可以提升事务并发处理能力。② 降低了死锁的概率。因为 MVCC 采用了乐观锁的方式读取数据时并不需要加锁对于写操作也只锁定必要的行。③ 解决快照读的问题。当查询数据库在某个时间点的快照时只能看到这个时间点之前事务提交更新的结果而不能看到这个时间点之后事务提交的更新结果。