事务
事务(Transaction) 是数据库管理系统中的一个重要概念,指的是一组数据库操作,它们要么全部成功,要么全部失败,保持数据的一致性。事务的主要目标是确保数据的完整性和一致性,即使在系统发生故障或出现并发操作的情况下,也能够保证数据库的状态是正确的。
事务通常被描述为一个原子操作(atomic operation),即事务中的所有操作要么都执行,要么都不执行。
1. 事务的四个特性(ACID)
Section titled “1. 事务的四个特性(ACID)”事务有四个核心特性,通常用 ACID 来表示,它是保证事务一致性的基础。
1.1. 原子性(Atomicity)
Section titled “1.1. 原子性(Atomicity)”事务是一个“原子操作”,要么完全执行,要么完全不执行。如果事务中的某一部分操作失败,整个事务都会回滚,系统不会留下半完成的状态。
例如,如果一个转账事务包含两个步骤:从账户 A 转账到账户 B,并且转账过程中发生了错误,那么这个操作不会只修改其中的一部分(例如只从 A 扣款),而是会回滚到事务开始前的状态。
1.2. 一致性(Consistency)
Section titled “1.2. 一致性(Consistency)”事务的执行必须将数据库从一个一致性状态转换到另一个一致性状态。事务开始前和结束后,数据库的所有数据都必须满足数据库的完整性约束(如主键、外键等)。
例如,转账操作不会导致余额为负数(假设系统中不允许账户余额为负数)。
1.3. 隔离性(Isolation)
Section titled “1.3. 隔离性(Isolation)”每个事务的执行是相互独立的,即使多个事务并发执行,一个事务的中间状态对其他事务是不可见的。事务的隔离性可以通过不同的隔离级别来控制,隔离级别越高,系统的并发性通常会越低。
例如,如果有两个转账事务同时进行,事务 1 的操作不会被事务 2 看到,反之亦然。
1.4. 持久性(Durability)
Section titled “1.4. 持久性(Durability)”一旦事务提交(commit),它的更改就会永久地保存在数据库中,即使系统发生故障,已提交的事务数据也不会丢失。
例如,在转账成功后,账户 A 和账户 B 的余额更新将持久保存,即使数据库崩溃,数据也不会丢失。
2. 事务的状态
Section titled “2. 事务的状态”一个事务的生命周期包括以下几个状态:
-
活跃(Active):事务正在执行中,还没有完成。
-
提交(Committed):事务中的所有操作都已成功执行,数据已经持久化到数据库中。
-
回滚(Rolled Back):事务失败并回到初始状态,所有在事务中进行的操作都会被撤销,数据恢复到事务开始之前的状态。
3. MySQL的事务
Section titled “3. MySQL的事务”在MySQL中,默认的存储引擎InnoDB支持事务,因此在使用InnoDB引擎的表中可以实现事务操作。
MySQL中的事务操作主要包括以下几种:
3.1. 开始事务
Section titled “3.1. 开始事务”START TRANSACTION; 或 BEGIN; 用于开始一个新的事务。
3.2. 提交事务
Section titled “3.2. 提交事务”COMMIT; 用于提交当前事务,保存所有对数据库所做的更改。
3.3. 回滚事务
Section titled “3.3. 回滚事务”ROLLBACK; 用于回滚当前事务,撤销所有未提交的更改。
3.4. 设置保存点
Section titled “3.4. 设置保存点”SAVEPOINT savepoint_name; 用于在事务中创建一个保存点,可以回滚到该保存点。
3.5. 回滚到保存点
Section titled “3.5. 回滚到保存点”ROLLBACK TO SAVEPOINT savepoint_name; 用于回滚到指定的保存点。
3.6. 释放保存点
Section titled “3.6. 释放保存点”RELEASE SAVEPOINT savepoint_name; 用于删除一个事务的保存点。
4. 事务隔离级别
Section titled “4. 事务隔离级别”在数据库中,事务隔离级别定义了一个事务在执行过程中,能看到其他事务的哪些操作。隔离级别决定了一个事务与其他事务的并发执行如何相互影响,尤其是在读取数据时,如何处理其他事务的更新。
- 脏读
指的是一个事务读取了另一个事务尚未提交的数据,即读取了另一个事务中的脏数据(Dirty Data)。在此情况下,如果另一个事务回滚了或者修改了这些数据,那么读取这些脏数据的事务所处理的数据就是不准确的。
- 不可重复读
指在一个事务内,多次读取同一个数据行,得到的结果可能是不一样的。这是由于其他事务对数据行做出了修改操作,导致数据的不一致性。
- 幻读
指在事务执行过程中,前后两次相同的查询条件得到的结果集不一致,可能会变多或变少
| 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 可能 |
| 串行化 | 不可能 | 不可能 | 不可能 |
4.1.读未提交 (READ UNCOMMITTED)
Section titled “4.1.读未提交 (READ UNCOMMITTED)”-
脏读:这是最低的隔离级别,允许事务读取其他事务未提交的数据。这可能会导致脏读,即一个事务读取到另一个事务未提交的更改,而这个更改可能会被回滚。
-
不可重复读:一个事务可能在执行过程中读取到不一致的数据,因为其他事务可能会修改它已读取的数据。
-
幻读:允许读取不同的数据行,尤其是有新行被插入或删除时。
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;读操作非常多且对数据一致性要求较低的情况。
4.2. 读已提交 (READ COMMITTED)
Section titled “4.2. 读已提交 (READ COMMITTED)”-
脏读:避免了脏读,即一个事务只能读取到另一个事务已提交的数据。
-
不可重复读:同一事务中的相同查询可能会返回不同的结果,因为其他事务可以修改该事务已经读取的数据。
-
幻读:允许其他事务在当前事务操作期间插入新行,从而导致查询结果的变化。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;需要确保不会读取到未提交的数据,但可以接受在同一事务中数据发生变化的场景。
4.3. 可重复读 (REPEATABLE READ)
Section titled “4.3. 可重复读 (REPEATABLE READ)”-
脏读:避免了脏读。
-
不可重复读:保证在一个事务中多次读取同一数据时,结果是相同的,即使其他事务提交了修改。这通常通过对已读取的数据行加共享锁(S锁)实现。
-
幻读:虽然避免了不可重复读,但
REPEATABLE READ理论上仍允许幻读。在同一事务内,其他事务可能插入新数据,使得事务查询的结果集发生变化。补充说明:在 MySQL 的 InnoDB 引擎中,会使用 MVCC(多版本并发控制)和 Next-Key Lock(记录锁 + 间隙锁)机制,在绝大多数情况下也能避免幻读,因此 MySQL 在该隔离级别下几乎不会发生幻读。
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;大多数情况下,事务希望保证已读取数据的一致性(防止读取不一致的数据),但对新增数据(幻读)不太敏感。
4.4. 可串行化 (SERIALIZABLE)
Section titled “4.4. 可串行化 (SERIALIZABLE)”-
脏读:避免了脏读。
-
不可重复读:避免了不可重复读。
-
幻读:避免了幻读。所有事务被强制按序执行的效果,即一个事务未完成时,其他事务无法对其读取范围进行插入或修改。数据库通常通过对查询范围加更严格的锁(如强制加共享锁甚至范围锁)来实现这种一致性。
-
性能:最严格的隔离级别,因此并发性最差,性能相对较低。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;需要最高级别的数据一致性(如银行转账、会计系统等)时使用。
锁机制对比总结:
隔离级别 锁对象 并发性 是否可能幻读 REPEATABLE READ 已读取记录(行锁) 较高 理论允许,MySQL 实际几乎避免 SERIALIZABLE 查询范围(范围锁,甚至接近表锁) 较低 完全避免
4.5. 性能和一致性的权衡
Section titled “4.5. 性能和一致性的权衡”-
READ UNCOMMITTED 提供最低级别的隔离,性能最佳,但可能会出现严重的数据不一致问题(脏读、不可重复读、幻读)。
-
SERIALIZABLE 提供最高级别的数据一致性,避免了所有并发问题,但性能最差,因为它通过大量的锁操作来确保事务的顺序执行。
通常,开发者会根据具体的业务需求和性能要求,在这四种隔离级别中选择合适的级别。例如,对于不要求极高一致性的查询,READ COMMITTED 或 REPEATABLE READ 已经足够,而对于需要极高一致性的应用,如银行系统,可能需要使用 SERIALIZABLE 隔离级别。
5. 幻读的解决方案
Section titled “5. 幻读的解决方案”幻读(Phantom Read) 是指在一个事务中,执行两次相同的查询时,查询结果集会发生变化,因为另一个事务在该过程中插入、更新或删除了数据。为了解决幻读问题,MySQL 提供了多种机制来保证数据的一致性。
5.1. 快照读(Snapshot Read)+ MVCC 解决幻读
Section titled “5.1. 快照读(Snapshot Read)+ MVCC 解决幻读”MVCC(多版本并发控制) 是 MySQL 用来解决幻读的核心机制,尤其是在 可重复读(REPEATABLE READ) 隔离级别下。通过 MVCC,事务在开始时会获得一个数据的快照,这个快照会在事务执行过程中保持一致性,避免了其他事务插入新数据。
工作原理:
-
事务启动时的快照:在事务开始时,MySQL 会为当前事务创建数据快照,这个快照记录了所有该事务在执行期间可以看到的数据状态。
-
数据不可见性:即使在事务执行期间,其他事务插入新数据(例如新行),当前事务也无法看到这些新插入的记录。
-
快照一致性:事务中所有的
SELECT查询都返回一致的结果,保证了事务内数据一致性,避免了幻读。
假设在 REPEATABLE READ 隔离级别下,执行以下查询。
SELECT * FROM employees WHERE salary > 50000;
- 事务 T1 启动并执行此查询,返回当前薪资大于 50,000 的员工列表。
- 在 T1 执行期间,事务 T2 插入了一条薪资为 60,000 的新记录。
- 由于 MVCC 机制,T1 事务在后续执行相同查询时,仍然只能看到事务开始时的快照数据,而看不到 T2 插入的新数据。因此,幻读问题得到了避免。
5.2. 当前读(Current Read)+ Next-Key Lock 解决幻读
Section titled “5.2. 当前读(Current Read)+ Next-Key Lock 解决幻读”对于 当前读(如 SELECT ... FOR UPDATE),MySQL 使用 next-key lock(即记录锁和间隙锁的组合)来解决幻读。SELECT ... FOR UPDATE 是一种行级锁操作,它通过加锁的方式确保其他事务不能在当前事务的查询范围内插入数据,从而避免幻读。
工作原理:
-
记录锁:锁定当前行的数据,防止其他事务修改或删除该行。
-
间隙锁:锁住一个数据范围,防止其他事务在该范围内插入数据行。
SELECT * FROM employees WHERE salary > 50000 FOR UPDATE;
- 当事务 T1 执行
SELECT ... FOR UPDATE查询时,它会锁定查询到的所有记录,同时锁定这些记录之间的间隙。- 如果事务 T2 尝试在 T1 查询的范围内插入一条新记录,它会被阻塞,直到 T1 提交或回滚,防止了幻读。
- 该查询会锁定所有符合条件的记录,并且锁住这些记录之间的间隙(防止其他事务插入符合条件的新记录)。
- 如果事务 T2 尝试插入一条薪资为 60,000 的新记录,由于事务 T1 的
SELECT ... FOR UPDATE已经锁住了该范围,T2 会被阻塞,直到 T1 提交或回滚,从而避免了幻读。
5.3. 总结
Section titled “5.3. 总结”-
快照读(Snapshot Read):通过 MVCC 机制,在 REPEATABLE READ 隔离级别下解决了幻读问题。事务执行期间,只能看到事务启动时的数据快照,避免了其他事务的插入操作。
-
当前读(Current Read):通过 next-key lock(记录锁 + 间隙锁)来解决幻读。执行
SELECT ... FOR UPDATE查询时,锁定当前行数据和数据范围,防止其他事务在当前事务的查询范围内插入新数据。 -
事务隔离级别:不同的隔离级别对幻读的控制力度不同,REPEATABLE READ 隔离级别通过 MVCC 解决幻读,SERIALIZABLE 通过严格的锁机制解决幻读,但性能开销较大。