Read Committed 为什么不能防止不可重复度现象

我们都知道MySQL 数组库有四大事务隔离级别,分别是未提交读(Read Uncommitted)、提交读(Read Committed)、可重复度(Repeatable Read)、可串行化(SERIALIZABLE).

其中每个隔离级别有不同的特性

未提交读可能会导致脏读,不可重复度和幻读问题。

提交读因为只能读取已经提交的数据,所以可以避免脏读,但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据,所以不能避免不可重复读和幻读现象,

可重复读则可以避免脏读和不可重复度现象,但是不能避免幻读现象

可串行化则能同时避免脏读、不可重复读和幻读现象。

相信大家对上面的四个隔离级别的特征都耳熟能详,但是今天我突然发现一个问题,为什么提交读不能避免不可重复现象,既然读的事务还没有提交,为什么其他事务可以修改刚才读到的数据?既然一个字段或一条记录已经被一个事务的读锁占据,为什么还会被另一个事务的写锁获取到?

先看一个提交读的隔离级别下发生不可重复度读现象的一个例子

① 打开命令客户端A,将这个客户端的隔离级别设置为提交读(Read Committed),然后开启事务,读取account表中的数据

第一次读取account表的数据,money 为1000.00,不提交事务

 ② 开启命令行客户端B, 同样设置事务的隔离级别为提交读,开启事务,对account 表中tom的 money 进行修改,不提交事务

客户端开启事务后B修改表数据,但是不提交事务

 ③ 客户端A 再次读取表中数据,因为客户端B的事务还未提交,所以客户端A读取不到客户端B修改后的值,读取到的数据仍是上一次事务提交后的值1000, 这就避免了脏读

④ 客户端B提交事务

⑤ 客户端 A 再次查看,读取到 2000, 发现和上一次读取的money值不一样,同一个事务中前后两次读取到的数据不一样,发生了不可重复度现象

 

  通过上面这个例子,可以看到提交读的隔离级别下,确实不能避免不可重复读现象,现在的疑问是,明明客户端A的事务1已经先对记录加了读锁,为什么客户端B的事务2可以修改记录的数据呢?按我们常规的想法不应该是读锁和写锁互斥吗?

 原因:

MySQL 中的提交读和可重复读两个隔离级别是使用多版本并发控制 MVCC 来实现的,而不是通过添加读写锁来实现的,如果通过读写锁来实现隔离级别的话,只有读读可以并发,读写,写读,写写都不能并发,这样数据库的并发度太低了,所以一般不通过加读写锁来实现隔离级别。而如果使用 MVCC 来实现 提交读和可重复读两个隔离级别的话则可以在读的时候不加锁,读写和写读可以同时进行,只有写写需要阻塞,这样就极大地提高了并发度。

MVCC 机制会记录每行数据的历史版本,通过可见性算法、undo 日志以及 read view 控制每个读操作所读取的行数据历史版本,

Repeatable Read 在事务发生第一次读的时候选定所要读取的数据行的版本,整个事务都读取这一个版本的数据行,所以可以重复读,每次读取的数据都一致。

Read Committed 在事务中每次读操作都是读取最新的行数据版本,而这最新的数据行版本很可能是某个事务进行了修改操作后提交的,所以可能会发生多次读取同一行数据,但是前后读取的数据不一致的情况。这就是不可重复读现象,所以提交读不能避免不可重复度现象。

想要详细了解MVCC是如何实现事务隔离的,可以阅读这篇博客,MySQL中MVCC的正确打开方式(源码佐证),写的非常好,强力推荐。

文章参考:

不可重复读(read-committed)读已提交例子

mysql事务之提交读(Read Committed)

原文地址:https://www.cnblogs.com/hi3254014978/p/12721152.html