mysql事务隔离级别和MVCC详解
-
数据隔离级别。
-
脏写:所有数据库都解决了脏写,脏写是指写入的时候没有加锁,然后两次并发写入不会排队,然后丢失一次修改的更新。即便是redis之类的非关系型数据库也通过CAS乐观锁来解决了脏写问题。
-
脏读:读了没有提交的事务的修改记录,这种数据可能会回滚,这种数据是不能用的。
-
不可重复读:两次读取读到的内容不一样,在脏读已经解决的情况下,不可重复读如果读到的是已经提交的数据,其实第二次读到的是最新的,一定程度上来说,有些时候我们希望读到这种数据。
-
幻读:幻读是不可重复读的一种特殊情况,不可重复读描述的行内数据,幻读描述的整行数据的添加或者删除,两次读取的数量不一样(一般都是这么说,但是我认为不准确)。
-
mysql通过多版本并发控制(MVCC)解决了脏读和不可重复读,mysql通过多版本并发控制,让当前事务不会读到未提交的事务,mysql利用同一个ReadView让二次查询不会查到新提交事务的结果,我们查询一般查的快照而不是当前数据。
-
mysql通过事务隔离级别来平衡mysql 效率和可以接受的上述数据问题。隔离级别越高,上述问题出现的越少,效率越低。
-
mysql innodb 使用 间隙锁,一定程度上解决了幻读。但是需要程序员利用间隙锁,锁定区间不让另外一个事务写入或者删除。
-
-
mysql读取数据有2种情况。
-
一种是当前读,所有带有锁的读取,或者写入读到的数据都是当前读,比如删除,比如更新,比如 for update ,lock in share model。
-
一种是快照读,正常的 select 是快照读,读取的时候要使用ReadView,mysql只有在快照读的时候才会使用MVCC,创建ReadView。
-
-
mysql的MVCC,的版本控制类似下面的图
在undo日志中对同一条数据的修改记录写成一个版本链表,里面记录着数据Id,修改信息,修改事务Id,回滚版本Id。版本链和读试图配合起来可以解决脏读,不可重复读问题。
-
undo日志不是在事务提交以后就立即删除的,事务提交以后,别的事务可能还需要使用它,而是在所有事务Id比它小的都提交以后才考虑删除它。
-
ReadView 的结构。 读视图里面记录着的信息
-
m_ids:当前活跃的事务Ids
-
min_trx_id:最小的事务Id
-
max_trx_id:当前最大的事务Id+1。
-
creator_trx_id:创建这个读视图的事务Id。
-
-
读视图的工作原理
通过ReadView的4个参数参数,在版本链中做一下的判断,如果条件满足那么直接返回当前版本,如果不满足就和版本链的下一条比对。
ReadView和版本链里面记录可以访问有2种情况。
-
第一是版本链事务Id等于视图事务Id,自己写的数据可以读。
-
第二是在视图创建以前就已经提交了的数据,这种可以读。这种数据可以看出三段,小于min_trx_id的都是已经提交的,可以读。大于等于max_trx_id都是未来的,不能读。唯一两者之间的又分两种情况,m_ids里面存在的事务没提交,不可以读。m_ids里面不存在的已提交,可以读。
-
-
不可重复读和可重复读的区别在于第二次查询是不是要生成一个新的ReadView,不可重复读每次查询生成一个ReadView,可重复读第二次查询使用第一次的ReadView。
-
两次快照读之间,只有要当前读的操作(修改,删除,for update,lock in share mode,这里能在版本链写入新记录,说明拿到了写锁,并且前面的版本链的事务已经提交),就会让版本链的最后一个的事务Id,变成当前事务Id,然后另外的事务提交到版本链里面的记录就能被当前事务读到,可重复读被打破。所以这种可重复报被打破的情况算是幻读还是不可重复读?
-
例子1,事务1读取全表数据,读到1条,事务2删除全部数据,并且提交,事务1再次查询全表,还是会得到1条数据。这种情况的数据删除或者添加不会出现幻读问题。
-
例子2,事务1查询全表,得到1条数据,事务2删除全表然后提交,事务1插入一条记录,事务1查询全表依旧有2条数据。没出现幻读。删除那个版本的事务Id,高于事务1里面第一读取数据产生的实物图里面的事务Id,所以查询不到删除,但是添加的那条记录是自己事务,可以查到,所以是7条。
-
例子3,事务1查询指定Id的记录不存在,事务2查询指定Id的记录也不存在,事务2插入指定Id的数据然后提交,事务1插入指定Id的记录然后就会报错。查询不存在,但是我插入就报错,是这幻读吗?我认为是。如果查询条件不是id,之类的唯一索引,而是userName这种,就会插入两个一样的。
-
例子3.1,事务1查询指定userName=a的记录有2天,事务2插入了一条userName=a的记录然后提交,事务1 使用当前读,比更新 userName为一的字段的更新时间为当前时间,然后事务1查询userName=a的发现有3条。两次查询中嵌入了当前读,这个应该比例子3的更加容易明白。
-
例子4,事务1查询了一条记录,然后事务2修改了这条记录的A字段然后提交,事务1然后修改了这条记录的B字段,事务1在查询这条记录的 A 字段已经变了,这是幻读还是不可重复读?我认为是幻读
-
所以幻读不是指的插入删除,读取记录不一样,而是指快照读中嵌入了当前读?让可重复读被打破了。
-
个人对幻读的理解,很多资料都是说幻读是插入删除数据,前后查询的数量不一致。通过例子1和2,我们证明事务1两次查询之间,即便事务2添加,删除并且提交了数据,事务1依旧不会出现两次读取到的内容不一致。例子3,前后不一致,这就是通常我们说的插入数据导致的幻读问题(在用户注册情况下经常出)。例子4我觉得原理和例子3是一样的,都是快照读和当前读的结果不一样,我认为这两种都是幻读。
-
-
个人认为当前读for update,lock in share mode ,可能给版本链添加了一个当前最新版本数据一样的数据,并且事务Id是当前事务。
-
for update 不能命中索引的时候是表锁,可以命中索引的时候是行锁,如果命中的是不存在的数据(比如不存在的userName),那么是间隙锁,间隙锁是共享锁,在修改数据的时候升级成写锁,锁升级,就可能出现死锁问题。mysql处理这种死锁的方案是,默认自动选择一个事务回滚,然后完成另外一个。
posted on 2023-01-09 23:31 zhangyukun 阅读(104) 评论(0) 编辑 收藏 举报