mysql事务


事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。 MySQL 原生的 MyISAM 引擎就不支持事务, InnoDB 引擎支持事务。

事务四个特性

  • 原子性(Atomicity):一个事务必须被视为一个不可分割的工作单元。事务开始后所有操作,要么全部做完提交,要么全部不做回滚,不可能停滞在中间环节。
  • 一致性(Consistency):事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。比如A向B转账,不可能A扣了钱,B却没收到。
  • 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,另一条账户汇总程序开始运行,并不能发现A账户钱变少了。
  • 持久性(Durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响。

事务并发问题

  • 脏读:脏读指一个事务「读到」了另一个「未提交事务修改过的数据」。
  • 不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。
  • 幻读:在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。幻读指的是事务中读取到的记录数量不一致,
    不能认为可重复读就一定会出现幻读问题,必须要上串行化级别才能解决幻读问题。
    在快照读情况下,可重复读可以避免幻读(MVCC)。
    在当前读情况下,可重复读还是会出现幻读,要串行化级别才能解决。

快照读和当前读

  • 快照读:普通Select语句
  • 当前读(加锁读):update、insert、delete和select … lock in share mode和select … for updte

InnoDB的RR有没有解决幻读

InnoDB中的REPEATABLE READ这种隔离级别通过间隙锁+MVCC解决了大部分的幻读问题,但是并不是所有的幻读都能解读,想要彻底解决幻读,需要使用Serializable的隔离级别。

RR中,通过间隙锁解决了部分当前读的幻读问题,通过增加间隙锁将记录之间的间隙锁住,避免新的数据插入。

RR中,通过MVCC机制的,解决了快照读的幻读问题,RR中的快照读只有第一次会进行数据查询,后面都是直接读取快照,所以不会发生幻读。

但是,如果两个事务,事务1先进行快照读,然后事务2插入了一条记录并提交,再在事务1中进行update新插入的这条记录是可以更新出成功的,这就是发生了幻读。

还有一种场景,如果两个事务,事务1先进行快照读,然后事务2插入了一条记录并提交,在事务1中进行了当前读之后,再进行快照读也会发生幻读。

事务隔离级别

  • 读未提交(read uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交(read committed):一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读(repeatable read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。这是MySQL InnoDB 引擎的默认隔离级别;
  • 串行化(serializable ):强制所有的事务按序执行,使不同的事务之间不可能产生冲突。具体做法是对于每一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。所以可能导致大量的超时和锁争用问题。除非系统严格确保数据安全且可以接受并发性能下降时可以设置该隔离级别。

事务隔离级别原理

MVCC(快照读)

详细逻辑:
InnoDB为数据库中的每一行添加了三个隐藏字段:DB_TRX_ID(事务版本号)、DB_ROLL_PTR(回滚指针)、DB_ROW_ID(隐藏ID)。

DB_TRX_ID:记录了创建/更新这条数据的事务版本号(版本号会递增)。
DB_ROLL_PTR:记录了一个指向undo log中历史版本的数据指针。(用来支持回滚操作)
DB_ROW_ID:一个自增的隐藏行ID。
InnoDB基于事务版本号、回滚指针这两个字段,可以在undo log中形成一个单向链表,最新版本的数据放在链表头部,历史数据通过DB_ROLL_PTR指针进行关联。如下图所示

有了这种结构的数据后,InnoDB可以很方便的管理多个版本的数据,也为MVCC的实现打下来基础。

接下来我们来了解一下MVCC在InnoDB中具体的实现逻辑是怎样的,以及MVCC解决了哪些问题。

首先,InnoDB在事务开启后执行第一个查询时,会创建一个快照(下文称之为ReadView),这个ReadView包含了以下信息

m_ids: 活动事务id列表(活动事务指的是已经开始、尚未提交/回滚的事务)
min_trx_id: 最小活动事务id
max_trx_id:最大活动事务id
creator_trx_id:当前事务id
紧接着InnoDB会通过查询语句定位到最新版本的数据行,并根据以下规则获取到可以访问的数据版本。

如果被访问版本的trx_id,与readview中的creator_trx_id值相同,表明当前事务在访问自己修改过的记录,直接返回该版本的数据;

如果被访问版本的trx_id,小于readview中的min_trx_id值,表明生成该版本的事务在当前事务生成readview前已经提交,直接返回该版本的数据;

如果被访问版本的trx_id,大于或等于readview中的max_trx_id值,表明生成该版本的事务在当前事务生成readview后才开启,此时该版本不可以被当前事务访问,需要通过隐藏的回滚指针从undo log中读取历史版本;

如果被访问版本的trx_id,在readview的min_trx_id和max_trx_id之间,则需要判断trx_id值是否在m_ids列表中?

如果在:说明readview创建时,创建该版本数据的事务还未提交,因此需要通过回滚指针读取历史版本并返回。
如果不在:说明readview创建时,创建该版本数据的事务已经提交,所以直接返回该版本的数据;
可重复读隔离级别下,ReadView只会在第一次查询时创建,同一个事务中后续所有的查询共用一个ReadView,由此便解决了不可重复读的问题。

读已提交隔离级别下,每次查询都会创建一个新的ReadView。新建的ReadView会更新creator_trx_id以外的其余字段,因此不可重复读现象依然存在。但是由于ReadView可以判断出修改此数据的事务是否已经提交,因此可以避免脏读的出现。

其次,从上述MVCC实现逻辑中可以发现,没有任何加锁、获取锁的操作,因此MVCC读操作不会因为等待锁而阻塞(也就是常说的非阻塞读)。

注意:
对于读未提交所有事务都是读取数据库最新值即可,对于串行化的隔离级别所有请求都会加锁,不需要通过MVCC实现。

快照读不会出现幻读,当前读能出现幻读。

当前读(行锁、间隙锁、临键锁)

InnoDB在可重复读隔离级别中,通过间隙锁解决了部分当前读的幻读问题,通过增加间隙锁将记录之间的间隙锁住,避免新的数据插入。

  • 行锁(Record Lock):锁直接加在索引记录上面。防止其它事务 新增/更新/删除 该行。
  • 间隙锁(Gap Lock):锁定索引记录间隙。防止其它事务新增记录。
    间隙锁是针对事务隔离级别为可重复读或以上级别而已的。解决了部分当前读的幻读问题
  • 临键锁(Next-Key Lock) :行锁和间隙锁组合起来就叫Next-Key Lock。

注意:所有锁都是针对于索引的

行锁(Record Lock)

select … lock in share mode(共享锁/读锁)
select … for update (排它锁/写锁)
update 、delete、insert(排它锁/写锁)

间隙锁(Gap Lock)

间隙锁(Gap Lock)是Innodb在可重复读隔离级别下为了解决幻读问题时引入的锁机制,幻读的问题存在是因为新增数据记录操作,这时如果进行范围查询的时候(加锁查询),会出现不一致的问题,这时使用行锁已经没有办法满足要求,需要对一定范围内的数据进行加锁。
间隙锁是不分排他锁和共享锁,它只阻塞写入,所以两个事务分别持有相同的间隙锁后,其中某个事务在间隙里面新增记录,此时会死锁。

事务的传播行为

链接: 点这里

  • 39
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值