这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
使用场景:
并发事务大致可以分为4类,读读,写写,读写,写读。读读不会影响数据。
- 读读场景:不会引起数据的变化,不用做任何处理。
- 写写场景:会有更新覆盖的问题,需要进行处理,需要进行上锁处理。
- 读写写读场景:两种类似,写读,会有脏读,不可重复读,幻读出现,需要处理,使用的是mvcc机制处理。
MVCC机制:
多版本并发控制技术,主要用来提升数据库性能设计的,处理读写并发冲突。主要是在InnoDB中使用。
MySQL中仅在RC读已提交级别、RR可重复读级别才会使用MVCC机制。
实现原理-三种支持技术:
通过MVCC机制的隐藏字段,Undo-log日志和ReadView三个东西实现。
隐藏字段:
主要有DB_ROW_ID、DB_Deleted_Bit、DB_TRX_ID、DB_ROLL_PTR这四个隐藏字段。
隐藏主键-RowID:聚簇索引没有指定主键会选取一个合适的作为主键或者生成,也就是这个主键
没有主键选取规则:1自增键位,2非空字段,3创建一个递增的Row_ID作为聚簇索引列
删除标识-Deleted_Bit:先设置为1,用于标识有事务将该条数据标识为delete了,其他sql不会将这条数据作为结果。如果事务成功则等待purger来进行删除,如果事务失败回滚,设置为0就好。用这个机制的优点在于,不用真正再索引中删除,使得树结构改变。
purger删除机制,在设置了删除标识为1后,会自动删除缓存树,防止占用过多的磁盘,purger自身维护了一个ReadView。
最近更新的事务ID-TRX_ID(6Byte) :对于每一个创建的事务都会有一个事务ID,事务ID递增,除了查询的事务ID=0,用于记录事务的ID
回滚指针-ROLL_PTR(7Bytes): 每个事务都会有一个回滚指针,老版本的数据在Undo-log中,rollback_pointer就是地址指针,指向undo-log中的旧版本的数据。需要回滚事务就需要用到这个隐藏列。
undo-log日志:
undo-log记录了旧版本的数据,这个是一个版本链,不止一个版本的数据,只要有修改操作就会被记录,串起来的版本链,最新的旧数据在链表头。版本链就是多版本。
对update语句举例:
先在数据上加排他锁,然后数据拷贝到undo-log,然后数据修改,设置trx_id为当前事务ID,隐藏字段的roll_ptr,然后提交事务释放锁。
ReadView:
就是一个事务读取一条数据时,MVCC对Mysql的运行状态生成一个快照,就是ReadView,一个select事务开始后,就会生成一个ReadView,包含四个部分:
- create_trx_id:ReadView的事务ID
- trx_ids:生成当前Read_View时,系统活跃的事务的ID列表。
- up_limit_id:活跃事务列表中最小的ID
- low_limit_id:生成当前Read_View时,系统分配的下一个事务ID。
实现原理-运行原理:
- 事务进行修改操作,将旧数据放入Undo-log日志中。
- 事务进行查询操作,MVCC会生成一个ReadView快照。
undo-log主要存储数据的历史版本,ReadView主要进行多版本并发控制。
就是首先会去获取表中行数据的隐藏列,然后经过上述一系列判断后,可以得知:目前查询数据的事务到底能不能访问最新版的数据。如果能,就直接拿到表中的数据并返回,反之,不能则去Undo-log日志中获取旧版本的数据返回。如果Undo-log日志中的旧数据存在一个版本链时,此时会首先根据隐藏列roll_ptr找到链表头,然后依次遍历整个列表,从而检索到最合适的一条数据并返回。
- ReadView.trx_ids中的数据时旧数据不能读取。因为还没有提交。
select查询到新添加的数据,这时候roll_ptr=null,不会读取到这条数据。查询数据的事务不能读取最新版数据,同时又无法从版本链中找到旧数据,那就意味着这条数据对T1事务完全不可见。
RC,RR不同级别下的MVCC机制:
MySQL的事务隔离机制处于RC读已提交级别。
总结:
MVCC多版本并发控制,就是通过undo-log记录历史版本数据,然后通过隐藏字段+ReadView快照进行回滚和并发处理逻辑。