像select lock in share mode(共享锁); select for update, update,delete,insert(排它锁)这些操作就是一种当前读,因为它读取的是数据的最新版本,读取时还要保证其他事务不能修改当前记录,会对记录进行加锁。
不加锁的select就是快照读,即不加锁的非阻塞读;(快照读的前提是隔离级别不是串行化,串行化的隔离级别下快照读会退化成当前读) 之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但是它在很多情况下避免了加锁操作,降低了开销,既然是基于多版本,所以快照读可能读到的不一定是数据的最新版本,而有可能是之前的历史版本。
说白了MVCC就是为了实现读-写不冲突,而这个读指的就是快照读,而非当前读,当前读实际上是一种加锁的操纵,是悲观锁的实现
数据库并发场景有三种,分别是:
MVCC是一种解决写-读冲突的无锁并发控制手段,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决一下问题:
总结:MVCC就是开发人员不满意只让数据库采用悲观锁(加锁)这样性能不佳的形式去解决读-写的问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以我们可以形成两个组和
MVCC解决写-读冲突,悲观锁解决写写-冲突
MVCC解决读写冲突,乐观锁解决写写冲突。
在一张表中,除了我们自定义的列,实际上MySQL会隐式的定义DB_TRX_ID(最后一次修改该记录的事务ID), DB_ROLL_PTR(指向这条记录的上一个版本), DB_ROW_ID(隐藏主键)等字段。
如上图,DB_ROW_ID是数据库为改行记录生产的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个版本。
undo log主要被分为两种:
代表事务在insert新纪录时产生的undo_log,只在事务回滚时需要,并且在事务提交后可以被立即抛弃。
事务在update或者delete时产生的undo log,不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只要在快速读或者事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。
(1)在事务1修改该行数据时,数据会先对这行记录加排它锁
(2)然后把改行数据拷贝到undo log中,作为旧记录,即在undo log中由当前行的拷贝副本
(3)拷贝完毕后,修改该行的name为tom,并且修改隐藏字段的事务ID为当前事务1的ID,我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,即表示我的上一个版本就是它
(4)事务提交后,释放排它锁
(1)在事务2修改该行数据时,数据库也先为该行加在事务2修改该行数据时,数据库也先为该行加在事务2修改该行数据时,数据库也先为该行加锁
(2)然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
(3)修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
(4)事务提交,释放锁
从上面我们可以看出,不同事务或者相同事务对同一记录的修改,会导致该记录的undo log称为一条记录版本线性表,即链表,undo log的表头就是最新的旧记录,(当然就像之前说的该undo log的节点可能是会purge线程清除掉,像图中的第一条 insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)。
RV就是事务进行快照读操作时产生的读视图(RV),在该事务执行快照读的那一刻,会生成数据库系统的当前的一个快照,记录并维护当前活跃事务的ID(当每个事务开启时,都会被分配一个ID,这个ID是自增的,所以最新的事务,ID值越大)
所以我们知道RV主要是用来做可见行判断的,即当我们某个事务执行快照读的时候,对该记录创建一个RV读视图,把它比作条件来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
RV遵循一个可见性算法,主要是将要被修改的数据的最新记录的DB_TRX_ID(即当前事务ID),与系统当前其他活跃事务的ID去对比(由RV维护),如果DB_TRX_ID跟RV的属性做了某些对比,不符合可见性,那么就由DB_ROLL_PTR回滚指针去取出undo log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链表头到尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID,那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。
我们可以将RV简单的理解为三个全局属性
1. trx_list 一个数值列表,用来维护RV生成时刻此时系统正活跃的事务ID
2. up_limit_id 记录trx_list中的最小的事务ID
3. low_limit_id RV生成时刻系统尚未分配的下一个事务ID,即目前(不一定是RV中)已经出现过的事务ID最大值+1
我们在了解了隐式字段,undo log, 以及Read View的概念之后,就可以来看看MVCC实现的整体流程是怎么样了,整体的流程是怎么样的呢?我们可以模拟一下
在上表的顺序下,事务B的在事务A提交修改后的快照读是旧版本数据,而当前读是实时新数据400
而在表这里的顺序中,事务B在事务A提交后的快照读和当前读都是实时的新数据400,是因为这里与上表的唯一区别仅仅是表1的事务B在事务A修改金额前快照读过一次金额数据,而表2的事务B在事务A修改金额前没有进行过快照读。
所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力,我们这里测试的是更新,同时删除和更新也是一样的,如果事务B的快照读是在事务A操作之后进行的,事务B的快照读也是能读取到最新的数据的
正是由于生成Read View的时机不同,从而造成RC RR级别下快照读的结果的不同。
总之在RC隔离级别下,每次快照读都会生成最新的ReadView;而在RR级别下,则是同一个事务中的第一个快照读才会创建ReadView,之后的快照读获取的都是同一个ReadView
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有