MYSQL-3.事务

事物

基本概念

ACID

  1. Atomic(原子性):事物操作的原子性;指一个事物中的操作要么全部成功,要么全部失败;

    1. redoLog日志:记录事物操作,在事物异常中断时,如Mysql服务器宕机等情况下,保证对未完成事物的恢复;
    2. undoLog日志:事物失败时,对已经成功的SQL进行回滚操作,保证失败情况下,事务的原子性;
  2. Consistency(一致性):数据状态的一致性;指事物执行前后,数据应该符合数据库的特定规则和约束;

    比如:假设有个事物A(客户下单),它由「扣库存、新增订单」两个操作组成,当客户下单购买了100个商品,库存需要减少100、生成一条100个商品的订单数据,也就是【总库存 = 库存的商品数量+ 订单的商品数据】的等式需恒成立;若一个成功一个失败则数据库整体数据出现不一致;

    1. undoLog日志:事物失败时,对已经成功的SQL进行回滚操作,保证失败情况下,事务的一致性;
    2. redoLog日志:记录事物操作,在事物异常中断时,如Mysql服务器宕机等情况下,保证对未完成事物的恢复;
    3. 锁机制:保证事物对数据的独占性;
    4. 完整性约束:确保插入、更新或删除的数据满足特定条件;
      • 实体完整性:保证关系中的每个元组都是可识别的和惟一的;例如:主键不能为空、允许重复等
      • 引用完整性:保证实体与实体之间的引用关系必须满足一定约束;例如:外键引用需与对应的主键值一致,或者为空;
      • 域完整性: 数据库中的数据必须满足定义的域约束条件;例如:数值必须在允许的范围内、日期格式必须正确、唯一性约束、非空约束等。
  3. Isolation(隔离性):事物之间的隔离性,指事物的执行相互之间互不影响;

    1. 锁机制:保证写操作时,事物对数据的独占性;
    2. MVCC机制:通过保存数据的多个版本,以及使用版本控制来实现并发事务之间的隔禽;
  4. Durability(持久性):数据的持久性;指一个事务执行完成后,它对数据库的修改应该是永久性的;即使发生系统崩溃或机器宕机,也不会导致数据的丢失。

    1. redoLog日志:记录事物操作,在事物异常中断时,如Mysql服务器宕机等情况下,保证对未完成事物的恢复;

隔离机制

  1. 丢失更新、脏读、幻读、不可重复读问题

    1. 脏写(丢失更新):多个事物操作同一条数据,导致后提交事物的修改结果覆盖了先提交修改结果;

    2. 脏读读未提及;一个事物读到了另一个事物还未提交的数据;

    3. 不可重复读读已提交;同一个事物中,对某一个数据多次读取却得到了不同的结果;

    4. 幻读:同一个事物中,多次查询返回的结果集不同;

      例:假设我们需要将数据库中的数据全部设置为软删(del_flag = 1),此时事物A开始更改数据库中的del_flag字段,当负责执行事务A刚更改好最后一条数据时,此时事务B来了,正好向表中插入了一条「del_flag = 0」的数据并提交了,然后事物A再次去查询数据时,发现还存在一条「del_flag = 0」的数据,似乎产生幻觉一样;
      原因:另外一个事务在第一个事务要处理的目标数据范围之内新增了数据,并且先于第一个事务提交;

隔离级别

  1. Read Uncommitted(读未提交):解决脏写,可能产生脏读、不可重复读、幻读;

    • 实现写操作加排他锁,读操作不加锁;当事物对一条数据进行写操作时,需先获取到锁资源,才允许对数据进行写操作,读操作不加锁;
    • 例子:当存在两个事物T1、T2,T1在修改ID=1的数据时添加上排他锁,若这时T2也尝试修改ID=1的数据也需要获取排他锁,两个事物的写操作就会互斥,T2就需要阻塞等待;但读操作不会加锁,因此T2在尝试读取ID=1的数据时,自然可以读到;
  2. Read Committed(读以提交):解决脏写、脏读,可能产生不可重复读、幻读;

    • 实现写操作加排他锁,读操作采用MVCC多版本并发控制技术限制,在每一次查询数据时,生成一个ReadView读视图;

      也可以通过读操作加共享锁实现,但是会导致并发事物串行化执行,影响效率;

  3. Repeatable Read(可重复读、默认):解决了脏写、脏读、不可重复读,可能产生幻读;

    • 实现对于写操作同样通过互斥锁限制;读操作同样通过MVCC限制,区别于读已提交级别,一个事务中只有首次查询时会生成ReadView快照;

      也可以在查询时对目标数据加上行锁,即读操作执行时,不允许其他事物改动数据;

    • 幻读问题:Mysql通过采用MVCC机制已经极大程度的在RR级别下规避了幻读问题,但在极端情况下(离谱操作)还是存在幻读可能;

  4. Serializable(串行化):解决了脏写、脏读、不可重复读、幻读;

    • 实现:所有写操作加临键锁,所有读操作加共享锁;这种情况下只有读-读场景可以并发执行。

Mysql的隔离机制

  1. 查询命令SELECT @@tx_isolation;show variables like '%tx_isolation%';

  2. 设置命令

    -- 设置隔离级别为RU级别(当前连接生效)
    set transaction isolation level read uncommitted;
    -- 设置隔离级别为RC级别(全局生效)
    set global transaction isolation level read committed;
    -- 设置隔离级别为RR级别(当前连接生效)
    set tx_isolation = 'repeatable-read';
    -- 设置隔离级别为最高的serializable级别(全局生效)
    set global tx_isolation = 'serializable';
    

    设置全局生效需要加上global关键字

Mysql的事物实现原理和执行流程:

  1. 事务是基于数据库连接的,而每个数据库连接在MySQL中,又会用一条工作线程来维护,也意味着一个事务的执行,本质上就是一条工作线程在执行;
    1. 基于锁和MVCC多版本并发控制机制实现MVCC又主要通过隐藏字段、undo-log日志、读诗图实现;
  2. InnoDB事物执行的流程:
    1. start trasaction;关闭事物自动提交机制;
    2. SQL解析过程,经过SQL接口、解析器、执行器得到执行计划;
    3. 记录一条状态为prepareredo-loge日志;
    4. 生成对应undo-log日志并记录;
    5. 执行SQL,在缓冲区BufferPool中更改对应数据, 如果有多条sql则逐条依次做上述相同处理;
    6. 直到碰到了rollback、commit命令时,再对前面的所有写SQL做相应处理;
      • commit事物提交:将redo-log日志改为commit状态;
      • rollback事物回滚:根据前面已经执行了的写SQL的更改行数据的隐藏列roll_ptr回滚指针,定位到undo-log日志中的undo记录,然后从xx.ibdata共享表数据文件中拷贝到xx.ibd表数据文件,覆盖掉原本改动过的数据(存在缓存优化,日志是写入undo_log_buffer缓冲区,数据也是修改BufferPool缓冲区中的数据);若是插入操作,则新插入的行数据隐藏列roll_ptr = null,因此之间用null覆盖插入的新纪录即可;
    7. Mysql进行刷盘处理,将缓冲区中的数据落入磁盘中;

Mysql中事物分组

Mysql在提交事物时内部会调用ordered_commit函数来处理相关工作,具体流程为:
在这里插入图片描述
每一个事物提交时都会调用ordered_commit函数,首先会将事务加入等待事务组,接着会经过三个核心步骤:FLUSH、SYNC、COMMIT,对应的也会有三个队列,它们三者的工作原理都大致相同:

  1. 如果某个事务进入FLUSH队列时,该队列还是空的,则这个事务会担任“队长”的角色。
  2. 当后续其他事务进入队列时,发现队列不为空,则会将提交工作委托给队长来完成。
  3. 如上图中的「事务1」则是队长,后续的都是队员,但队长不会无限制等待队员到来:从队长加入的时间点开始,当超出 binlog_group_commit_sync_delay规定的时间后,就会进行一次组提交;

同一时刻只允许一个事物组进行工作;
当事物组提交后会将当前事务组的内容记录到Bin-log日志中,同时会将这组事务记录成一个GTID(全局事务标识符,主从同步时使用),不同事务之间通过,逗号分隔;

Spring中事务的使用

编程式事务

TransactionTemplate
  1. 使用:

    // SpringBoot 注入对象
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    transactionTemplate.execute(transactionStatus -> {
        // 业务代码 一个表删除不成功则回滚
        try {
            if (mapper1.delete(id) < 1) {
                transactionStatus.setRollbackOnly();
                return Boolean.FALSE;
            }
            if (mapper2.delete(id) < 1) {
                transactionStatus.setRollbackOnly();
                return Boolean.FALSE;
            }
            if (mapper3.delete(id) < 1) {
                transactionStatus.setRollbackOnly();
                return Boolean.FALSE;
            }
            return Boolean.TRUE;
        } catch (Exception e) {
            transactionStatus.setRollbackOnly();
            return Boolean.FALSE;
        }
    });
    
DataSourceTransactionManager与TransactionDefinition
  1. 使用:

    //DataSourceTransactionManager: 数据源事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    //TransactionDefinition:事务定义    
    @Autowired
    private TransactionDefinition transactionDefinition;
    
    
    TransactionStatus transactionStatus = null;
    int result = 0;
    try{
       //开启事务
        transactionStatus = transactionManager.getTransaction(transactionDefinition);
       //业务操作,删除事务
       result = userService.delete(id);
       //提交事务
       transactionManager.commit(transactionStatus);
    }catch (Exception e){
       //回滚事务
       if(transactionStatus != null){
            transactionManager.rollback(transactionStatus);
        }
    }
    
注意点:
  1. 通常只在单数据库操作多个表时使用;
  2. 事务只能保证数据库的一致,若存在redisrpc调用等三方操作,则不能保证数据库和三方的一致性;
  3. 事务未提交前,程序抛出异常(未处理)时会自动回滚事务;
  4. 捕获异常需要手动回滚事务

声明式事务

@Transactional
  1. 作用范围:可以加在方法上以及类上

    1. 当使用 @Transactional 注解修饰方法时,它只对public的方法生效。
    2. 当使用 @Transactional 注解修饰类时,表示对该类中所有的public方法生效。
  2. 原理:当一个类或者方法带有 @Transactional 注解时,Spring 将创建一个代理对象来管理事务。Spring 使用 AOP将事务管理逻辑织入到带有 @Transactional 注解的方法周围。具体spring实现方法:TransactionInterceptor#invokeWithinTransaction

  3. 失效场景:

    1. 访问权限问题 (只有public方法会生效);原因:在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务;

    2. 方法使用final、static修饰原因:使用final、static修饰的方法无法被spring AOP生成的代理类重写;

    3. 同一个类中的方法直接内部调用原因:内部掉用使用的是this对象的方法;

      解决办法:新加一个Service方法;在该Service类中注入自己;通过AopContent获取到代理类掉用,((ServiceA)AopContext.currentProxy()).doSave(user);

    4. 多线程调用:在一个事务方法中,新开线程调用另一个事务方法;原因:事务是基于数据库连接的,在不同的线程,拿到的数据库连接是不一样的,所以是不同的事务;

    5. 类未被spring管理;

    6. 错误的传播特性;

    7. 异常自行捕获了却没有重新抛出;

传播特性

支持当前事务:

REQUIRED(默认):如果当前存在事务,则加入到当前事务中,如果没有事务,则创建一个新的事务。

SUPPORTS:如果当前存在事务,则加入到当前事务中,如果没有事务,则以非事务的方式执行。

MANDATORY:必须在一个已存在的事务中执行,否则抛出异常。

不支持当前事务:

REQUIRES_NEW:每次都会创建一个新的事务,如果当前存在事务,则将当前事务挂起。

NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,则将当前事务挂起。

NEVER:必须以非事务方式执行,如果当前存在事务,则抛出异常。

嵌套事务:

NESTED:如果当前存在事务,则在嵌套事务内执行,如果没有事务,则创建一个新的事务。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值