轻松上手MYSQL:MYSQL事务隔离级别的奇幻之旅

​🌈 个人主页:danci_

🔥 系列专栏:《设计模式》《MYSQL》

💪🏻 制定明确可量化的目标,坚持默默的做事。


✨欢迎加入探索MYSQL索引数据结构之旅✨

    👋 大家好!文本学习研究事务隔离级别。👋 无论您是刚接触MySQL的初学者,还是希望深入优化性能的资深开发者,这篇文章都将为您揭开MySQL事务隔离级别的神秘面纱,让您掌握其中的奥秘,进而提升数据库操作的效率和精度。快来一起探索吧!


1. 什么是事务?


目录

一、事务隔离级别

1.1 事务并发执行的一致性问题

1.1.1 脏写

1.1.2 脏读

1.1.3 不可重复读:

1.1.4 幻读

1.2 SQL标准中的4种隔离级别

1.3 MYSQL查询事务隔离级别

1.4 MYSQL设置隔离级别

二、MYSQL4种隔离级别实战

2.1 READ UNCOMMITTED

2.2 READ COMMITTED

2.3 REPEATABLE READ(MYSQL默认的隔离级别)

2.4 SERIALIZABLE

三、总结


一、事务隔离级别

MYSQL是客户端 / 服务器的软件,是多对一的关系,即同时可以有多个客户端同时连一个接服务,每个客户端连接服务器后,就生成一个会话。每个会话都可以向服务器发送请求语句,一个请求语句可能是一个事务,也可能是一个事务的某一部分语句。而服务器可以同时处理来自多个客户端的请求语句。

事务简述:一个事务就对应着现实世界的一次状态转换。

下面举个例子,比如用户A向用户B转账100元,账户状态变更简化为以下几个步骤:

  1. 取出A账户余额a1。即一次select A
    1. a1 < 100,退出转账
    2. a1 >= 100,则继续
  2. a1 -= 100
  3. 更新入库。即一次update A
  4. 取出B账户余额b1。即一次select B
  5. b1 += 100
  6. 更新入库。即一次update B

在这个转账事务中,一定要保证 A减100 和 B加100 都成功,换句话说就是必须保证参与转账的账户的总余额保持不变,这也就是这个转账事务的一致性要求。

如果事务是以单个的形式一个接一个地执行,那么在一个事务开始时,面对的就是上一个事务执行结束后留下的一致性状态,它执行之后又会产生下一个一致性状态。那么在多个事务的情况下,情况就变得比较复杂。假如事务是交替执行的,如下图

如图中的交替执行的两个事务,A转账了两次5最后余额为6 ,B转入两次余额为12。显然A账户余额是错误的,这就违背了“参与转账的账户的总余额保持不变”的一致性要求。

这时就需要某种手段来强制让这些事务按照顺序一个一个单独地执行,或者最终执行的效果和单独执行一样。换句话说就是让多个事务隔离的执行,也就是互不干涉。这就是事务的隔离性要求。

如何实现事务的隔离性?

  • 串行执行:在系统中同一时刻最多只允许一个事务运行
    • 能保证一致性
    • 导致许多事务等待时间,资源利用率低
  • 可串行化执行:事务可并发执行,但涉及到同一个数据时,就等待其它事务提交之后才能继续访问这个数据。

    可串行化执行对同一数据写操作就会出现等待,性能还是不高。在一些高并发场景中影响会非常明显。

    1.1 事务并发执行的一致性问题

    1.1.1 脏写

    事务T1和事务T2同时修改数据x,事务T1开启事务写x值且还没有提交事务,事务T2开始写x值,这就发生了脏写。如下图:

    脏写不只影响数据的一致性,还影响事务的原子性,如下图:(假设x初始值为 5)

    事务 T1 因某种原因要回滚,那么需要将它的数据回滚到事务开启时的状态,也就是将x的值变为初始值为 5。但是对于事务 T2 来说,它修改x为200并且已经提交了,如果 T1 回滚,则 T2 事务的修改有一部分数据回滚了,这就影响了事务的原子性。

    1.1.2 脏读

    事务T1读到未提交事务T2修改过的数据,这就发生了脏读。如下图:

    事务T1 读 x 时 T2 未提交,这时 T1 读到的是不一致的数据。

    1.1.3 不可重复读:

    事务 T1 修改了未提醒事务 T2 读取的数据,这就发生了不可重复读。如下图:

    事务 T1 和事务 T2 都开启事务,事务 T2 读 x 为 50,之后事务 T1 修改 x = 100 并且提交了事务,事务 T2 再次读 x,此时 x 的值为 100了,这就出现在同一个事务中读同一条数据的值不一致问题。这种在同一个事务中读同一条数据不一致问题不应该暴露给用户的。

    1.1.4 幻读

    事务 T1 根据某种搜索条件井底出记录,在该事务未提交时,另一事务写入了符合那些搜索条件的记录,这就产生了幻读。如下图:

    事务 T1 查询 col = 1 的数据有记录1 和 记录2,此时事务 T2 修改记录3 的col = 1并且提交事务,这时,事务 T1 再查询 col = 1 的数据有 记录1、记录2 和 记录3,与之前的查询结果不一致,不符合一致性要求。

    影响一致性严重性大到小排序: 脏写 > 脏读 > 不可重复读 > 幻读

    1.2 SQL标准中的4种隔离级别

    • READ UNCOMMITTED: 读未提交

    • READ COMMITTED: 读已提交

    • REPEATABLE READ: 可重复读

    • SERIALIZABLE: 可串行化

      四种隔离级别在并发事务中可能发生的数据一致性问题如下:

      也就是说:

      • READ UNCOMMITTED隔离级别下,可能脏读、不可重复读和幻读
      • READ COMMITTED隔离级别下,可能发生不要重复读和幻读
      • REPEATABLE READ隔离级别下,可能发生幻读
      • SERLIALIZABLE隔离级别下,脏读、不可重复读和 幻读 均不可能发生

        注:脏写这个情况对一致性影响很严重,以上四种隔离级别都不准脏写发生。

        1.3 MYSQL查询事务隔离级别

        Mysql8以前:SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
        Mysql8开始:SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;

        1.4 MYSQL设置隔离级别

        MYSQL支持4种事务隔离级别,默认的隔离级别为REPEATABLE READ(可重复读)

        设置事务隔离级别

        SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level

        其中

        • level有:REPEATABLE READ、READ UNCOMMITTED、READ COMMIITTED 和 SERIALIZABLE
        • GLOBAL| SESSION:可设置可不设置
          • 不设置:只对执行SET后的下一个事务有效
            • 对下一个事务有效
            • 对下下个事务无效,事务隔离级别恢复到之前的隔离级别
            • 在已经开启的事件中执行会报错
          • GLOBAL:全局范围有效
            • 当前会话无效
            • 只对执行完该SET后的会话有效
          • SESSION:在当前会话有效
            • 多个事务执行,只对该语句后面执行的事务有效
            • 可在开启的事务中执行,不影响之前正在执行的事务
            • 对当前会话后续的事务无效

          查看事务隔离级别

          mysql> SHOW VARIABLES LIKE 'transaction_isolation';
          +-----------------------+-----------------+
          | Variable_name         | Value           |
          +-----------------------+-----------------+
          | transaction_isolation | REPEATABLE-READ |
          +-----------------------+-----------------+
          1 row in set (0.03 sec)
          mysql> select @@transaction_isolation;
          +-------------------------+
          | @@transaction_isolation |
          +-------------------------+
          | REPEATABLE-READ         |
          +-------------------------+
          1 row in set (0.00 sec)

          其实设置事务隔离级别相当于修改transaction_isolation的值。

          设置隔离级别:

          语法作用范围
          SET GLOBAL transaction_isolation = 隔离级别全局
          SET @@GLOBAL.var_name = 隔离级别全局
          SET var_name = 隔离级别会话
          SET SESSION var_name = 隔离级别会话
          SET @@SESSION.var_name = 隔离级别会话
          SET @@var_name = 隔离级别下一个事务

          二、MYSQL4种隔离级别实战

          创建个测试表

          CREATE TABLE `test`.`test`  (
            `id` int NULL DEFAULT NULL,
            `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
            PRIMARY KEY (`id`) USING BTREE
          ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
          INSERT INTO test values(1, 'zhangsan');

          2.1 READ UNCOMMITTED

          设置事务隔离级别、开启两个会话且开启事务,操作如下:(左边:事务a;右边:事务b)

                                                                          图 2.1-1

           打开两个会话,事务隔离级别设置为 READ UNCOMMITTED(读未提交)

          SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
          如图2.1-1中
          脏读: name初始化为zhangsan,事务a修改name为lisi 且事务未提交,
          事务b已读出name为lisi,而不是zhangsan(读未提交),出现了“脏读”现象。
          不可重复读:在事务b中两次读取的数据不一样,出现了“不可重复读”现象。

                                                                          图 2.1-2

          如图2.1-2中
          在事务a插入新数据之前读name='zhangsan'和事务b读name='zhangsan'都为一条数据,
          事务a插入一条数据且未提交,
          幻读: 此时事务b用相同的查询条件,读出来了两条两次读出来不一致的数据,出现了“幻读”现象。

          2.2 READ COMMITTED

          设置事务隔离级别、开启两个会话且开启事务,操作如下:(左边:事务a;右边:事务b)

                                                                          图2.2-1

          如图2.1-1中,同时启动两个事务ab

          name初始化为 'lisi',事务a修改name为 'zhangsan' 且事务未提交,事务b读name时,值仍然为 'lisi',而不是 'zhangsan'(读已提交)。当事务a提交了事务,事务读出的数据为 'zhangsan'。

          未出现脏读: 在事务a未提交的情况下,事务b读的数据没有变化,未出现“脏读”现象。

          不可重复读: 在同一个事务b中两次读取的数据不一致,出现了“不可重复读”现象。

          幻读:在同一个事务b中相同条件下读数据不一致,出现了“幻读”现象。

          2.3 REPEATABLE READ(MYSQL默认的隔离级别)

          图 2.3-1

          如图2.3-1,区域 1,同时启两个事务ab。

          可重复读:事务ab执行之后,事务a删除id为2的数据,提交前或提交后,事务b再次读取的数据者与之前读取的数据保持一致。

          如图2.3-1,区域 2,同时启两个事务ab。(事务a已提交,默认自动提交)

          幻读:事务a插入一条新数据id为3(自动提交事务)后,事务b读取数据,还是两条,保证了可重复读特性。但,事务b可以修改 id=3 的数据,然后再查询就能把 id为3 的数据查询出来了,出现了幻读的现象。

          2.4 SERIALIZABLE

                                                                          图 2.4-1

           如图2.4-1,设置隔离级别为SERIALIZABLE,同时开启两个事务,各步骤说明和结论:

          • 1: 读 id = 1 的数据
          • 2: 修改 id = 1 的数据报锁超时,结论:读某数据且加锁,本事务可读写,别的事务只可读不可写
          • 3: 更新 id = 5 成功,无其它事务读或写的数据,可写
          • 4: 读 id = 5 报锁超时,结论:写某数据,别的事务不允许读也不允许写
          • 5: 插入 id = 2 成功,无其它事务读或写的数据,可写
          • 6: 范围查询 id (5, 11)
          • 7: 插入 id = 7 成功,无其它事务读或写的数据,可写
          • 8: 插入 id为6 报锁超时
          • 9: 插入 id为6 成功
          • 10: 范围查询 id (5, 11)
          • 11: 插入 id为7 成功
          • 12: 插入 id为8 报锁牛奶时,结论:范围查询加锁,该范围数据本事务可读写,其它事务只可读
          • 13: 插入 id = 12 成功,无其它事务读或写的数据 id = 12,可写

             如此,本事务查询的数据,本事务可修改,其它事务只可读; 本事务修改的数据,其它事务不可查询和修改。完美避免脏读、不可重复读、幻读等读现象。当然因各种加锁,使得该事务隔离级别下效率低下,耗费数据库性能,不推荐使用。

            三、总结

                    本文我们深入探索了MySQL事务隔离级别的奥秘。从READ UNCOMMITTED到SERIALIZABLE,每个级别都有其独特的特点和适用场景。READ UNCOMMITTED虽快但风险高,SERIALIZABLE则提供最严格的数据一致性保证但性能消耗大。在实际应用中,根据业务需求和性能考量,合理选择事务隔离级别至关重要。通过本次奇幻之旅,我们掌握了事务隔离级别的核心概念及实战,为在MySQL数据库中实现高效、稳定的数据处理提供了有力支持。

                希望你喜欢这次的探索之旅!不要忘记 "点赞" 和 "关注" 哦,我们下次见!🎈