可重复复读
在MySQL中,“可重复读”(Repeatable Read)是四种隔离级别之一,另外三种是读未提交(Read Uncommitted)、读已提交(Read Committed)和序列化(Serializable)。隔离级别决定了一个事务中的操作与其他并发事务中的操作之间的隔离程度。
以下是对“可重复读”隔离级别的详细解释:
定义和行为
在可重复读隔离级别下,一个事务在启动时,会建立一个一致性视图(Snapshot)。事务内的所有查询都在这个一致性视图下执行,这意味着如果事务中途执行了多次相同的查询,结果将是相同的,不受其他事务的影响。
解决的问题
- 脏读(Dirty Read):读到了其他未提交事务的数据。
- 不可重复读(Non-repeatable Read):同一事务内多次读取同一数据时,结果不同。
- 幻读(Phantom Read):同一事务内两次查询,第二次查询发现了第一次查询不存在的行。
可重复读隔离级别解决了脏读和不可重复读的问题,但不能完全解决幻读的问题。InnoDB通过一种称为Next-Key Locking的机制来部分解决幻读的问题。
在可重复读隔离级别下,事务在开始时获取一个一致性视图,确保在该事务的执行期间,即使有其他事务更新了数据,该事务读取到的数据仍然是一致的,不会发生变化。这就解决了不可重复读的问题。然而,针对幻读问题,可重复读级别依赖于InnoDB的Next-Key Locking机制来减轻,但在某些情况下可能仍会遇到幻读。要完全避免幻读,可以使用更高的隔离级别——序列化(Serializable)。
一致性
一致性保证事务在执行前后,数据库状态是合法的,即数据库必须从一个一致的状态转换到另一个一致的状态。一个一致的状态是指所有数据库约束(如主键约束、外键约束、唯一性约束、业务逻辑约束等)都必须被满足。
一致性的保证
-
事务的开始和结束:
- 在事务开始之前,数据库处于一致状态。
- 在事务执行过程中,数据库可能处于不一致状态。
- 在事务结束之后(提交或回滚),数据库必须恢复到一致状态。
-
约束检查:
- 主键约束、外键约束、唯一性约束等数据库约束在事务提交时必须被满足。
- 业务逻辑约束,例如账户余额不能为负等,在事务结束时也必须被满足。
示例
考虑一个简单的银行转账系统,涉及两个账户的余额转移。在这种情况下,必须保证转账事务的一致性。
账户表结构
CREATE TABLE account (
id INT PRIMARY KEY,
balance DECIMAL(10, 2) NOT NULL
);
转账事务的实现
假设从账户A转账100元到账户B:
START TRANSACTION;
-- 从账户A扣除100元
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 向账户B增加100元
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 检查余额是否符合业务逻辑约束(如不能为负数)
SELECT balance FROM account WHERE id = 1;
-- 提交事务
COMMIT;
在这个事务中:
- 开始事务:在事务开始时,数据库处于一致状态。
- 更新账户A的余额:扣除100元。
- 更新账户B的余额:增加100元。
- 检查业务约束:确保账户A的余额在扣除100元后不会变为负数。
- 提交事务:如果所有操作成功且业务逻辑约束满足,提交事务使数据库从一个一致状态转换到另一个一致状态。
实现一致性的机制
- 原子性:确保事务中的所有操作要么全部执行,要么全部不执行。通过
START TRANSACTION
和COMMIT
或ROLLBACK
语句实现。 - 隔离性:确保事务的中间状态对其他事务不可见,从而避免脏读、不可重复读和幻读等问题。通过设置适当的隔离级别实现,如
REPEATABLE READ
或SERIALIZABLE
。 - 约束机制:数据库系统自动检查和维护的约束,如主键约束、外键约束和唯一性约束。
- 业务逻辑检查:在事务中显式执行业务逻辑检查,如账户余额不能为负等。
- 技术上,通过AID保证C
多版本并发控制 (MVCC)
多版本并发控制(Multi-Version Concurrency Control,简称MVCC)是一种用于数据库管理系统的并发控制方法。它通过保存数据的多个版本来实现高效的并发操作,使得读操作不会阻塞写操作,写操作也不会阻塞读操作。MVCC广泛应用于许多现代关系数据库管理系统(RDBMS),包括MySQL的InnoDB存储引擎和PostgreSQL。
MVCC的基本原理
MVCC的核心思想是通过维护每条记录的多个版本,使得不同事务可以看到同一数据的不同版本,从而实现并发控制。具体来说,MVCC通过以下方式来实现这一点:
-
版本控制:
- 每条记录包含多个版本,每个版本对应于某个事务对该记录的操作。
- 每个版本记录了其创建和删除的事务ID(Transaction ID,简称
TXID
)。
-
读取一致性视图:
- 每个事务开始时,会创建一个一致性视图(Snapshot),该视图包含了当前数据库状态的一个快照。
- 事务只能看到在其开始之前已经提交的事务所做的修改,即事务在执行过程中所看到的数据是一致的,不受其他并发事务影响。
-
隐藏和删除版本:
- 在更新和删除操作时,新版本的记录会被创建,旧版本仍然保留以供并发事务访问。
- 当没有事务再需要访问旧版本时,旧版本会被垃圾回收机制清理。
MVCC在InnoDB中的实现
MySQL的InnoDB存储引擎通过MVCC来实现并发控制。以下是InnoDB中MVCC的实现细节:
-
隐藏列:
- InnoDB在每条记录后面隐含地添加了两个列:
trx_id
和roll_pointer
。 trx_id
:记录最后一次修改该行的事务ID。roll_pointer
:指向Undo日志,用于存储数据的前一版本。
- InnoDB在每条记录后面隐含地添加了两个列:
-
一致性视图:
- InnoDB通过维护一个全局的系统事务ID(
system version number
)来跟踪每个事务的开始和结束。 - 一致性视图通过这个系统事务ID生成,用于确定哪些版本对当前事务可见。
- InnoDB通过维护一个全局的系统事务ID(
-
可见性规则:
- 当前事务只能看到在其开始之前已经提交的事务所做的修改。
- 当前事务自身的修改对其自身可见,但对其他事务不可见。
-
Undo日志:
- Undo日志用于存储数据的旧版本,以便在需要时能够回滚到之前的状态。
- Undo日志与数据记录通过
roll_pointer
链接。
MVCC的优点
- 高并发性能:读操作不会阻塞写操作,写操作也不会阻塞读操作,从而提高了并发性能。
- 一致性视图:每个事务都有一个一致性视图,确保了事务在执行过程中读取到的数据是一致的。
- 减少锁争用:通过版本控制,减少了事务之间的锁争用,从而提高了系统的吞吐量。
MVCC的缺点
- 存储开销:每条记录的多个版本以及Undo日志会增加存储开销。
- 复杂的垃圾回收:需要有效的垃圾回收机制来清理不再需要的旧版本,以避免存储空间浪费。
- 读写放大:维护多个版本和Undo日志可能会导致读写操作的开销增加。
快照
MySQL中的快照功能是多版本并发控制(MVCC)的核心组件之一。它确保了事务在并发执行时能够看到一致的数据库视图,从而实现高效的并发控制。以下是对MySQL快照功能的详细解释。
快照的概念
在MySQL中,快照是一种一致性视图(Snapshot),它在事务开始时创建,捕获了事务开始时数据库的状态。这个快照使得事务在其整个生命周期内都能够看到数据库在事务开始时的状态,不受其他并发事务的影响。
快照的实现
MySQL通过MVCC机制实现快照一致性。具体来说,InnoDB存储引擎使用以下技术来实现快照:
-
系统版本号(System Version Number,简称
SVN
):每当一个新事务开始时,InnoDB会分配一个唯一的递增事务ID(transaction ID
,简称TXID
)。这个事务ID用于标识事务并排序事务的开始顺序。 -
一致性视图(Consistent Read View):在事务开始时,InnoDB会创建一个一致性视图,这个视图记录了当前所有活跃事务的ID。这些信息存储在一个称为
Read View
的数据结构中。
快照的工作原理
-
事务开始:当一个事务开始时,InnoDB会创建一个一致性视图,捕获当前所有已提交和未提交事务的状态。
-
读操作:在事务内部的所有读操作都会基于这个一致性视图。一致性视图确保读操作只会看到在事务开始之前已经提交的数据。对于在事务开始之后提交的更改,读操作是不可见的。
-
写操作:写操作(INSERT、UPDATE、DELETE)在执行时会生成新的版本,旧版本仍然保留,以便其他并发事务继续访问旧版本的数据。
快照隔离级别
MySQL支持不同的事务隔离级别,不同的隔离级别对快照的一致性视图有不同的要求:
-
读未提交(Read Uncommitted):事务可以看到其他事务未提交的更改。这种隔离级别不使用快照,因此可能会看到脏读(Dirty Read)。
-
读已提交(Read Committed):事务只能看到其他事务已经提交的更改。每个查询都会创建一个新的快照,因此每个查询都基于最新的已提交数据。
-
可重复读(Repeatable Read):事务在其整个生命周期内都使用同一个快照。这意味着在同一个事务内的所有查询都会看到一致的数据,不会出现不可重复读的问题。
-
串行化(Serializable):最高的隔离级别,事务按顺序执行,确保没有并发操作。实现方式通常涉及锁定机制,而不是快照。
快照的优点
- 高效并发控制:通过使用快照,读操作不会阻塞写操作,写操作也不会阻塞读操作,从而提高了系统的并发性能。
- 一致性保证:快照确保了事务在其生命周期内看到的一致性数据,避免了脏读、不可重复读和幻读的问题(取决于隔离级别)。
- 无锁读取:快照使得读操作不需要加锁,从而减少了锁争用,提高了系统吞吐量。
Read View读视图
在MySQL的InnoDB存储引擎中,Read View
是实现多版本并发控制(MVCC)关键的一部分。Read View
确保了事务在执行过程中能够看到一致性的数据视图,即在事务开始时的数据快照。以下是对Read View
的详细解释。
Read View
的概念
Read View
是事务在执行过程中维护的一致性视图,它记录了事务开始时系统中活跃的事务情况。这个视图确保了事务在执行读操作时,只能看到在事务开始之前已经提交的修改,从而保证数据的一致性。
Read View
的组成
一个Read View
主要包括以下几个部分:
m_ids
:表示当前系统中所有未提交事务的ID列表。这个列表用于确定哪些事务的修改对当前事务不可见。up_limit_id
:表示在创建Read View
时系统中下一个将要分配的事务ID。这个ID是递增的,因此它表示了事务开始时的一个时间点。low_limit_id
:表示系统中已提交的最大事务ID。这个ID用于快速判断某些事务是否已提交。creator_trx_id
:表示创建Read View
的事务ID。
Read View
的工作原理
在创建Read View
时,InnoDB会记录当前系统中所有活跃事务的ID,并将这些ID保存到Read View
中。然后,事务在执行读操作时,根据Read View
中的信息来判断哪些版本的数据对当前事务可见。
具体来说,判断一个数据版本是否可见的规则如下:
- 如果数据版本的创建事务ID小于
low_limit_id
:数据版本在Read View
创建之前已提交,因此对当前事务可见。 - 如果数据版本的创建事务ID大于等于
up_limit_id
:数据版本在Read View
创建之后创建,因此对当前事务不可见。 - 如果数据版本的创建事务ID在
low_limit_id
和up_limit_id
之间:- 如果数据版本的创建事务ID在
m_ids
列表中:数据版本对应的事务未提交,因此对当前事务不可见。 - 如果数据版本的创建事务ID不在
m_ids
列表中:数据版本对应的事务已提交,因此对当前事务可见。
- 如果数据版本的创建事务ID在
RR和RC的区别
在MySQL的InnoDB存储引擎中,Read View
的调度在不同的事务隔离级别下表现有所不同。具体来说,在“可重复读”(Repeatable Read,RR)和“读已提交”(Read Committed,RC)隔离级别下,Read View
的创建和使用方式存在显著差异。
事务隔离级别概述
-
可重复读(Repeatable Read,RR):
- 保证在同一事务中多次读取同一数据的结果是一致的,即使其他事务进行了更新操作。
- 可以防止脏读(Dirty Read)和不可重复读(Non-Repeatable Read)。
- 通过Next-Key Locking机制部分防止幻读(Phantom Read)。
-
读已提交(Read Committed,RC):
- 每次读取数据时只能看到已经提交的事务所做的更改。
- 可以防止脏读,但不能防止不可重复读和幻读。
Read View
在RR和RC中的区别
可重复读(RR)
在可重复读隔离级别下,Read View
在事务开始时创建,并在整个事务期间保持不变。这意味着事务在其生命周期内始终看到的是事务开始时的一致性视图,无论其他事务在其执行期间提交了什么更改,当前事务看到的数据都不会变化。
Read View
的创建:在事务开始时创建。Read View
的生效时间:整个事务期间保持不变。- 事务读取行为:所有读取操作使用相同的
Read View
,确保多次读取相同数据的结果一致。
读已提交(RC)
在读已提交隔离级别下,Read View
在每次读取操作时创建。这意味着每次读取操作都会看到当前已提交的最新数据。因此,不同的读取操作可能会看到不同的结果,因为在读取操作之间可能有其他事务提交了更改。
Read View
的创建:在每次读取操作时创建。Read View
的生效时间:每次读取操作时创建新的Read View
。- 事务读取行为:每次读取操作都会使用最新的
Read View
,看到当前已提交的数据。