当事务出错,需要回滚时,怎么办?MySQL提供了undo 重做日志。
对于MySQL的一条记录有几个隐藏字段,如下图:
如果表没有主键,会默认生成一个row_id,一般表都有主键。
另外两个 trx_id 事务id,表示修改这条记录的事务。 roll_pointer, 该记录的上一条信息,指向的就是一个undo log记录。
最终一个记录会有一串历
史数据,对应不同的事务id,这就是Mysql的MVCC,多版本并发控制的实现原理。
当数据回滚时,只要根据roll_pointer,就可以回滚到历史版本了。
MVCC的实现
我们知道事务的隔离级别,有下面四个。
READ UNCOMMITTED:未提交读。
READ COMMITTED:已提交读。
REPEATABLE READ:可重复读。
SERIALIZABLE:可串行化。
对应的问题如下:
MySQL默认 RR级别,对于一般数据库,会出现幻读,比如事务1执行查询 id >2 的数据,然后事务2插入一个id=10的记录,提交了。 事务1在次执行就有可能看到这条记录,不过MySQL中解决了这个问题。很神奇吧,MySQL是如何实现的呢?
每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:
为了实现隔离级别,MySQL提出ReadView的概念。
ReadView中主要包含4个比较重要的内容:
m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
小贴士: 注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。
creator_trx_id:表示生成该ReadView的事务的事务id。
有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
对于不同的隔离级别,ReadView的工作机制如下:
ReadCommit 级别 读已提交
READ COMMITTED —— 每次读取数据前都生成一个ReadView
事务1,查询
事务2 id 200 执行插入,然后提交。
事务1 继续查询。
事务1在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[200],min_trx_id为200,max_trx_id为201,creator_trx_id为0。
对于select语句,是不生成事务id的,所以creator_trx_id为0。
第一次查询, 事务1 2 都在
事务id因为0,readview中
m_ids:200
min_trx_id:200
max_trx_id:201
creator_trx_id:0
第一次查询还没有事务200的数据,也看不到。第二次执行,生成新的ReadView
事务1的readview中
m_ids:[]
min_trx_id:[]
max_trx_id:201
creator_trx_id:0
没有活跃的事务id,所以也查不出来。
对于RR的级别也是一样,ReadView以第一次为准,事务200提交的也看不到。
如果事务1插入 id为100,提交,事务2插入后 id为200,有了事物id,两次查询,RC与RR就不一样了。
RC下,事务200可以读取100的。
对于RR级别,以第一次的RV为准,所以即使100提交了, 100在 事务id中,还是看不到,这就避免了幻读。
幻读现象
但是有一种情况,就是事务2提交数据后,事务1修改了这条数据,这时候这条数据的事务id就是事务1的id,就能看到了。
还有一种情况,事务1执行了时,事务2插入id=1提交,事务1也插入,报错。但是事务1怎么查却查不出来。
解决方式,select for update.
https://segmentfault.com/a/1190000016566788?utm_source=tag-newest