怀化住建部网站,网站建设柒首先金手指6,免费刷推广链接的软件,仙游有人做网站多版本并发控制(MVCC) 文章目录 多版本并发控制(MVCC)1. 概述2. 快照读与当前读2.1 快照读2.2 当前读 3. MVCC实现原理之ReadView3.1 ReadView概述3.2 设计思路3.3 ReadView的规则3.4 MVCC整体操作流程 4. 举例说明4.1 READ COMMITTED隔离级别下4.2 REPEATABLE READ隔离级别下 …多版本并发控制(MVCC) 文章目录 多版本并发控制(MVCC)1. 概述2. 快照读与当前读2.1 快照读2.2 当前读 3. MVCC实现原理之ReadView3.1 ReadView概述3.2 设计思路3.3 ReadView的规则3.4 MVCC整体操作流程 4. 举例说明4.1 READ COMMITTED隔离级别下4.2 REPEATABLE READ隔离级别下 5. 总结 1. 概述
MVCC Multiversion Concurrency Control多版本并发控制。顾名思义MVCC 是通过数据行的多个版本管理来实现数据库的 并发控制 。这项技术使得在InnoDB的事务隔离级别下执行 一致性读 操作有了保证。换言之就是为了查询一些正在被另一个事务更新的行并且可以看到它们被更新之前的值这样在做查询的时候就不用等待另一个事务释放锁。
2. 快照读与当前读
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能用更好的方式去处理 读-写冲突 做到即使有读写冲突时也能做到 不加锁 非阻塞并发读 而这个读指的就是 快照读 , 而非 当前读 。当前读实际上是一种加锁的操作是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。
2.1 快照读
快照读又叫一致性读读取的是快照数据。不加锁的简单的 SELECT 都属于快照读即不加锁的非阻塞读比如这样
SELECT * FROM player WHERE ...快照读的前提是隔离级别不是串行级别串行级别下的快照读会退化成当前读。
2.2 当前读
当前读读取的是记录的最新版本最新数据而不是历史版本的数据读取时还要保证其他并发事务不能修改当前记录会对读取的记录进行加锁。加锁的 SELECT或者对数据进行增删改都会进行当前读。比如
SELECT * FROM student LOCK IN SHARE MODE; # 共享锁
SELECT * FROM student FOR UPDATE; # 排他锁
INSERT INTO student values ... # 排他锁
DELETE FROM student WHERE ... # 排他锁
UPDATE student SET ... # 排他锁3. MVCC实现原理之ReadView
MVCC 的实现依赖于隐藏字段、Undo Log、Read View。
3.1 ReadView概述
在MVCC 机制中多个事务对同一个行记录进行更新会产生多个历史快照这些历史快照保存在 Undo Log 里。如果一个事务想要查询这个行记录需要读取哪个版本的行记录呢? 这时就需要用到 ReadView 了它帮我们解决了行的可见性问题。
ReadView 就是事务A在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时会生成数据库系统当前的一个快照InnoDB 为每个事务构造了一个数组用来记录并维护系统当前 活跃事务 的ID(“活跃”指的就是启动了但还没提交)。
3.2 设计思路
使用 READ UNCOMMITTED 隔离级别的事务由于可以读到未提交事务修改过的记录所以直接读取记录的最新版本就好了。
使用 SERIALIZABLE 隔离级别的事务InnoDB规定使用加锁的方式来访问记录。
使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务都必须保证读到 已经提交了的 事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交是不能直接读取最新版本的记录的核心问题就是需要判断版本链中的哪个版本是当前事务可见的这是ReadView要解决的主要问题。
ReadView中主要包含4个比较重要的内容分别如下 creator_trx_id 创建这个 Read View 的事务 ID 只有在对表中的记录做改动时执行INSERT、DELETE、UPDATE这些语句时才会为事务分配事务id否则在一个只读事务中的事务id值都默认为0。 trx_ids 表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表 up_limit_id 活跃的事务中最小的事务 ID low_limit_id表示生成ReadView时系统中应该分配给下一个事务的 id 值。low_limit_id 是系统最大的事务id值这里要注意是系统中的事务id需要区别于正在活跃的事务ID low_limit_id并不是trx_ids中的最大值事务id是递增分配的。比如现在有id为123这三个事务之后id为3的事务提交了。那么一个新的读事务在生成ReadView时trx_ids就包括1和2up_limit_id的值就是1low_limit_id的值就是4
3.3 ReadView的规则
有了这个ReadView这样在访问某条记录时只需要按照下边的步骤判断记录的某个版本是否可见。
如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同意味着当前事务在访问它自己修改过的记录所以该版本可以被当前事务访问。如果被访问版本的trx_id属性值小于ReadView中的 up_limit_id 值表明生成该版本的事务在当前事务生成ReadView前已经提交所以该版本可以被当前事务访问。如果被访问版本的trx_id属性值大于或等于ReadView中的 low_limit_id 值表明生成该版本的事务在当前事务生成ReadView后才开启所以该版本不可以被当前事务访问。如果被访问版本的trx_id属性值在ReadView的 up_limit_id 和 low_limit_id 之间那就需要判断一下trx_id属性值是不是在 trx_ids 列表中。
如果在说明创建ReadView时生成该版本的事务还是活跃的该版本不可以被访问。如果不在说明创建ReadView时生成该版本的事务已经被提交该版本可以被访问
3.4 MVCC整体操作流程
下当查询一条记录的时候系统如何通过MVCC找到它
首先获取事务自己的版本号也就是事务 ID获取 ReadView查询得到的数据然后与 ReadView 中的事务版本号进行比较如果不符合 ReadView 规则就需要从 Undo Log 中获取历史快照最后返回符合规则的数据。
如果版本链中的最后一个版本也不可见则该条记录对该事务完全不可见查询结果就不包含该记录。
在隔离级别为 读已提交Read Committed时一个事务中的每一次 SELECT 查询都会重新获取一次 Read View。
事务说明begin;select * from student where id 2;获取一次Read View…select * from student where id 2;获取一次Read Viewcommit; 此时同样的查询语句都会重新获取一次 Read View这时如果 Read View 不同就可能产生不可重复读或者幻读的情况。 当隔离级别为 可重复读 的时候就避免了不可重复读这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View而后面所有的 SELECT 都会复用这个 Read View如下表所示 4. 举例说明
4.1 READ COMMITTED隔离级别下
READ COMMITTED 每次读取数据前都生成一个ReadView。
现在有两个 事务id 分别为 10 、 20 的事务在执行
# Transaction 10
BEGIN;
UPDATE student SET name李四 WHERE id1;
UPDATE student SET name王五 WHERE id1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...此刻表student 中 id 为 1 的记录得到的版本链表如下所示 假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1Transaction 10、20未提交
SELECT * FROM student WHERE id 1; # 得到的列name的值为张三之后我们把 事务id 为 10 的事务提交一下
# Transaction 10
BEGIN;
UPDATE student SET name李四 WHERE id1;
UPDATE student SET name王五 WHERE id1;
COMMIT;然后再到 事务id 为 20 的事务中更新一下表 student 中 id 为 1 的记录
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name钱七 WHERE id1;
UPDATE student SET name宋八 WHERE id1;此刻表student中 id 为 1 的记录的版本链就长这样 然后再到刚才使用 READ COMMITTED 隔离级别的事务中继续查找这个 id 为 1 的记录如下
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1Transaction 10、20均未提交
SELECT * FROM student WHERE id 1; # 得到的列name的值为张三
# SELECT2Transaction 10提交Transaction 20未提交
SELECT * FROM student WHERE id 1; # 得到的列name的值为王五4.2 REPEATABLE READ隔离级别下
使用 REPEATABLE READ 隔离级别的事务来说只会在第一次执行查询语句时生成一个 ReadView 之后的查询就不会重复生成了
比如系统里有两个 事务id 分别为 10 、 20 的事务在执行
# Transaction 10
BEGIN;
UPDATE student SET name李四 WHERE id1;
UPDATE student SET name王五 WHERE id1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...此刻表student 中 id 为 1 的记录得到的版本链表如下所示 假设现在有一个使用 REPEATABLE READ 隔离级别的事务开始执行
# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1Transaction 10、20未提交
SELECT * FROM student WHERE id 1; # 得到的列name的值为张三之后我们把 事务id 为 10 的事务提交一下就像这样
# Transaction 10
BEGIN;
UPDATE student SET name李四 WHERE id1;
UPDATE student SET name王五 WHERE id1;
COMMIT;然后再到 事务id 为 20 的事务中更新一下表 student 中 id 为 1 的记录
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name钱七 WHERE id1;
UPDATE student SET name宋八 WHERE id1;此刻表student 中 id 为 1 的记录的版本链长这样 然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个 id 为 1 的记录如下
# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1Transaction 10、20均未提交
SELECT * FROM student WHERE id 1; # 得到的列name的值为张三
# SELECT2Transaction 10提交Transaction 20未提交
SELECT * FROM student WHERE id 1; # 得到的列name的值仍为张三5. 总结
这里介绍了 MVCC 在 READ COMMITTD 、 REPEATABLE READ 这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使不同事务的 读-写 、 写-读 操作并发执行从而提升系统性能。
核心点在于 ReadView 的原理 READ COMMITTD 、 REPEATABLE READ 这两个隔离级别的一个很大不同就是生成ReadView的时机不同
READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadViewREPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView之后的查询操作都重复使用这个ReadView