数据库事务

事务概念

  数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

事务特性

原子性(Atomicity)

  一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。

一致性(Consistency

  数据库总是从一个一致性的状态转换到另外一个一致性的状态。如:拿转账来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。

隔离性(Isolation

  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
  即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

隔离级别

隔离级别脏读不可重复读幻读特点
读未提交(Read Uncommitted)可能可能可能所有事务都可以看到其他事务没有提交的修改,实际业务中很少使用
读已提交(Read Committed)不可能可能可能只能看到其他已经提交的事务
可重复读(Repeatable Read)不可能不可能可能确保同一个事务在并发读取的时候,读到的结果是一致的。mysql默认的隔离级别
串行化(Serializable)不可能不可能不可能串行读取数据,锁争取严重

不可重复读与幻读的区别是什么呢?

  • 不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的「数据不一样」。(因为中间有其他事务提交了修改);
  • 幻读的重点在于新增或者删除:在同一事务中,同样的条件,第一次和第二次读出来的「记录数不一样」。(因为中间有其他事务提交了插入/删除)。

设置隔离级别的语法

# 查看事务隔离级别 5.7.20 之后
show variables like 'transaction_isolation';
SELECT @@transaction_isolation

# 5.7.20 之后
SELECT @@tx_isolation
show variables like 'tx_isolation'

修改隔离级别的语句是:set [作用域] transaction isolation level [事务隔离级别],
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}。

其中作用于可以是 SESSION 或者 GLOBAL,GLOBAL 是全局的,而 SESSION 只针对当前回话窗口。隔离级别是 {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} 这四种,不区分大小写。

比如下面这个语句的意思是设置全局隔离级别为读提交级别。

mysql> set global transaction isolation level read committed;

Mysql事务隔离的原理

  MySQL 事务隔离其实是依靠来实现的,加锁自然会带来性能的损失。

  读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。所以根本谈不上什么隔离效果,可以理解为没有隔离。

  再来说串行化。读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。

  读已提交和可重复读,MySQL 采用了 MVCC (多版本并发控制) 的方式。我们在数据库表中看到的一行记录可能实际上有多个版本,每个版本的记录除了有数据本身外,还要有一个表示版本的字段,记为 row trx_id,而这个字段就是使其产生的事务的 id,事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。

  在很多人介绍读提交和可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,这也是可重复读和不可重复读的关键,可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提

交则是每次执行语句的时候都重新生成一次快照。

持久性(Durability

  一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。持久性是个有点模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必。而且「不可能有能做到100%的持久性保证的策略」否则还需要备份做什么。

MVCC原理

在讲解MVCC之前先了解两个概念:快照读和当前读

  • 快照读:读取的是快照版本。普通的SELECT就是快照读。通过mvcc来进行并发控制的,不用加锁。
  • 当前读:读取的是最新版本。UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是当前读。

那么MySQL是如何避免幻读?

  • 在快照读情况下,MySQL通过mvcc来避免幻读。
  • 在当前读情况下,MySQL通过next-key来避免幻读(加行锁和间隙锁来实现的)。

MVCC的实现主要依赖两个概念:undo log(回滚日志) 和 read view。

undo log

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • DB_TRX_ID:6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR:7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • DB_ROW_ID:6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
  • 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

Read view

Read view 是“快照读”sql执行时MVCC提取数据的依据,read view是一个数据结构,包含4个字段

字段含义
m_ids当前活跃的事务编号集合
min_trx_id最小活跃事务编号
max_trx_id预分配事务编号,当前最大事务编号+1
creator_trx_idread view创建者的事务编号

Read view 匹配条件规则如下:

  1. 如果数据事务ID trx_id < min_limit_id,表明生成该版本的事务在生成Read View前,已经提交(因为事务ID是递增的),所以该版本可以被当前事务访问。
  2. 如果trx_id>= max_limit_id,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。
  3. 如果 min_limit_id =<trx_id< max_limit_id,需腰分3种情况讨论
  • (1).如果m_ids包含trx_id,则代表Read View生成时刻,这个事务还未提交,但是如果数据的trx_id等于creator_trx_id的话,表明数据是自己生成的,因此是可见的。
  • (2)如果m_ids包含trx_id,并且trx_id不等于creator_trx_id,则Read View生成时,事务未提交,并且不是自己生产的,所以当前事务也是看不见的;
  • (3).如果m_ids不包含trx_id,则说明你这个事务在Read View生成之前就已经提交了,修改的结果,当前事务是能看见的。

MVCC推导过程

下面通过案例对上面的规则进行解释(其中读已提交和可重复读的实现都是按照上面的规则执行的,只不过两者生成的read view的时机不一样)

读已提交(RC):在每次执行快照读时生成一个全新的read view

image-20240130230950462

unlog链如下:

image-20240204215401224

MVCC的原理就是通过undo log链从上到下逐个按照上面提到的匹配规则进行匹配,直到匹配成功的那个,就是当前事务可以看到的快照版本。下面我们代入规则走一遍

在trx_id=4的事务中,第一次生成的read view中m_ids=[2,3,4],min_trx_id=2,max_trx_id=5,creator_trx_id=4,代入规则过程如下:

  1. 快照trx_id=3:min_trx_id<=3<max_trx_id,进一步判断了3在trx_id在m_ids中,但是不等于creator_trx_id,所以看不到快照3,因此需要进一步判断前一个快照
  2. 快照trx_id=2:min_trx_id<=3<max_trx_id,进一步判断了2在m_ids中,但是2不风雨creator_trx_id,所以还是看不到,需要继续判断前一个快照
  3. 快照trx_id=1:由于1<min_trx_id,表明生成该版本的事务在生成Read View前,已经提交,所以可以看到

最终结果就是当前事务4可以看到的是快照trx_id=1的信息,即name=‘A’

而trx_id=4的事务中,第二次生成的read view中m_ids=[2,4],min_trx_id=2,max_trx_id=5,creator_trx_id=4,代入规则过程如下:

  1. 快照trx_id=3:min_trx_id<=3<max_trx_id,进一步判断了3不在trx_id在m_ids中,说明这个事务在Read View生成之前就已经提交了,所以当前事务4可以看到事务3的快照,判断结束

最终结果就是当前事务4可以看到的是快照trx_id=3的信息,即name=‘C’、

可重复读(RR):仅在第一次执行快照读时生成read view,后续快照读复用c

image-20240204221548353

image-20240204215401224

在trx_id=4的事务中,第一次生成的read view中m_ids=[2,3,4],min_trx_id=2,max_trx_id=5,creator_trx_id=4,代入规则过程如下:

  1. 快照trx_id=3:min_trx_id<=3<max_trx_id,进一步判断了3在trx_id在m_ids中,但是不等于creator_trx_id,所以看不到快照3,因此需要进一步判断前一个快照
  2. 快照trx_id=2:min_trx_id<=3<max_trx_id,进一步判断了2在m_ids中,但是2不风雨creator_trx_id,所以还是看不到,需要继续判断前一个快照
  3. 快照trx_id=1:由于1<min_trx_id,表明生成该版本的事务在生成Read View前,已经提交,所以可以看到

最终结果就是当前事务4可以看到的是快照trx_id=1的信息,即name=‘A’

由于是可重复读,所以事务4中第二个select语句对应的read view就是第一次执行select的时候生成的,所以看到的结果也和第一次一样。

参考:

https://zhuanlan.zhihu.com/p/117476959

https://mp.weixin.qq.com/s/T8ZfheTdJmrfsaTsUOYNrg

https://juejin.cn/post/7016165148020703246

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值