mysql-事务详解

在这里插入图片描述

事务实现

MySQL 事务的实现主要依赖于以下几个核心组件和机制:

  1. InnoDB存储引擎:MySQL事务通常是由InnoDB存储引擎支持的。InnoDB提供了事务的核心功能,如ACID(原子性、一致性、隔离性、持久性)属性。

​ 2.Redo Log(重做日志):为了确保事务的持久性(Durability),InnoDB使用了Redo Log。在事务提交时,MySQL会将事务的修改记录写入Redo Log,即使系统崩溃,也可以通过Redo Log进行数据恢复

​ 3.Undo Log(回滚日志):为了支持原子性(Atomicity)和一致性(Consistency),MySQL会在事务开始之前记录数据的原始状态,即Undo Log。这样,如果事务失败或者被回滚,MySQL可以通过Undo Log恢复到修改前的状态。。 AT模式

​ 4.锁机制

  • 行锁:InnoDB支持行级锁,以确保多事务并发执行时的数据一致性。
  • 表锁:MySQL也支持表级锁,但InnoDB引擎通常使用行锁来提高并发性。

锁的机制确保了事务的隔离性(Isolation),使得事务在并发操作时不会互相干扰。

​ 5.隔离级别:MySQL支持多种事务隔离级别,如读未提交、读已提交、可重复读、串行化。通过这些隔离级别,MySQL可以控制事务并发时的读写行为,以减少并发带来的数据一致性问题。

​ 6.两阶段提交:为了保证数据的一致性,MySQL采用了两阶段提交机制(2PC),在事务提交时,首先会将事务的变更写入Redo Log,确保日志写入成功后,再将实际数据写入磁盘。这可以保证事务的原子性,即事务要么全部提交成功,要么回滚。

​ 7.MVCC(多版本并发控制)**:MySQL通过MVCC来实现事务的隔离性,特别是在可重复读(Repeatable Read)隔离级别下,使用MVCC可以让事务在读取数据时看到的是某个固定的快照,而不会被其他事务的并发修改所影响。

mvcc

锁学后补充

1
当前读和快照读

MVCC(多版本并发控制,Multi-Version Concurrency Control)是一种常用的机制,可以帮助数据库系统实现事务的隔离性。MVCC 通过为每个事务提供数据库中数据的不同版本来实现隔离,从而避免了事务之间的直接冲突。

隔离性

并发事务问题

1.脏读

不同事务读取

定义:脏读是指一个事务可以读取到另一个事务尚未提交的修改。如果后续事务回滚,读取的数据就会变成无效的,从而导致数据不一致。

场景:事务A修改了某条记录,但未提交,事务B读取了该记录的修改内容。如果事务A回滚,事务B读取的数据就是无效的。

SQL示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码-- 假设表 `accounts` 有一行数据:id = 1, balance = 1000

-- 事务A
START TRANSACTION;
UPDATE accounts SET balance = 900 WHERE id = 1; -- 修改余额为900,但未提交

-- 事务B
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 读到了事务A未提交的余额900

-- 事务A
ROLLBACK; -- 事务A回滚,balance恢复为1000

-- 事务B现在读到的余额是900,但实际上余额已经恢复为1000,这是脏读

解决方法:设置隔离级别为读已提交(Read Committed)或更高,防止脏读。

1
2

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
2.不可重复读

不可重复读发生在事务多次读取同一条记录时,由于另一个事务提交了修改,导致读取结果不同。

定义:不可重复读是指在一个事务中对同一条记录的多次读取结果不一致,因为另一事务在两次读取之间修改了该记录并提交了修改。

场景:事务A第一次读取某条记录的数据,事务B修改并提交了这条记录,事务A再次读取该记录时,读取到了不同的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 假设表 `accounts` 有一行数据:id = 1, balance = 1000

-- 事务A
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 第一次读取,余额为1000

-- 事务B
START TRANSACTION;
UPDATE accounts SET balance = 800 WHERE id = 1; -- 修改余额为800
COMMIT; -- 提交事务B

-- 事务A
SELECT balance FROM accounts WHERE id = 1; -- 第二次读取,余额变为800

-- 事务A第一次读取到的是1000,第二次读取到的是800,发生了不可重复读

解决方法:设置隔离级别为可重复读(Repeatable Read)或更高,防止不可重复读。

1
2

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
3.幻读

定义:幻读是指一个事务在读取某范围内的记录时,另一事务插入了新的记录。由于新的记录符合第一次查询的条件,因此再次读取时,前后结果不同。

场景:事务A在查询某个条件范围内的记录,事务B插入了一条符合条件的新记录,导致事务A的再次查询结果中出现“幻影”记录。

幻读发生在事务读取一个数据范围时,另一个事务在该范围内插入、删除了数据,导致读取到的记录集不同

SQL示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sql复制代码-- 假设表 `orders` 有两行数据:id = 1, 2,对应的金额为500, 800

-- 事务A
START TRANSACTION;
SELECT * FROM orders WHERE amount > 300; -- 查询金额大于300的订单,返回两条记录:id = 1, 2

-- 事务B
START TRANSACTION;
INSERT INTO orders (id, amount) VALUES (3, 700); -- 插入新订单,金额为700
COMMIT; -- 提交事务B

-- 事务A
SELECT * FROM orders WHERE amount > 300; -- 再次查询,发现多了一条新记录:id = 3

-- 事务A第一次查询结果中没有id为3的记录,第二次却出现了,这是幻读

解决方法:设置隔离级别为串行化(Serializable)或通过InnoDB的间隙锁(Gap Lock)防止幻读。

1
2

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

脏读读取了其他事务尚未提交的修改,解决方法是使用READ COMMITTED或更高的隔离级别。

不可重复读同一事务中多次读取同一数据得到不同的结果,解决方法是使用REPEATABLE READ

幻读事务中读取的范围内的记录集在后续读取时发生变化,解决方法是使用SERIALIZABLE或Gap Lock。

事务隔离级别

30c55ef7b7688240c3ef12043d093524

img

1.. Read Uncommitted(读未提交)
  • 脏读(Dirty Read):会出现,事务可以读取未提交的数据。
  • 不可重复读(Non-repeatable Read):会出现,由于数据可以被修改并提交,导致多次读取不一致。
  • 幻读(Phantom Read):会出现,由于插入或删除操作可以影响读取范围内的数据集。

在该隔离级别下,没有有效的并发控制,允许读取未提交的数据,因此可能出现所有并发问题。这种隔离级别通常不用于生产环境,因为数据一致性无法得到保障。

2.Read Committed(读已提交)
  • 脏读:× 不会出现,事务只能读取已经提交的数据,避免了读取未提交的数据。
  • 不可重复读:√ 仍然会出现,因为在一个事务中多次读取同一条记录时,其他事务可以修改并提交该记录,导致前后读取结果不同。
  • 幻读:√ 仍然会出现,因为其他事务可以插入或删除符合查询条件的新记录,导致数据集的变化。
  • 在 RC 级别下,事务每次读取数据时都会获取当前最新的已提交数据的快照。也就是说,当事务在执行两次相同的查询时,如果在这两次查询之间有其他事务提交了新的数据变更,查询结果可能不同。

如何解决脏读:通过只允许读取已提交的数据,避免了脏读问题。换句话说,事务只能看到其他事务已经提交的结果,而不是中途修改的未提交数据。

3. Repeatable Read(可重复读,MySQL默认隔离级别)
  • 脏读:× 不会出现,事务只能看到已经提交的修改。
  • 不可重复读:× 不会出现,事务在其生命周期内使用一致性视图(Snapshot),即使其他事务修改并提交了数据,本事务多次读取同一条记录时,仍然会看到初始读取时的数据。—在 RR 级别下,事务在第一次读取数据时,会生成一个一致性读快照(快照的时间点是事务开始时或第一次读取时)。
    • 一致性视图(Snapshot):当事务在 Repeatable Read 隔离级别下启动时,MySQL 会创建一个快照(Snapshot)。事务中所有的 SELECT 语句都会从这个快照中读取数据,而不会受到其他事务在该事务执行过程中对数据库所做的更改影响。
  • 幻读:√ 仍然可能出现,因为其他事务可以在查询范围内插入或删除新记录,影响结果集。

** ********* 一致性视图**只能保护已有的数据的一致性,但它并不能防止新的数据插入 **********

如何解决脏读和不可重复读

  • 脏读通过只读取已提交的数据解决。
  • 不可重复读通过一致性视图(MVCC)解决。事务在开始时获取一个快照,后续查询将基于这个快照,即使其他事务修改了记录,本事务始终读取同一版本的数据,避免了多次读取时数据不一致。

如何解决幻读(MySQL特性)

  • InnoDB通过引入间隙锁(Gap Lock)来部分解决幻读问题。间隙锁不仅锁住查询到的记录,还锁定记录之间的“间隙”,防止其他事务在这些间隙内插入新记录。虽然这在一定程度上解决了幻读问题,但并不是完全解决,特别是在复杂的查询场景中。
4. Serializable(串行化)
  • 脏读:× 不会出现。
  • 不可重复读:× 不会出现。
  • 幻读:× 不会出现。

如何解决所有问题

  • 在串行化隔离级别下,事务是串行执行的,仿佛事务是一个接一个顺序完成的,不存在并发问题。MySQL通过加锁机制 元数据锁(meta data lock)保证这一点。每次读取数据时都会加锁,任何其他事务在该事务执行过程中都无法对相关数据进行修改或插入,确保了没有数据的并发修改或插入,从根本上防止了脏读、不可重复读和幻读。

代价:由于串行化隔离级别会对所有相关数据加锁,因此并发性能大大降低,所有事务必须一个接一个地执行,无法同时进行并发操作。因此,这种隔离级别通常只在对数据一致性要求极高的场景中使用,且性能开销较大。


mysql-事务详解
http://example.com/2024/09/24/mysql/事务视图/事务/
作者
John Doe
发布于
2024年9月24日
许可协议