道路越深, 即越孤独.
大家都知道, MySQL innodb 引擎支持事务, 而事务具有 ACID 四大特性, 分别是原子性, 一致性, 隔离性及持久性. 其中事务的隔离性, 指的是当多条事务并发时, 对事务中的 sql 指令的一些同步及加锁约束, 隔离性分为四个隔离级别, 分别是 Read-uncommit,Read-commited,Repeatable-Read,Serializable.
这四种事务隔离级别, 分别对应着不同的同步机制, 其中, 可重复读和序列化是我们最常接触到的, 而另外两种相对用的比较少, 因此接下来我会通过简单的实验来验证一下
可重复读和序列化事务隔离级别的加锁情况与 MVCC 机制.
实验开始前, 我们不妨先记一下试验用的事务分析指令:
- show variables like '%iso%'; // 查看当前连接使用的事务隔离级别
- set global transaction isolation level Repeatable read; // 设置 MySQL 全局事务隔离级别为可重复读
- set session transaction isolation level Repeatable read; // 设置当前会话事务隔离级别为可重复读
- set session transaction isolation level Serializable; // 设置当前会话事务隔离级别为序列化
- show variables like 'AUTOCOMMIT'; // 显示当前会话事务是否自动提交
- set AUTOCOMMIT = 'OFF'; // 禁止事务自动提交
- show processlist; // 查看数据库当前所有连接
- SELECT
- trx_id AS ` 事务 ID`,
- trx_state AS ` 事务状态 `,
- trx_requested_lock_id AS ` 事务需要等待的资源 `,
- trx_wait_started AS ` 事务开始等待时间 `,
- trx_tables_in_use AS ` 事务使用表 `,
- trx_tables_locked AS ` 事务拥有锁 `,
- trx_rows_locked AS ` 事务锁定行 `,
- trx_rows_modified AS ` 事务更改行 `
- FROM
- information_schema.innodb_trx ; // 查看当前数据库的所有正在执行的事务
有了这些指令后, 便可以开始进行实验了, 我们可以先研究一下机制更加简单的序列化.
一般书籍对序列化定义是: 所有事务串行执行.
在楼主未进行实验前, 楼主曾经猜测并深深以为, 序列化的事务是通过多线程存入 , 单线程消费阻塞队列得以实现的 . 因为这样做符合书籍上定义的事务串行执行并且不需要加锁控制.
这次实验, 证明了楼主曾经的以为是错的.
序列化事务, 其实是通过严格的写锁控制来实现的. 下面是实验过程
一: 序列化事务加锁机制探究
1. 建立测试表 serial, 包含 id,key,value 三个字段
2. 开启两条数据库连接
3. 插入几条数据
4. 修改数据库隔离级别为序列化并禁止自动提交
- show processlist;
- show variables like 'AUTOCOMMIT';
- show variables like '%iso%';
- set session transaction isolation level Serializable;
- set AUTOCOMMIT = 'OFF';
5. 修改记录
在连接 1 中执行
UPDATE `test_transaction`.`serial` SET `value`='5' WHERE `id`='1';
然后在连接 2 中执行
UPDATE `test_transaction`.`serial` SET `value`='6' WHERE `id`='2';
然后在连接 1 中执行
UPDATE `test_transaction`.`serial` SET `value`='7' WHERE `id`='2';
最后在连接 2 中执行
UPDATE `test_transaction`.`serial` SET `value`='8' WHERE `id`='1';
6. 总结结论
结果如下
此时发生死锁.
死锁的产生, 即说明了序列化是通过加锁的机制实现事务的. 与此同时, 有
由于产生死锁的事务自动回滚退出, 连接 1 的事务持有 2 个行锁, 即 id 为'1','2'的两行记录. 此时在连接 1 中执行 commit, 事务结束.
二, 可重复读与 MVCC 机制探究
1. 建立测试表 repeatableread, 包含 id,key,value 三个字段
2. 开启两条数据库连接
3. 插入几条数据
4. 修改数据库隔离级别为可重复读并禁止自动提交
- show processlist;
- show variables like 'AUTOCOMMIT';
- show variables like '%iso%';
- set session transaction isolation level Repeatable read;
- set AUTOCOMMIT = 'OFF';
5. 实验研究可重复读 sql 行为与 MVCC
5.1 在连接 1 中执行查询 sql
select * from repeatableread where ID='1';
此时有
这说明可重复读的读操作不加锁. 这就对了, 可重复读的读操作依靠的 MVCC 使其对读操作无需加锁, 只需要读对应版本号的行数据就可以了
5.2 在连接 1 中执行更新 sql
UPDATE `test_transaction`.`repeatableread` SET `value`='8' WHERE `id`='1';
此时有
这说明可重复读的写操作加锁, 那么写操作到底加的是写锁还是读锁呢?
在连接 2 中执行更新 sql
UPDATE `test_transaction`.`repeatableread` SET `value`='8' WHERE `id`='1';
此时有
这就说明可重复读的写操作加的是写锁了.
5.3.MVCC 探究
我们都知道, 可重复读的特点就是在一个事务中重复多读取同一条记录的的查询结果是相同的.
而通过刚才的实验可知, 可重复读的读操作不加锁, 而写操作对行加写锁, 仅仅依靠这样的读写加锁机制是无法实现可重复读的特点的.
那么可重复读的特点是如何实现的呢? 答案就是 MVCC 机制 (多版本并发控制) 了.
要理解 MVCC 机制的话需要引入 ReadView 的概念, ReadView 是什么呢?
(引用部分原文 https://blog.csdn.net/qq_38538733/article/details/88902979 , 因为讲的实在太清楚了!):
ReadView 可以理解为一个列表, 它记录着当前正在活跃的事务, 我们把这个列表命名为为 m_ids.
这样在访问某条记录时, 只需要按照下边的步骤判断记录的某个版本是否可见:
如果被访问版本的 trx_id 属性值小于 m_ids 列表中最小的事务 id, 表明生成该版本的事务在生成 ReadView 前已经提交, 所以该版本可以被当前事务访问.
如果被访问版本的 trx_id 属性值大于 m_ids 列表中最大的事务 id, 表明生成该版本的事务在生成 ReadView 后才生成, 所以该版本不可以被当前事务访问.
如果被访问版本的 trx_id 属性值在 m_ids 列表中最大的事务 id 和最小事务 id 之间, 那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中, 如果在, 说明创建 ReadView 时生成该版本的事务还是活跃的, 该版本不可以被访问; 如果不在, 说明创建 ReadView 时生成该版本的事务已经被提交, 该版本可以被访问.
如果某个版本的数据对当前事务不可见的话, 那就顺着版本链找到下一个版本的数据, 继续按照上边的步骤判断可见性, 依此类推, 直到版本链中的最后一个版本, 如果最后一个版本也不可见的话, 那么就意味着该条记录对该事务不可见, 查询结果就不包含该记录.
在 MySQL 中, READ COMMITTED 和 REPEATABLE READ 隔离级别的的一个非常大的区别就是它们生成 ReadView 的时机不同, 我们来看一下.
READ COMMITTED --- 每次读取数据前都生成一个 ReadView
REPEATABLE READ --- 在第一次读取数据时生成一个 ReadView
小贴士: 事务执行过程中, 只有在第一次真正修改记录时(比如使用 INSERT,DELETE,UPDATE 语句), 才会被分配一个单独的事务 id, 这个事务 id 是递增的.
6. 总结结论
通过实验, 我们验证了可重复读是通过对写操作加写锁, 及通过 MVCC 机制进行无锁读取数据. 这样做, 完全避免了读操作阻塞写操作, 大大地提升了查询效率, 至于该隔离级别并发修改可能引发的更新问题, 在业务中我们一般是通过显式地加锁来避免.
(欢迎想要交流的同学加我 qq:1363890602,qq 群: 297572046, 备注: 编程艺术)
来源: https://www.cnblogs.com/coding-nerver-die/p/10783523.html