数据库事务基础

事务是什么?

数据库事务(Transaction)是将一系列数据库操作打包成一个不可分割的逻辑整体。事务执行时,其内部的数据库操作要么全部成功,要么全部失败!它是保证数据一致性的核心机制。

例子:转账

考虑 A 账户给 B 账户转100元的业务场景,一般需要如下步骤:

步骤1:从A账户扣减100元。 步骤2:给B账户增加100元。

假如没有事务的干预,我们独立执行这两个操作。

假设在步骤1执行成功后,正准备执行步骤2时因为某些原因导致步骤2执行失败,此时 A 的钱就凭空消失了,这是个不可容忍的重大事故!

事务的基础操作

事务的基础操作主要包含三个核心命令:

  • 开启事务BEGINSTART TRANSACTION
  • 提交事务COMMIT
  • 回滚事务ROLLBACK

MySQL 默认开启了**自动提交(autocommit)**模式。这意味着,每一条单独的 SQL 语句都会被当作一个独立的事务,执行完毕后自动提交,无需手动干预。

当我们需要将多条 SQL 语句放到一个事务中执行时,就必须显式开启事务

操作流程如下:

  1. 开启事务:执行 BEGIN;START TRANSACTION;
  2. 执行业务 SQL:依次执行需要捆绑的多条SQL语句
  3. 判断结果并结束事务
    • SQL 全部执行成功 → 执行 COMMIT;,将修改持久化到数据库
    • 任意一条 SQL 执行失败 → 执行 ROLLBACK;,撤销当前事务中所有已执行的操作

事务的四大特性(ACID)

  1. 原子性(Atomicity):事务中的所有操作像原子一样不可分割,它们要么全部成功,要么全部失败。
  2. 一致性(Consistency):事务执行前后,数据从一个合法性状态变换到另外一个合法性状态 。这里的合法性状态是根据具体的业务决定的。
  3. 隔离性(Isolation):多个事务并发执行时,它们之间互不干扰
  4. 持久性(Durability):一旦事务提交成功,那么它对数据库的改变就是永久性的。

并发事务存在的问题

在 ACID 四大特性中,最需要进行权衡取舍的,正是隔离性。

我们不妨设想一种最完美的隔离性保证——不允许事务并发执行,让它们全部串行执行

然而,串行执行意味着并发性能极差,这在如今需要高并发的互联网应用场景下是不可容忍的!

为了提高并发性能,DBMS 提供了不同等级的隔离级别。而隔离级别越低,数据库对并发操作的“监控”就越松,由此可能引发以下三类典型的并发问题:

  • 脏读
  • 不可重复读
  • 幻读

脏读

脏读是指一个事务读取到另一个事务未提交的数据

请看如下的场景图:

image.png

  1. 事务B将该行的数据改为“张老三”
  2. 事务A读到了“张老三”
  3. 事务B回滚,数据恢复为“张三”

此时事务A原先读到的 “张老三” 就成了脏数据!这就是脏读。

不可重复读

不可重复读是指一个事务对同一条数据进行两次查询,得到的结果却不一致

请看如下的场景图:

image.png

  1. 事务A先查询该行的数据,此时的结果为“张三”
  2. 事务A去处理其他数据
  3. 事务B更新这条数据的值为 “张老三” 并提交
  4. 事务A再次查询该行的数据,此时的结果为“张老三”

看到这里,很多人其实会产生这样的疑问:“这有什么问题?事务B改完并提交了,事务A查到最新的,不是很正常吗?

这个疑问非常合理,但是我们从事务隔离性的角度去重新审视,这个疑问便不攻自破。

在事务A看来,它的内部从未对这行数据有过修改操作,因此按照事务之间理应互不干扰的理想状态,它有充分的理由相信它前后两次读取的同一行数据必须完全一致,这样才合理!

然而结果却不一致,这说明事务B的临门一脚对事务A造成了影响,因此隔离性遭到了破坏。

幻读

幻读是指一个事务使用相同的条件对数据集合进行两次查询,后一次查询返回的记录条数与之前不一致

请看如下的场景图:

image.png

  1. 事务A查询整张 stu 表,此时只有一条记录 “张三”
  2. 事务B往 stu 表中插入一条数据 “李四” 并提交
  3. 事务A再次查询整张 stu 表,此时发现多出来一条 “李四”

看到这里,同样的疑问可能又会浮现:“这有什么问题?事务B插入了新数据并提交了,事务A查到新增的,不是很正常吗?

这个疑问和上一节的“不可重复读”如出一辙。如果我们再次从事务 A 的视角来看

在事务A看来,它的内部从未对这张表做过任何插入或删除操作,因此按照事务理应互不干扰的理想状态,它前后两次查询到的记录条数应该完全一致,这样才合理!

然而第二次却凭空多出了一条“幽灵”记录(仿佛出现了幻觉),这说明事务B的插入操作对事务A造成了影响,事务的隔离性遭到破坏。

讲到这,其实很多人可能会认为不可重复读和幻读很像啊,其实不然。

二者的本质区别在于:

  • 不可重复读:同一条记录的字段值变了(由其他事务的 UPDATE 操作引发);
  • 幻读:满足条件的记录条数变了(由其他事务的 INSERT 或 DELETE 操作引发)。

事务隔离级别

SQL标准定义了4种事务的隔离级别,它们的级别从低到高(隔离性由弱到强,并发性能由高到低)依次为:

  • 读未提交(Read Uncommitted)
  • 读已提交(Read Committed)
  • 可重复读(Repeatable Read)
  • 串行化(Serializable)

读未提交

读未提交,顾名思义,就是一个事务能够读到另一个事务未提交的数据。

显然,这种隔离级别的隔离性几乎为 0,但并发性能是最好的。代价是它无法解决任何一类并发事务问题

读已提交

读已提交,顾名思义,就是一个事务只能读到另一个事务已提交的数据。

这种隔离级别能够避免脏读,但不可重复读和幻读仍然可能发生。

可重复读

可重复读,顾名思义,就是保证在同一个事务内,多次读取同一条记录的结果始终一致

这种隔离级别能够避免脏读和不可重复读,但仍然存在幻读的问题。

值得一提的是,MySQL 的 InnoDB 存储引擎默认采用此隔离级别,并且通过 MVCC机制,解决了大部分场景下的幻读问题。

串行化

串行化是最高的隔离级别。它通过强制事务串行执行,彻底杜绝了脏读、不可重复读和幻读

这是隔离性最强、数据最安全的级别,但代价是并发性能最差,在实际生产环境中很少使用。

总结:

脏读不可重复读幻读
读未提交可能可能可能
读已提交不可能可能可能
可重复读不可能不可能可能(MySQL InnoDB 解决了一部分)
串行化不可能不可能不可能
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
坤坤
下载 APP