在MySQL中,事务是指一组数据库操作,这些操作要么全部成功执行,要么全部失败回滚。事务是保证数据库操作的一致性和完整性的重要机制。
什么是事务
事务具有以下特性(即ACID特性):
事务的目的在于确保数据库操作的一致性和完整性。当多个操作需要作为一个逻辑单元来执行时,使用事务可以确保这些操作要么全部成功执行,要么全部回滚,避免了数据的不一致性和错误状态的产生。
事务的使用场景包括:
事务隔离级别
MySQL提供了四种事务隔离级别,分别是:
可以通过设置SET TRANSACTION ISOLATION LEVEL
语句来指定事务的隔离级别。默认情况下,MySQL使用的是可重复读(Repeatable Read)隔离级别。
幻读、脏读和不可重复读
在事务隔离级别的概念中,幻读、脏读和不可重复读是三种可能出现的数据一致性问题。
隔离级别 | 脏读 | 幻读 | 不可重复读 |
---|---|---|---|
读未提交 | 是 | 是 | 是 |
读已提交 | 否 | 是 | 是 |
可重复读 | 否 | 否 | 是 |
串行化 | 否 | 否 | 否 |
串行化
是MySQL中的一种事务隔离级别,也是最高级别的隔离级别。在串行化隔离级别下,事务串行执行,即每个事务都会完全独立地执行,不会与其他事务并发执行。这意味着每个事务必须等待前一个事务完成后才能执行,确保了数据的一致性和完整性。在串行化隔离级别下,不会出现幻读、脏读和不可重复读等数据一致性问题。这是因为每个事务在执行读操作时,会对读取的数据进行锁定,其他事务无法修改或插入符合查询条件的数据,从而保证了数据的一致性。
然而,串行化隔离级别也带来了性能上的损失,因为事务串行执行,无法并发地处理多个事务。这可能会导致系统的吞吐量降低,并发性能下降。
MVCC
MVCC是多版本并发控制(Multi-Version Concurrency Control)的缩写。它是一种用于数据库管理系统中的并发控制机制,用于解决并发事务执行时可能出现的数据一致性问题。
数据库隔离级别读已提交、可重复读 都是基于MVCC实现的,相对于加锁简单粗暴的方式,它用更好的方式去处理读写冲突,能有效提高数据库并发性能。
MySQL使用了回滚段(Undo Log)和读视图(Read View)来支持MVCC。回滚段用于记录事务对数据的修改操作,而读视图用于记录事务开始时的系统版本号和事务ID。当事务需要读取数据时,系统会根据读视图来判断是否可见该数据版本。
回滚段是MySQL中用于记录事务对数据的修改操作的一种数据结构。当一个事务对数据进行修改时,MySQL会将修改前的数据记录在回滚段中,以便在事务回滚或者其他事务需要读取旧版本数据时进行恢复。
回滚段的作用是为了保证事务的原子性和一致性。如果一个事务在执行过程中发生错误或者被回滚,MySQL可以通过回滚段中的数据将数据恢复到事务开始之前的状态,以保证数据的一致性。
读视图(Read View):
读视图是MySQL中用于记录事务开始时的系统版本号和事务ID的一种数据结构。每个事务在开始时都会创建一个唯一的读视图,并且在整个事务执行期间都使用这个视图来读取数据。
假设一个值从1被按顺序改成了2、3、4,在回滚日志里面就会有类似下面的记录。
当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view。如图中看到的,在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)
每个读视图包含以下内容:
读视图的工作可以分为两个方面:
当一个事务需要读取数据时,MySQL会根据该事务的读视图来判断是否可见该数据版本。具体地,MySQL会比较数据的版本号和事务的读视图中的系统版本号。如果数据的版本早于事务的读视图的系统版本号,那么该数据对于该事务是可见的;如果数据的版本晚于事务的读视图的系统版本号,那么该数据对于该事务是不可见的。
读视图的另一个重要作用是实现事务的隔离性。每个事务都有自己的读视图,事务之间的读操作不会相互干扰,从而避免了脏读、不可重复读和幻读等并发问题。
示例
在一个银行转账的项目中,假设有两个账户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可以确定事务对于数据的可见性,从而保证了并发执行时的数据一致性。