一文搞定MySQL事务

事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
在这里插入图片描述

事务简介

  • 数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令
  • 事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么同时成功,要么同时失败
  • 事务是一个不可分割的工作逻辑单元。

我们可以举一个例子更好的理解事务:
如下图有一张表

在这里插入图片描述

张三和李四账户中各有100块钱,现李四需要转换500块钱给张三,具体的转账操作为

  • 第一步:查询李四账户余额
  • 第二步:从李四账户金额 -500
  • 第三步:给张三账户金额 +500

现在假设在转账过程中第二步完成后出现了异常第三步没有执行,就会造成李四账户金额少了500,而张三金额并没有多500;这样的系统是有问题的。如果解决呢?使用事务可以解决上述问题

在这里插入图片描述

从上图可以看到在转账前开启事务,如果出现了异常回滚事务,三步正常执行就提交事务,这样就可以完美解决问题。

开启事务其实就是打一个标记,告诉数据库后边的执行都是一些临时的更改。只有当遇到提交事务的时候,才会成为一个持久化更改。而回滚事务就是撤销那些临时的操作回到开启事务之前的状态。

相应语法:
在这里插入图片描述

事务操作

我们将上述问题用事务去解决

  • 不加事务演示问题

    -- 转账操作
    -- 1. 查询李四账户金额是否大于500
    
    -- 2. 李四账户 -500
    UPDATE account set money = money - 500 where name = '李四';
    
    出现异常了...  -- 此处不是注释,在整体执行时会出问题,后面的sql则不执行
    -- 3. 张三账户 +500
    UPDATE account set money = money + 500 where name = '张三';
    

    整体执行结果肯定会出问题,我们查询账户表中数据,发现李四账户少了500。

  • 添加事务sql如下:

    -- 开启事务
    BEGIN;
    -- 转账操作
    -- 1. 查询李四账户金额是否大于500
    
    -- 2. 李四账户 -500
    UPDATE account set money = money - 500 where name = '李四';
    
    出现异常了...  -- 此处不是注释,在整体执行时会出问题,后面的sql则不执行
    -- 3. 张三账户 +500
    UPDATE account set money = money + 500 where name = '张三';
    
    -- 提交事务
    COMMIT;
    
    -- 回滚事务
    ROLLBACK;
    

    上面sql中的执行成功进选择执行提交事务,而出现问题则执行回滚事务的语句。以后我们肯定不可能这样操作,而是在java中进行操作,在java中可以抓取异常,没出现异常提交事务,出现异常回滚事务。

事务四大特征(ACID)

  • 原子性(Atomicity): 事务是不可分割的最小操作单位,要么同时成功,要么同时失败

  • 一致性(Consistency) : 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;

  • 隔离性(Isolation) :并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。

  • 持久性(Durability) :事务一旦提交或回滚,它对数据库中的数据的改变就是永久的

只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
在这里插入图片描述
理论依据:
《Designing Data-Intensive Application(数据密集型应用系统设计)》 的作者在他的这本书中如是说:
Atomicity, isolation, and durability are properties of the database, whereas consis‐ tency (in the ACID sense) is a property of the application. The application may rely on the database’s atomicity and isolation properties in order to achieve consistency, but it’s not up to the database alone.
翻译过来的意思是:原子性,隔离性和持久性是数据库的属性,而一致性(在 ACID 意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。因此,字母 C 不属于 ACID

隔离性的说明:
隔离性越强,操作越不可见,性能越低
隔离性约弱,操作越可见,性能越高
一般我们不会去改变事务的隔离性。
默认:当我们在MySQL中回滚或者提交之后,别人才能看到我们对应的修改

说明:
默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐式的提交事务。
可以通过下面语句查询默认提交方式:

SELECT @@autocommit;

查询到的结果是1 则表示自动提交,结果是0表示手动提交。当然也可以通过下面语句修改提交方式

set @@autocommit = 0;

在这里插入图片描述

并发事务

问题描述
脏读一个事务读到另一个事务还没提交的数据
不可重复读一个事务先后读取同一条记录,但两次读取的数据不同
幻读一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在

我们来详细解释一下这几个概念:

脏读
在这里插入图片描述

一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据,这也就是脏读的由来。

例如:事务 1 读取某表中的数据 A=20,事务 1 修改 A=A-1,事务 2 读取到 A = 19,事务 1 回滚导致对 A 的修改并为提交到数据库, A 的值还是 20。

不可重复读
在这里插入图片描述
事务A先去执行一个select语句,在执行第二步操作的时候,B事务并发的进行了一个update操作,并且成功提交。而这时当事务A再次去查询id为1的数据的时候会发现数据不一样。这种现象称之为不可重复读。

幻读
在这里插入图片描述

事务A先开始查询id为1的数据,发现没有;这时候有一个并发事务B往数据库里面添加了一条id为1的数据,并且成功提交,也就是说此时数据库中已经有一条id为1的数据了,当事务A再去执行insert的时候因为主键冲突肯定不会成功的。此时事务A再去查询数据库仍然查不到id为1的数据(假设已经解决了重复读的问题),但是前面的插入操作确实失败了啊,好像出现了幻觉一般,这种情况就叫做幻读。

幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

那么不可重复读和幻读有什么区别?

  • 不可重复读的重点是内容修改或者记录减少,比如多次读取一条记录发现其中某些记录的值被修改;
  • 幻读的重点在于记录新增,比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。

幻读其实可以看作是不可重复读的一种特殊情况,单独区分幻读的原因主要是解决幻读和不可重复读的方案不一样

举个例子:

  • 执行 delete 和 update 操作的时候,可以直接对记录加锁,保证事务安全。
  • 而执行 insert 操作的时候,由于记录锁(Record Lock)只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁(Gap Lock)。也就是说执行 insert 操作的时候需要依赖临键锁 Next-Key Lock(Record Lock+Gap Lock) 进行加锁来保证不出现幻读。

事务隔离级别

事务的隔离级别就是用来解决并发事务中出现的问题

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交) : 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • READ-COMMITTED(读取已提交) : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • REPEATABLE-READ(可重复读) : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • SERIALIZABLE(可串行化) : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读
隔离级别脏读不可重复读幻读
Read uncommitted
Read committed×
Repeatable Read(默认)××
Serializable×××
  • Serializable 性能最低;Read uncommitted 性能最高,数据安全性最差

MySQL的默认隔离级别是Repeatable Read,而Oracle的是Read committed

MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读

从上面对 SQL 标准定义了四个隔离级别的介绍可以看出,标准的 SQL 隔离级别定义里,REPEATABLE-READ(可重复读)是不可以防止幻读的。但是InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况:

  • 快照读 :由 MVCC 机制来保证不出现幻读。
  • 当前读 : 使用 Next-Key Lock (临键锁)进行加锁来保证不出现幻读,Next-Key Lock 是记录锁(Record Lock)和间隙锁(Gap Lock)的结合,记录锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED ,但是你要知道的是 InnoDB 存储引擎默认使用 REPEATABLE-READ 并不会有任何性能损失。

看到这可能会有一个疑问:什么是快照读?什么是当前读?

在数据库中,事务可以以快照读(Snapshot Read)当前读(Current Read)的方式读取数据。这两种读方式对应的隔离级别不同,有各自的优点和缺点。

快照读:

  • 快照读对应的隔离级别是可重复读(REPEATABLE READ)。
  • 事务在开始时保存数据的一个快照(版本),并在事务期间读取这个快照中的数据。
  • 这意味着快照读可以隔离其他事务的修改,一个事务在读取这个snapshot中的数据时,不会看到其他未提交事务的修改。
  • 快照读可以避免读取过程中数据被其他事务修改的问题,保证事务读到一致的数据。

当前读

  • 当前读对应的隔离级别是读未提交(READ UNCOMMITTED)。
  • 事务直接在数据上读取最新的数据版本(包括未提交的修改)。
  • 这意味着当前读无法隔离其他事务的修改,可能会读取到已提交或未提交的最新数据版本。
  • 当前读可以读取最新的修改版本,但可能会导致读取到未提交的数据或遇到脏读问题。

总结如下:

快照读当前读
隔离级别可重复读读未提交
是否可以隔离其他事务未提交修改
是否可以避免脏读
是否可以读取最新数据

应用场景:

  • 快照读适用于要求一致读取且不需要最新数据的场景,如报表统计。
  • 当前读适用于要求读取实时数据且一致性要求不高的场景,如监控应用。

查看事务隔离级别:

SELECT @@TRANSACTION_ISOLATION

设置事务隔离级别:

SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };

SESSION 是会话级别,表示只针对当前会话有效,GLOBAL 表示对所有会话有效

注意:事务隔离级别越高,数据越安全,但是性能越低。

我们对这四种事务隔离级别进行一个演示:

脏读(读未提交)

在这里插入图片描述

避免脏读(读已提交)

在这里插入图片描述

不可重复读

还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。

在这里插入图片描述

可重复读

在这里插入图片描述
幻读

在这里插入图片描述

SQL 脚本 1 在第一次查询工资为 500 的记录时只有一条,SQL 脚本 2 插入了一条工资为 500 的记录,提交之后;SQL 脚本 1 在同一个事务中再次使用当前读查询发现出现了两条工资为 500 的记录这种就是幻读。

解决幻读的方法解决幻读的方式有很多,但是它们的核心思想就是一个事务在操作某张表数据的时候,另外一个事务不允许新增或者删除这张表中的数据了。解决幻读的方式主要有以下几种:

  • 将事务隔离级别调整为 SERIALIZABLE 。
  • 在可重复读的事务级别下,给事务操作的这张表添加表锁。
  • 在可重复读的事务级别下,给事务操作的这张表添加 Next-key Lock(Record Lock+Gap Lock)(这也就是InnoDB的解决方式)
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十八岁讨厌编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值