MySQL_事务

3 篇文章 0 订阅
1 篇文章 0 订阅

1、什么是事务?

事务由一个有限的数据库操作序列构成,是数据库管理系统(DBMS)执行过程中的一个逻辑单位,要么全部执行成功,要么全部执行失败,不会只执行其中的一部分;事务是为了保证数据的一致性、可靠性和完整性的重要机制,也是数据库系统中的核心概念之一

2、事务的四大特性

事务包含四大特性,即原子性(Atomicity)一致性(Consistency)隔离性(Isolation)**和**持久性(Durability)(ACID)。

  1. 原子性(Atomicity) 原子性是指对数据库的一系列操作,要么全部成功,要么全部失败,不可能出现部分成功的情况。一般是由后面要讲到的Undo log 回滚日志来保证的。

  2. 一致性(Consistency) 一致性是指数据库的完整性约束没有被破坏,在事务执行前后都是合法的数据状态。这里的一致可以表示数据库自身的约束没有被破坏,比如某些字段的唯一性约束、字段长度约束等等;还可以表示各种实际场景下的业务约束,比如上面转账操作,一个账户减少的金额和另一个账户增加的金额一定是一样的。一致性则是通过原子性+隔离性+持久性来保证的。

  3. 隔离性(Isolation) 隔离性指的是多个事务彼此之间是完全隔离、互不干扰的。隔离性的最终目的也是为了保证一致性。由MVCC多版本并发控制或者是锁机制来保持的。

  4. 持久性(Durability) 持久性是指只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态。由Redo log 重做日志来保证。

3、事务的状态

根据事务所处的不同阶段,事务大致可以分为以下5个状态:

  1. 活动的(active) :当事务对应的数据库操作正在执行过程中,则该事务处于活动状态。
  2. 部分提交的(partially committed) :当事务中的最后一个操作执行完成,但还未将变更刷新到磁盘时,则该事务处于部分提交状态。
  3. 失败的(failed) :当事务处于活动或者部分提交状态时,由于某些错误导致事务无法继续执行,则事务处于失败状态。
  4. 中止的(aborted): 当事务处于失败状态,且回滚操作执行完毕,数据恢复到事务执行之前的状态时,则该事务处于中止状态。
  5. 提交的(committed) :当事务处于部分提交状态,并且将修改过的数据都同步到磁盘之后,此时该事务处于提交状态。

4、事务隔离级别

为什么要隔离?并发事务会引发什么问题?

  1. 脏读:一个事务读到另一个事务“未提交事务修改的数据”
  2. 不可重复读:在一个事务内多次读同一个数据,出现前后两次读到的数据不一样的情况
  3. 幻读:在一个事务内多次查询某个符合条件的“记录数量”,出现前后两次查询到的记录数量不一样的情况

上述三种问题都是数据库中的读一致性问题。

这里还需要区别一下:不可重复读和幻读的区别在于不可重复读是读到的是其他事务修改或者删除的数据,而幻读读到的是其它事务新插入的数据

而接下来我们要说的事务的隔离级别就是用来规避上述三种问题的,现隔离性最简单的方式就是不允许事务并发,每个事务都排队执行,但是这种方式性能实在太差了。为了兼顾事务的隔离性和性能,事务支持不同的隔离级别。

  • READ UNCOMMITTED:未提交读。脏读、不可重复读、幻读都可能发生,最低的隔离级别。
  • READ COMMITTED:已提交读。不可重复读、幻读可能发生。
  • REPEATABLE READ:可重复读。幻读仍然可能发生。InnoDB存储引擎默认的就是“可重复读”隔离级别,实际上已经在很大程度上避免了幻读。解决的方案有两种:
    • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
    • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
  • SERIALIZABLE:串行化。可以保证上述三种现象都不会发生,但是会非常影响性能。

隔离级别越高,事务的并发度就越低

这四种事务隔离级别具体是怎么来实现的呢?

  • 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
  • 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问,不存在读不一致的问题;
  • 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个Read View,而「可重复读」隔离级别是「启动事务时」生成一个Read View,然后整个事务期间都在用这个Read ViewReadView中主要包含以下4个内容:
    • m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务
    • min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
    • max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
    • creator_trx_id :指的是创建该 Read View 的事务的事务 id

5、MVCC

MVCC(Multi Version Concurrency Control),中文名是多版本并发控制,简单来说就是通过维护数据历史版本,从而解决并发访问情况下的读一致性问题。

版本链

InnoDB中,每行记录实际上都包含了两个隐藏字段:事务id(trx_id)和回滚指针(roll_pointer)。

  1. trx_id:事务id。每次修改某行记录时,都会把该事务的事务id赋值给trx_id隐藏列。
  2. roll_pointer:回滚指针。每次修改某行记录时,都会把undo日志地址赋值给roll_pointer隐藏列。

由于每次变动都会先把undo日志记录下来,并用roll_pointer指向undo日志地址。因此可以认为,对该条记录的修改日志串联起来就形成了一个版本链,版本链的头节点就是当前记录最新的值

6、事务的传播行为

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法时事务如何传播。

Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为。

事务传播行为是 Spring 框架独有的事务增强特性,他不属于的事务实际提供方数据库行为,而是 Spring 为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。

  1. REQUIRED:表示当前方法必须在一个事务中运行,如果当前存在事务,则加入该事务,否则新建一个事务。
  2. SUPPORTS:表示当前方法支持在一个事务中运行,如果当前存在事务,则加入该事务,否则以非事务方式运行。
  3. MANDATORY:表示当前方法必须在一个事务中运行,如果当前不存在事务,则抛出异常。
  4. REQUIRES_NEW:表示当前方法必须在一个新的事务中运行,如果当前存在事务,则挂起该事务,并新建一个事务。
  5. NOT_SUPPORTED:表示当前方法不应该在一个事务中运行,如果当前存在事务,则挂起该事务,并以非事务方式运行。
  6. NEVER:表示当前方法不应该在一个事务中运行,如果当前存在事务,则抛出异常。
  7. NESTED:表示当前方法必须在一个嵌套事务中运行,如果当前存在事务,则在该事务的上下文中嵌套一个新的事务,如果当前不存在事务,则新建一个事务。

其中,REQUIRED 是默认的传播行为,表示当前方法必须在一个事务中运行,如果当前存在事务,则加入该事务,否则新建一个事务。而其他的传播行为则是根据不同的业务需求进行选择和设置的。

在实际应用中,需要根据业务需求、数据一致性和性能要求等因素来选择合适的事务传播行为。常见的场景包括:

  1. 多个事务方法之间需要共享一个事务,使用 REQUIRED 或 NESTED。
  2. 多个事务方法之间需要独立的事务,使用 REQUIRES_NEW。
  3. 在业务方法中调用其他业务方法,需要控制事务的传播行为,使用不同的传播行为来实现。

REQUIRED,REQUIRES_NEW,NESTED 异同?

  • NESTED 和 REQUIRED 修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。
    • 但是,REQUIRED 是加入外围方法事务,所以和外围事务同属于一个事务,一旦 REQUIRED 事务抛出异常被回滚,外围方法事务也将被回滚。
    • NESTED 是外围方法的子事务,有单独的保存点,所以 NESTED 方法抛出异常被回滚,不会影响到外围方法的事务。
  • NESTED 和 REQUIRES_NEW 都可以做到内部方法事务回滚而不影响外围方法事务。
    • 但是,因为 NESTED 是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。
    • REQUIRES_NEW 是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

Spring事务传播行为,👇👇👇有更加详细的哦~

https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486668&idx=2&sn=0381e8c836442f46bdc5367170234abb&chksm=cea24307f9d5ca11c96943b3ccfa1fc70dc97dd87d9c540388581f8fe6d805ff548dff5f6b5b&token=1776990505&lang=zh_CN#rd

7、Spring事务管理

  1. 声明式事务
  2. 编程式事务

8、总结

事务是在 MySQL 引擎层实现的,我们常见的 InnoDB 引擎是支持事务的,事务的四大特性是原子性、一致性、隔离性、持久性,我们这次主要讲的是隔离性。

当多个事务并发执行的时候,会引发脏读、不可重复读、幻读这些问题,那为了避免这些问题,SQL 提出了四种隔离级别,分别是读未提交、读已提交、可重复读、串行化,从左往右隔离级别顺序递增,隔离级别越高,意味着性能越差,InnoDB 引擎的默认隔离级别是可重复读。

要解决脏读现象,就要将隔离级别升级到读已提交以上的隔离级别,要解决不可重复读现象,就要将隔离级别升级到可重复读以上的隔离级别。

而对于幻读现象,不建议将隔离级别升级为串行化,因为这会导致数据库并发时性能很差。MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象。解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同:

  • 「读提交」隔离级别是在每个 select 都会生成一个新的 Read View,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
  • 「可重复读」隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。

这两个隔离级别实现是通过「事务的 Read View 里的字段」和「记录中的两个隐藏列」的比对,来控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。

在可重复读隔离级别中,普通的 select 语句就是基于 MVCC 实现的快照读,也就是不会加锁的。而 select … for update 语句就不是快照读了,而是当前读了,也就是每次读都是拿到最新版本的数据,但是它会对读到的记录加上 next-key lock 锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值