MVCC
史上最详尽,一文讲透 MVCC 实现原理_一文讲透mvcc实现原理-CSDN博客
怎么理解 事务启动时,InnoDB 会为它分配一个唯一的事务 ID,什么时候启动的,是@Transactional注解吗,执行注解方法代表,事物开始,这个事物ID存哪里的
1. 事务 ID 在 InnoDB 中的基本概念
在 InnoDB 存储引擎中,每个事务都会被分配一个 唯一的事务 ID(通常是一个递增的整数值),用来标识该事务。这一事务 ID 是 InnoDB 内部用来管理事务并确保数据一致性和隔离性的核心元素。
当一个事务开始时,InnoDB 会为其分配一个新的事务 ID。这个事务 ID 在整个事务的生命周期内都是固定的,直到该事务结束(提交或回滚)
不可重复读,是一个事务读取了另外一个事务未提交到数据还是已经提交到数据,到底该不该读到
不可重复读(Non-repeatable Read) 是指在一个事务中读取某个数据项的值,在该事务还未结束时,另一个事务对该数据项进行了修改(并且已经提交),从而导致第一个事务再次读取该数据时,获取到的值与第一次读取时的值不同。换句话说,就是在同一个事务中,读取到的数据在多次读取时发生了变化,导致结果不一致。
关键点:
- 事务A 在某一时刻读取了数据项X。
- 事务B 在 事务A 还没有结束时修改了数据项X,并且 事务B 提交了。
- 事务A 之后再次读取数据项X,发现其值已被 事务B 修改,导致数据不一致。
应该读取到吗?
- 是否应该读取到修改后的数据,取决于事务的隔离级别。
在 可重复读(Repeatable Read) 隔离级别下,事务A 应该始终读取到同样的数据,即使 事务B 提交了修改,也不应该影响 事务A 已经读取过的数据。
但是,在 读已提交(Read Committed) 或 未提交读(Read Uncommitted) 隔离级别下,事务A 可能会看到 事务B 提交的修改,导致不可重复读。
不可重复读的例子
假设有一个银行账户 balance
字段,初始值为100。
- 事务A 开始,读取
balance
的值为100。 - 事务B 修改了
balance
的值,将其更新为200,并提交。 - 事务A 再次读取
balance
,发现值变成了200。
在这个例子中,事务A 在两个读取操作之间,数据被 事务B 修改并提交,导致了 事务A 两次读取的值不同,这就是不可重复读。
隔离级别与不可重复读的关系
-
Read Uncommitted(未提交读):
- 允许读取其他事务未提交的数据,可能会出现脏读。
- 不可重复读:会发生,因为一个事务可以看到另一个事务未提交的修改。
-
Read Committed(读已提交):
- 保证读取的数据是已经提交的数据,但同一事务内的不同查询可能读取到不同的结果。
- 不可重复读:也可能发生,因为一个事务读取的数据可能在另一个事务提交后发生变化。
-
Repeatable Read(可重复读):
- 保证同一个事务内多次读取的数据一致,但不能防止幻读(即查询条件不变时,结果集的行数发生变化)。
- 不可重复读:在这个隔离级别下,通常是不会发生,因为同一个事务读取的数据在整个事务过程中是一致的。
-
Serializable(可串行化):
- 最严格的隔离级别,保证事务的完全隔离,避免了不可重复读和幻读的情况。
- 不可重复读:不会发生。
结论
在 可重复读 隔离级别下,事务A 在整个事务过程中不应该读取到被其他事务修改的已提交数据。因此,不可重复读 是违反 可重复读 隔离级别的特性。根据事务的隔离级别,是否能读取到其他事务提交的修改是有差异的。
- 在 读已提交 或更低隔离级别下,不可重复读 是允许发生的。
- 在 可重复读 或更高隔离级别下,事务内部的数据读取应该保持一致,不应发生不可重复读。
幻读
幻读(Phantom Read) 是指在一个事务中,进行多次相同的查询时,查询结果发生变化的情况。这种现象发生的原因是,在事务的执行过程中,其他事务对数据的插入、删除或更新导致查询结果发生了变化。与不可重复读(Non-repeatable Read)不同,幻读不仅仅是读取的同一行数据发生变化,而是查询结果的行数发生变化。
幻读的场景
假设有一个数据库表 orders
,包含以下数据:
order_id | amount |
---|---|
1 | 100 |
2 | 200 |
3 | 150 |
事务 T1 和事务 T2 进行如下操作:
-
事务T1 执行一个查询,查询所有
amount > 100
的订单:sql
SELECT * FROM orders WHERE amount > 100;
结果:
| order_id | amount | |----------|--------| | 2 | 200 | | 3 | 150 |
-
事务T2 插入了一条新的订单:
sql
INSERT INTO orders (order_id, amount) VALUES (4, 120);
-
事务T1 再次执行相同的查询:
sql
SELECT * FROM orders WHERE amount > 100;
结果:
| order_id | amount | |----------|--------| | 2 | 200 | | 3 | 150 | | 4 | 120 | <-- 新插入的数据
在这个例子中,事务T1 在两次查询之间看到的结果发生了变化,因为 事务T2 插入了一条符合查询条件的新数据。这种现象就是幻读,即查询结果的“幻象”发生了变化。
幻读的原因
幻读的根本原因在于其他事务在你进行查询后插入、更新或删除了满足你查询条件的数据行,从而导致你的查询结果发生了变化。在没有足够隔离的情况下,其他事务对数据的修改可能影响你当前事务的查询结果。
幻读与其他隔离级别的关系
幻读发生的频率和严重程度与事务的隔离级别密切相关。不同的隔离级别对于幻读的控制能力不同:
-
Read Uncommitted(未提交读):
- 允许读取其他事务未提交的数据,也就是可能发生脏读、不可重复读和幻读。
- 在这种隔离级别下,幻读很容易发生。
-
Read Committed(读已提交):
- 只允许读取已提交的数据,解决了脏读问题,但同一个事务中的查询可能会受到其他事务提交的修改的影响,因此仍然可能发生不可重复读和幻读。
- 幻读 可能发生,因为其他事务在事务T1的查询之间插入或删除了数据。
-
Repeatable Read(可重复读):
- 保证同一个事务内多次查询的数据是一致的,即不可重复读不会发生。然而,它仍然不能完全防止幻读,因为其他事务仍然可以插入、删除或修改满足查询条件的数据行。
- 在 MySQL 等数据库的 Repeatable Read 隔离级别下,虽然不可重复读被防止了,但幻读仍然可能发生。
-
Serializable(可串行化):
- 最严格的隔离级别,完全避免幻读。它通过强制事务按照串行执行的方式,确保在事务执行过程中,其他事务不能对查询结果集进行任何修改。换句话说,其他事务必须等待当前事务结束后,才能进行插入、更新或删除操作。
- 幻读 在这种隔离级别下是不会发生的。
幻读的解决方案
为了避免幻读,可以使用不同的事务隔离级别或加锁策略:
-
使用更高的隔离级别:
- Serializable 隔离级别是解决幻读的最直接方法。它通过强制事务串行化执行,防止其他事务在当前事务执行期间对数据进行插入或删除,从而避免幻读。
-
使用范围锁(Gap Lock):
- 在 Repeatable Read 隔离级别下,数据库可以通过使用 范围锁(Gap Lock) 来避免幻读。例如,MySQL 使用 Gap Lock 来锁住一个查询的范围,从而防止其他事务插入满足该查询条件的新数据行。
- 这种锁机制可以防止在查询操作之间插入新记录,从而避免幻读。
-
避免频繁修改数据的操作:
- 通过设计系统避免频繁的数据插入、删除等操作,尤其是对大型数据集进行范围查询时,减少幻读现象的发生。
总结
- 幻读(Phantom Read) 是指在同一个事务中,执行两次相同的查询时,查询结果的记录数量或内容发生了变化,通常是由于其他事务插入、删除或修改了符合查询条件的数据。
- 幻读常见于 Read Committed 和 Repeatable Read 隔离级别,但在 Serializable 隔离级别下不会发生。
- 为了防止幻读,可以采用 Serializable 隔离级别,或在 Repeatable Read 隔离级别下使用 范围锁。
快照读和当前读
快照读(Snapshot Read) 和 当前读(Current Read) 是数据库事务中两种常见的读取方式,它们在不同的隔离级别下有着不同的表现,具体的含义如下:
1. 快照读(Snapshot Read)
快照读 是指事务在读取数据时,基于某个时间点的一致性视图(即数据的快照)来读取数据,而不是直接读取当前数据库的最新数据状态。快照读不受其他事务的影响,它总是读取事务开始时数据库的快照内容。也就是说,快照读在事务的整个生命周期中始终看到相同的数据版本。
-
特点:
- 不阻塞其他事务:快照读不会锁住读取的数据行,允许其他事务对这些数据行进行修改。
- 一致性视图:每次执行快照读时,都从事务开始时的快照(数据的一致性视图)读取数据,确保事务内的一致性,即使其他事务在运行过程中对数据进行了更新或插入。
- 避免脏读、不可重复读:由于读取的是数据的快照,所以不会受到其他事务未提交的修改影响。
-
应用场景:快照读常见于隔离级别为 Repeatable Read(可重复读)和 Serializable(可串行化)时。它在数据库中通常通过实现 多版本并发控制(MVCC) 来实现。
-
在 MySQL 中,InnoDB 存储引擎就采用了 MVCC 来实现快照读。
-
例子:事务 T1 执行了一个查询,查询所有
amount > 100
的订单,且事务 T1 会看到自己开始时的快照视图,不管其他事务(如 T2)在事务 T1 执行期间是否插入或更新了数据。
-
2. 当前读(Current Read)
当前读 是指事务在读取数据时,会获取数据库中数据的当前版本,意味着它读取的是数据库的最新状态。当前读会被其他事务的更新或插入影响,并且通常会对数据加锁,以保证事务的隔离性。
-
特点:
- 数据锁定:当前读一般会涉及到行级锁或者更高级别的锁,确保读取的数据在事务执行期间不会被其他事务修改。通过锁定数据,避免数据的不一致性。
- 受其他事务影响:当前读读取的是数据库的最新数据,因此可能会受到其他事务的修改影响。其他事务对数据的更新会影响当前读的结果。
- 可能引发阻塞:由于当前读会加锁,因此如果其他事务正在更新同一数据行,当前读可能会阻塞,直到其他事务提交或回滚。
-
应用场景:当前读常见于隔离级别较低的 Read Committed(读已提交)和 Serializable(可串行化)时,通常通过 行级锁 来实现当前读。
- 例子:事务 T1 执行了一个查询,并且查询时对数据行加锁,确保该行数据在事务 T1 执行期间不会被其他事务修改或删除。事务 T1 可能会看到其他事务已经提交的更新数据。
快照读和当前读的比较
特性 | 快照读 (Snapshot Read) | 当前读 (Current Read) |
---|---|---|
读取的数据版本 | 读取事务开始时的数据快照,不会看到其他事务的未提交修改 | 读取数据库的当前数据,可能包含其他事务的修改或未提交的修改 |
受其他事务影响 | 不受其他事务的影响(不会看到其他事务的修改) | 受其他事务提交的修改影响(可能看到其他事务修改后的数据) |
数据一致性 | 保证一致性(在事务内看到一致的数据视图) | 可能不一致(当前读会看到其他事务提交的最新数据) |
锁的情况 | 不加锁(不会阻塞其他事务的操作) | 可能会加锁(例如行级锁),并可能引发阻塞 |
事务隔离级别 | 通常在 Repeatable Read 或 Serializable 隔离级别下 | 通常在 Read Committed 或 Serializable 隔离级别下 |
使用的技术 | MVCC(多版本并发控制) | 行级锁(或表级锁) |
总结
- 快照读 主要用于通过事务开始时的数据快照来确保数据一致性,它不被其他事务的修改影响,并且通常不会加锁,适用于高隔离级别(如 Repeatable Read 和 Serializable)。
- 当前读 读取的是数据库中的最新数据,可能受到其他事务的影响,通常会加锁确保数据一致性,适用于 Read Committed 等较低的隔离级别。
前读 (Current Read),既然确保该行数据在事务 T1 执行期间不会被其他事务修改或删除,为什么事务 T1 可能会看到其他事务已经提交的更新数据。不是不允许其他事物修改吗
您提到的 “前读 (Current Read)” 在理解时确实需要澄清一些概念。首先,允许我为您详细解答。
关于前读 (Current Read)
前读 (Current Read) 主要指的是一种事务读取数据的方式,它读取的是数据库中 当前的实际数据,而不是某个事务开始时的数据快照。在这个过程中,事务 T1 会在查询或更新数据时, 可能会加锁 被读取的数据,以确保隔离性。
为什么事务 T1 可能会看到其他事务已提交的数据?
这个问题的关键在于 "加锁" 和 "提交" 的概念。前读和快照读的差异在于 锁的管理 和 数据一致性视图。
-
锁定机制:
- 在 前读 (Current Read) 中,事务 T1 在读取数据时 会加锁 被读取的数据行或数据块,目的是确保在事务 T1 运行期间,其他事务不会修改这些数据。这个过程称为 行级锁 或 共享锁(具体取决于数据库系统和事务操作的类型)。
- 这种锁的目的是阻止 未提交的数据 或 正在修改中的数据 被其他事务读取,确保数据一致性。
-
提交的数据:
-
与隔离级别相关:
- 在 Read Committed 隔离级别下,前读 操作会读取所有 已提交的数据,即使这些数据是其他事务已经提交的。只要数据被提交并且没有被锁定,T1 就可以看到这些已提交的数据。
- 这意味着事务 T1 可以读取其他事务 T2 已经提交的更新数据。但它不能读取正在被事务 T2 修改但还没有提交的数据(避免脏读)。
举个例子来更清晰地说明:
假设有两个事务 T1 和 T2,数据库中有一行数据,表示订单的金额 (amount
),假设当前金额是 100。
-
事务 T1 执行了查询操作,查询这行数据(
amount > 50
),并锁定了该数据行以防止其他事务在 T1 完成之前修改它。 -
事务 T2 开始后,更新了该行数据(将
amount
改为 150),然后 提交。 -
在事务 T1 执行期间,它的锁可能会保证它 在事务 T1 执行过程中不被其他事务修改或删除,但是 T1 执行的查询会看到事务 T2 已经提交的更新,因为这些更新已经成为数据库的当前状态。
重要的几点总结:
- 前读 (Current Read) 确保事务 T1 读取数据时会看到数据库的最新提交数据,但 不允许读取其他事务未提交的数据。
- T1 可能会看到其他事务 T2 提交的更新数据,因为这些更新已经完成并持久化到数据库中了。
- 加锁机制:前读操作可能会在读取数据时加锁,但这并不会影响其他已提交的数据读取,只会阻止其他事务在锁定的记录上进行修改。
- 不会有脏读:因为前读确保只会读取 已提交的数据,从而避免了脏读的问题。
总之,前读确保了事务内部的数据一致性(即每次读取的数据都是数据库当前提交的最新状态),但它允许在事务 T1 执行期间,读取其他事务(如 T2)已经提交的更新数据,而不是阻止其他事务的修改。
undo log redo log binlog区别
在数据库系统中,Undo Log、Redo Log 和 Binlog 都是用于保证数据一致性、持久性和恢复机制的日志文件,但它们的用途和功能有所不同。以下是它们的主要区别:
1. Undo Log(撤销日志)
- 用途:Undo Log 用于实现事务的回滚,保证数据一致性和原子性。
- 作用:当事务执行过程中出现异常或需要回滚时,Undo Log 提供了执行操作前的数据状态,以便将数据库恢复到事务开始前的状态。
- 存储内容:记录了事务执行过程中修改前的数据状态(即“撤销”操作)。
- 作用对象:主要用于事务回滚的操作。
- 实例:如果事务修改了一条记录的值,Undo Log 会记录修改前的值,以便在事务回滚时恢复数据。
2. Redo Log(重做日志)
- 用途:Redo Log 用于在系统崩溃后进行数据恢复,保证事务的持久性。
- 作用:Redo Log 记录了已经提交的事务的操作内容,在数据库系统崩溃之后,可以通过 Redo Log 重新应用已经提交的事务,恢复到事务提交后的最新状态。
- 存储内容:记录了事务已经提交的操作(即“重做”操作)。
- 作用对象:主要用于恢复已提交事务的数据,确保事务的持久性。
- 实例:如果事务修改了一条记录的值并提交,那么 Redo Log 会记录下该操作,在数据库崩溃后,可以通过 Redo Log 来重做这个修改。
3. Binlog(二进制日志)
- 用途:Binlog 用于记录数据库的所有变更事件,并且可以用于数据的备份和复制。
- 作用:Binlog 记录了所有对数据库产生修改的操作(如 INSERT、UPDATE、DELETE),不仅限于事务性操作。Binlog 的主要用途是数据复制和恢复。
- 存储内容:记录了数据库的所有结构变更(包括 DDL)和数据变更(包括 DML)。
- 作用对象:主要用于数据的持久化和复制。
- 实例:Binlog 会记录 INSERT 操作和其修改的数据内容,并且可以用于 MySQL 主从复制,主库的 Binlog 会传送到从库,保证从库的数据与主库一致。
主要区别
特性 | Undo Log | Redo Log | Binlog |
---|---|---|---|
用途 | 事务回滚(原子性) | 事务恢复(持久性) | 数据备份和复制 |
存储内容 | 记录事务前的数据(撤销操作) | 记录已提交事务的操作(重做操作) | 记录所有数据变更(DML、DDL) |
作用对象 | 回滚操作 | 恢复已提交的事务 | 数据备份与复制 |
持久化方式 | 事务期间,通常在内存中 | 存储在磁盘,持久化 | 存储在磁盘,持久化 |
是否可以用于复制 | 否 | 否 | 是 |
是否记录未提交事务 | 记录未提交事务的变化 | 不记录未提交事务 | 不记录未提交事务 |
总结:
- Undo Log 用于实现事务回滚。
- Redo Log 用于事务提交后,系统崩溃时恢复数据。
- Binlog 记录数据库变更,用于备份和复制。
每种日志都有其特定的功能,帮助数据库系统保证事务的ACID特性(原子性、一致性、隔离性、持久性)。