likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 9

在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,在回滚日志里面就会有类似下面的记录。 【MySQL】面试了一次,我才知道事务是这样的

当前值是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。

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

+---------------------------+
|        数据表              |
+---------------------------+
|   账户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的版本链示意图:

+---------------------------+
|        账户A版本链         |
+---------------------------+
|   版本1:1000              |
|   版本2:900               |
|   版本3:1100              |
+---------------------------+

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

+---------------------------+
|        账户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可以确定事务对于数据的可见性,从而保证了并发执行时的数据一致性。

转载自:https://juejin.cn/post/7328234485590245415
评论
请登录