事务&锁&MVCC

事务&锁&MVCC

事务四大特性

A(Atomicity)

一组操作要么全成功,要么全失败,目的是保证数据的最终一致性
undo log保证,undo log记录数据修改前的信息,WAL(write ahead log)写前记日志

C(Consistency)

可以理解为事物的目的,A、I、D都是为了保障一致性的手段,保证一致需要程序代码来保证,比如,事务执行过程中出现异常,要回滚事务而不是强行提交事务来导致数据不一致

I(Isolation)

见下文四种事务隔离级别,三种读取错误,锁粒度
读已提交隔离级别:提供语句级快照,可重复读提供的是事务级别的快照

  • 读取时生成一个版本号,等到其他事务commit后才读取最新已commit的“版本号”数据
  • 举例,事务A读取了记录(生成版本号),事务B修改了此记录并且commit(此时加写锁),事务A再读取的时候,是按照最新的版本号来读取的(事务commit后会生成一个新的版本号),如果事务B还没有commit,那事务A读取的还是之前的版本号的数据

读提交隔离级别

  • 读已提交解决了脏读,但不能避免不可重复读,即一个事务读到另一个事务已提交的数据,也就是说一个事务可以看到别的事务已经提交的修改
  • 不可重复读例子
  • A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的】
    MVCC利用事务级别的快照,每次读取的都是的都是当前事务的版本,即使当前数据被其他事物修改了也只会读取当前事务版本的数据
    InnoDB的RR解决通过MVCC解决了幻读的问题(因为它是读历史版本的数据)解决了快照读的问题。
    至于当前读(比如Select * from table for update),则需要配合间隙锁和行锁(Next-Key Lock)来解决幻读的问题

RR可重复读
默认隔离级别

串行化是最高的隔离级别,不允许事务并行,事之间是串行的,效率最低,但同时也最安全。

D(Durability)

redo log保证,修改数据时,找到磁盘上记录所在的页,再把页加载到内存缓冲池中进行修改。但是内存修改完,MySQL可能会挂掉。

  • mysql为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用,然后后台线程定时或达到条件顺序刷新脏数据到磁盘中,然后使用后台线程去做缓冲池和磁盘之间的同步。那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?还没来得及执行上面图中红色的操作。这样会导致丢部分已提交事务的修改信息
  • 所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。
  • 写完内存会写一份redo log(顺序写,写入速度快,记录的是物理修改,在xx页做了xx修改,文件体积小,恢复速度快),即使MySQL中途挂掉,也可以通过redo log恢复数据
    在这里插入图片描述
    总结:redo log是用来恢复数据的, 用于保障已提交事务的持久化特性

ACID详解
https://mp.weixin.qq.com/s/n5gWGEvE7Lqk-5W2Kgdhpg
MVCC改进读未提交不能避免脏读的问题

InnoDB引擎下,按锁粒度分为表锁行锁行锁实际上是作用在索引上。当我们的SQL命中了索引,那锁住的就是命中条件内的索引节点(这种就是行锁),如果没有命中索引,那我们锁的就是整个索引树(表锁)
行锁简单分为读锁(共享锁,S锁)和写锁(排他锁,X锁)

MVCC

关系型数据库中处理事务冲突的主要手段,目的是提高数据库高并发场景下的吞吐性能。

背景

四种事务隔离级别

----------读未提交
脏读
----------读已提交
不可重复读
----------可重复读
幻读
----------串行化

脏读,不可重复读,幻读

脏读:一个事务读取到了另外一个事务没有提交的数据
不可重复读:在同一事务中,两次读取同一数据,得到内容不同
幻读:同一事务中,用同样的操作读取两次,得到的记录数不相同
MySQL默认可重复读,可以解决脏读和不可重复读的问题,但不能解决幻读问题,串行化虽然可以解决幻读问题,但会大幅降低事务并发能力
MVCC通过乐观锁解决不可重复读和幻读问题,它可以在大多数情况下替代行级锁,降低系统的开销

乐观锁与悲观锁

乐观锁:每次取数据的时候都认为他人不会对其修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。悲观锁:每次取数据的时候都认为他人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)

是什么,解决了什么?

通过数据行的多个版本管理来实现数据库的并发控制,简单来说就是保存数据的历史版本

我们可以通过比较版本号决定数据是否显示出来,读取数据的时候不需要加锁也可以保证事务的隔离效果

  1. 读写之间阻塞的问题。通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力
  2. 降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行
  3. 解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果
快照读(Consistent Read)

读取的是快照数据,不加锁的简单的SELECT都属于快照读(只是普通的读操作),直接利用MVCC进行读取,不会进行加锁
就是淡出的SELECT语句,返回的是ReadView,不包括以下两句:

SELECT … FOR UPDATE
SELECT … LOCK IN SHARE MODE

小贴士
对于 SERIALIZABLE 隔离级别来说,如果 autocommit 系统变量被设置为OFF,那普通读的语句会转变为锁定读,和在普通的 SELECT 语句后边加 LOCK IN SHARE MODE 达成的效果一样

实现原理

undo log + MVCC实现,举例说明:
下图右侧黄色部分是数据:一行数据记录,主键 ID 是 10,object = ‘Goland’ ,被 update 更新为 object = ‘Python’ .
事务会先使用排他锁锁定该行,将该行当前的值复制到 undo log 中,然后再真正地修改当前行的值,最后填写事务的 DB_TRX_ID ,使用回滚指针 DB_ROLL_PTR 指向 undo log 中修改前的行
在这里插入图片描述
DB_TRX_ID6字节字段,表示最后更新的事务id(update , delete , insert)。删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已软删除
DB_ROLL_PTR7 字节回滚指针,指向前一个版本的 undo log 记录,组成 undo 链表。如果更新了行,则撤消日志记录包含在更新行之前重建行内容所需的信息

小贴士:
insert undo log 只在事务回滚时需要, 事务提交就可以删掉了。update undo log 包括 update 和 delete , 回滚和快照读都需要

当前读(Locking Read)

当前读就是读取最新数据,而不是历史版本的数据,并且要获取相应记录的锁。加锁的SELECT,或者对数据进行增删改都会进行当前读(包括加锁的读取和DML操作)
以下为SQL类型

select … lock in share mode 、
select … for update、
update 、delete 、insert

获取的锁类型取决于当前事务的隔离级别、语句的执行计划、查询条件等因素。例如,要 update 一条记录,在事务执行过程中,如果不加锁,那么另一个事务可以 delete 这条数据并且能成功 commit ,就会产生冲突了。所以 update 的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

实现原理

Next-Key Lock(锁定一个范围并且锁定记录本身) = 行锁(Record Lock,锁直接加在索引记录上面) + 间隙锁(Gap Lock, Innodb 为了解决幻读问题时引入的锁机制,所以只有在 Read Repeatable 、Serializable 隔离级别才有)
例子
在这里插入图片描述
测试可知 delete from T where age = 7; 语句在 age 上的加锁区间为 (4,10) ,图解如下
在这里插入图片描述

MVCC实现原理

通过read view和undo log实现
undo log记录修改之前的数据,通过undo log照度指定版本的数据
read view实际上就是在查询时,InnoDB会生成一个read view,read view的几个字段分别是

  • trx_ids(系统当前正在活跃未提交的事务 ID 集合)
  • low_trx_id(活跃的事务中最小的事务 ID)
  • up_trx_id(活跃的事务中最大的事务 ID)
  • creator_trx_id(创建这个 Read View 的事务 ID)
    在每行数据有两列隐藏的字段,分别是DB_TRX_ID(记录着当前ID)以及DB_ROLL_PTR(指向上一个版本数据在undo log中里的位置指针)
    read view

总结
MVCC其实就是靠对比版本来实现读写不阻塞,而版本的数据保存于undo log中
而针对不同的隔离级别(读提交和可重复读),无非是在read uncommit隔离级别下,每次都获取一个新的read view,可重复读则是每次事务只获取一个read view
读视图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值