【MySQL】面试了一次,我才知道事务是这样的

在MySQL中,事务是指一组数据库操作,这些操作要么全部成功执行,要么全部失败回滚。事务是保证数据库操作的一致性和完整性的重要机制。

什么是事务

事务具有以下特性(即ACID特性):

  1. 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败回滚,不会出现部分执行的情况。
  2. 一致性(Consistency):事务在执行前后,数据库的状态必须保持一致。即,事务的执行不会破坏数据库的完整性约束。
  3. 隔离性(Isolation):并发执行的多个事务之间是相互隔离的,每个事务都感觉不到其他事务的存在。事务的隔离级别可以通过设置来控制,如读未提交、读已提交、可重复读和串行化。
  4. 持久性(Durability):一旦事务提交,其所做的修改将永久保存在数据库中,即使系统崩溃也不会丢失。

事务的目的在于确保数据库操作的一致性和完整性。当多个操作需要作为一个逻辑单元来执行时,使用事务可以确保这些操作要么全部成功执行,要么全部回滚,避免了数据的不一致性和错误状态的产生。

事务的使用场景包括:

  1. 多个操作需要作为一个原子操作执行,要么全部成功,要么全部失败回滚。
  2. 并发执行的多个操作需要保证数据的一致性和完整性。
  3. 需要对数据进行复杂的更新操作,如涉及多个表的操作,需要保证操作的一致性。

事务隔离级别

MySQL提供了四种事务隔离级别,分别是:

  1. 读未提交(Read Uncommitted):最低的隔离级别,事务中的修改操作可以被其他事务读取,即一个事务可以读取到另一个事务未提交的数据。这种隔离级别可能导致脏读(Dirty Read)问题,即读取到未提交的数据。
  2. 读已提交(Read Committed):事务中的修改操作只有在提交后才能被其他事务读取,即一个事务只能读取到已提交的数据。这种隔离级别可以避免脏读问题,但可能导致不可重复读(Non-repeatable Read)问题,即同一个事务内的两次查询结果不一致。
  3. 可重复读(Repeatable Read):事务中的查询操作只能读取到事务开始时的数据快照,即在同一个事务中多次执行同样的查询操作,结果应该是一致的。这种隔离级别可以避免脏读和不可重复读问题,但可能导致幻读(Phantom Read)问题,即同一个事务内的两次查询结果包含了不同的行。
  4. 串行化(Serializable):最高的隔离级别,事务串行执行,确保每个事务在执行时都完全看不到其他事务对数据库的修改。这种隔离级别可以避免脏读、不可重复读和幻读问题,但会降低并发性能。

可以通过设置SET TRANSACTION ISOLATION LEVEL语句来指定事务的隔离级别。默认情况下,MySQL使用的是可重复读(Repeatable Read)隔离级别。

幻读、脏读和不可重复读

在事务隔离级别的概念中,幻读、脏读和不可重复读是三种可能出现的数据一致性问题。

  1. 幻读(Phantom Read):幻读指的是在同一个事务中,多次执行同样的查询操作时,结果集中出现了不同的行。这是因为在并发环境下,其他事务插入或删除了符合查询条件的数据,导致了结果集的不一致性。幻读主要发生在可重复读(Repeatable Read)隔离级别下。
  2. 脏读(Dirty Read):脏读指的是一个事务读取到了另一个事务未提交的数据。换句话说,一个事务读取到了其他事务修改但尚未提交的数据。这可能导致事务读取到不正确的数据,因为未提交的数据可能会被回滚。脏读主要发生在读未提交(Read Uncommitted)隔离级别下。
  3. 不可重复读(Non-repeatable Read):不可重复读指的是在同一个事务中,多次执行同样的查询操作时,结果集中的某些行发生了变化。这是因为在并发环境下,其他事务修改了符合查询条件的数据,导致了结果集的不一致性。不可重复读主要发生在读已提交(Read Committed)和可重复读(Repeatable Read)隔离级别下。
隔离级别 脏读 幻读 不可重复读
读未提交
读已提交
可重复读
串行化

串行化

是MySQL中的一种事务隔离级别,也是最高级别的隔离级别。在串行化隔离级别下,事务串行执行,即每个事务都会完全独立地执行,不会与其他事务并发执行。这意味着每个事务必须等待前一个事务完成后才能执行,确保了数据的一致性和完整性。在串行化隔离级别下,不会出现幻读、脏读和不可重复读等数据一致性问题。这是因为每个事务在执行读操作时,会对读取的数据进行锁定,其他事务无法修改或插入符合查询条件的数据,从而保证了数据的一致性。

然而,串行化隔离级别也带来了性能上的损失,因为事务串行执行,无法并发地处理多个事务。这可能会导致系统的吞吐量降低,并发性能下降。

MVCC

MVCC是多版本并发控制(Multi-Version Concurrency Control)的缩写。它是一种用于数据库管理系统中的并发控制机制,用于解决并发事务执行时可能出现的数据一致性问题。

数据库隔离级别读已提交、可重复读 都是基于MVCC实现的,相对于加锁简单粗暴的方式,它用更好的方式去处理读写冲突,能有效提高数据库并发性能。

MySQL使用了回滚段(Undo Log)和读视图(Read View)来支持MVCC。回滚段用于记录事务对数据的修改操作,而读视图用于记录事务开始时的系统版本号和事务ID。当事务需要读取数据时,系统会根据读视图来判断是否可见该数据版本。

  1. 回滚段(Undo Log):
    回滚段是MySQL中用于记录事务对数据的修改操作的一种数据结构。当一个事务对数据进行修改时,MySQL会将修改前的数据记录在回滚段中,以便在事务回滚或者其他事务需要读取旧版本数据时进行恢复。

回滚段的作用是为了保证事务的原子性和一致性。如果一个事务在执行过程中发生错误或者被回滚,MySQL可以通过回滚段中的数据将数据恢复到事务开始之前的状态,以保证数据的一致性。

  1. 读视图(Read View):

    读视图是MySQL中用于记录事务开始时的系统版本号和事务ID的一种数据结构。每个事务在开始时都会创建一个唯一的读视图,并且在整个事务执行期间都使用这个视图来读取数据。

假设一个值从1被按顺序改成了2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view。如图中看到的,在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)

每个读视图包含以下内容:

  1. 系统版本号(System Version Number):读视图记录了事务开始时的系统版本号,也称为当前系统版本号。系统版本号是一个递增的数字,用于标识数据的创建时间。
  2. 事务ID(Transaction ID):读视图还记录了事务开始时的事务ID。事务ID是一个唯一的标识符,用于标识事务的开始和结束。

读视图的工作可以分为两个方面:

  1. 读取数据:
    当一个事务需要读取数据时,MySQL会根据该事务的读视图来判断是否可见该数据版本。具体地,MySQL会比较数据的版本号和事务的读视图中的系统版本号。如果数据的版本早于事务的读视图的系统版本号,那么该数据对于该事务是可见的;如果数据的版本晚于事务的读视图的系统版本号,那么该数据对于该事务是不可见的。
  2. 事务隔离:
    读视图的另一个重要作用是实现事务的隔离性。每个事务都有自己的读视图,事务之间的读操作不会相互干扰,从而避免了脏读、不可重复读和幻读等并发问题。

示例

在一个银行转账的项目中,假设有两个账户A和B,初始时账户A的余额为1000,账户B的余额为500。现在有两个事务同时进行转账操作:事务T1将100元从账户A转到账户B,事务T2将200元从账户B转到账户A。

以下是转账过程的图解示例:

diff 复制代码
+---------------------------+
|        数据表              |
+---------------------------+
|   账户A余额:1000         |
|   账户B余额:500          |
+---------------------------+

        事务T1(系统版本号1,事务ID1)
        |
        | 转账操作:A -> B 100元
        |
        v
+---------------------------+
|        读视图T1            |
+---------------------------+
|   系统版本号:1            |
|   事务ID:1               |
+---------------------------+

        事务T2(系统版本号1,事务ID2)
        |
        | 转账操作:B -> A 200元
        |
        v
+---------------------------+
|        读视图T2            |
+---------------------------+
|   系统版本号:1            |
|   事务ID:2               |
+---------------------------+

在上面的示例中,事务T1和事务T2都有自己的读视图,其中系统版本号和事务ID分别为1和2。

首先,事务T1读取账户A的余额为1000,然后执行转账操作将100元转到账户B。此时,账户A的余额变为900,账户B的余额变为600。

接着,事务T2读取账户B的余额为500,然后执行转账操作将200元转到账户A。此时,账户A的余额变为1100,账户B的余额变为300。

现在来看一下版本链的概念。版本链是由数据行的多个版本组成的链表结构,用于记录数据的历史版本。在这个例子中,账户A和账户B的余额都有多个版本。

下面是账户A的版本链示意图:

diff 复制代码
+---------------------------+
|        账户A版本链         |
+---------------------------+
|   版本1:1000              |
|   版本2:900               |
|   版本3:1100              |
+---------------------------+

下面是账户B的版本链示意图:

diff 复制代码
+---------------------------+
|        账户B版本链         |
+---------------------------+
|   版本1:500               |
|   版本2:600               |
|   版本3:300               |
+---------------------------+

版本链记录了每次数据变更的历史版本。通过比较数据的版本号和事务的读视图中的系统版本号,并沿着版本链向前遍历,MySQL可以确定哪些数据对于事务是可见的。

例如,对于事务T1来说,它的读视图的系统版本号为1。当事务T1读取账户A的余额时,它会比较数据的版本号和自己的读视图的系统版本号。在这个例子中,账户A的余额版本号为2,早于事务T1的读视图的系统版本号1,所以事务T1可以读取账户A的余额为900。

同样地,对于事务T2来说,它的读视图的系统版本号也为1。当事务T2读取账户B的余额时,它会比较数据的版本号和自己的读视图的系统版本号。在这个例子中,账户B的余额版本号为2,早于事务T2的读视图的系统版本号1,所以事务T2可以读取账户B的余额为600。

通过版本链的比较和遍历,MySQL可以确定事务对于数据的可见性,从而保证了并发执行时的数据一致性。

相关推荐
zhangxingchao4 分钟前
AI 大模型面试核心二:微调、RAG、MCP、Agent 与工程落地
前端·人工智能·后端
IT策士6 分钟前
Django 从 0 到 1 打造完整电商平台:商品搜索
后端·python·django
zhangxingchao6 分钟前
AI 大模型面试核心三: RAG、Agent 到 Prompt Engineering 的工程化理解
前端·人工智能·后端
彦为君9 分钟前
JavaSE-11-ByteBuffer(NIO核心组件)
java·开发语言·前端·数据库·后端·spring·nio
刀法如飞15 分钟前
《理解道德经》简单版-第 1 章:道可道,非常道
前端·后端·面试
浩风祭月29 分钟前
我用 Cursor 把一个订单状态机从 800 行重构到 120 行,且逻辑零差错
前端·后端
日月云棠37 分钟前
17 Dubbo 2.7 集群容错策略源码解析
java·后端
码上谈兵43 分钟前
一次 MySQL 连接池打满,我花一晚上重构了 Go 的数据库操作
mysql
Oneslide1 小时前
银河麒麟紧Linux 内核CopyFail高危漏洞(CVE-2026-31431)通告
后端
RelishCoding1 小时前
SpringMVC-01
后端·spring