验证mysql事务隔离级别机制加锁情况与MVCC机制

道路越深,即越孤独。

 大家都知道,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,备注:编程艺术)

猜你喜欢

转载自www.cnblogs.com/coding-nerver-die/p/10783523.html