详解Mysql的快照读和当前读区别
在 MySQL InnoDB 中,读取数据的方式主要分为快照读(Snapshot Read)和当前读(Current Read)两种,它们在并发控制、隔离级别实现中扮演着不同角色。
1. 基本概念对比
特性 | 快照读 (Snapshot Read) | 当前读 (Current Read) |
---|---|---|
定义 | 读取数据的历史版本 | 读取数据的最新提交版本 |
实现 | 基于 MVCC 机制 | 基于锁机制 |
SQL 类型 | 普通 SELECT 语句 | SELECT...FOR UPDATE, SELECT...LOCK IN SHARE MODE, INSERT, UPDATE, DELETE |
隔离级别 | READ COMMITTED 和 REPEATABLE READ 下生效 | 所有隔离级别 |
并发性 | 读写不冲突 | 读写可能冲突 |
2. 快照读详解
2.1 工作机制
-
通过 ReadView 访问 undo log 中的合适版本
-
不需要加锁,读取的是某个时间点的数据快照
-
受事务隔离级别影响:
-
READ COMMITTED:每次读取都生成新 ReadView
-
REPEATABLE READ:第一次读取时生成 ReadView
-
2.2 示例
-- 普通SELECT语句就是快照读
SELECT * FROM accounts WHERE id = 1;
2.3 特点
-
无锁读取,性能高
-
读取的数据可能不是最新的
-
解决读写冲突,实现非阻塞读
3. 当前读详解
3.1 工作机制
-
读取记录的最新提交版本
-
会对记录加锁,防止其他事务修改
-
保证读取的数据是最新的
3.2 当前读的SQL类型
-- 1. 加排他锁(X锁)的查询
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;-- 2. 加共享锁(S锁)的查询
SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE;-- 3. 数据修改操作(隐式当前读)
UPDATE accounts SET balance = 100 WHERE id = 1;
DELETE FROM accounts WHERE id = 1;
INSERT INTO accounts VALUES (...);
3.3 特点
-
需要加锁,可能阻塞其他操作
-
读取最新数据,保证数据一致性
-
解决写写冲突
4. 核心区别
4.1 数据版本
-
快照读:读取历史版本(通过undo log)
-
当前读:读取最新提交版本
4.2 并发控制
-
快照读:通过MVCC实现无锁并发
-
当前读:通过锁机制实现并发控制
4.3 使用场景
-
快照读:适用于普通查询,不要求数据绝对最新
-
当前读:适用于需要保证数据一致性的场景,如账户交易
5.实际应用示例
场景:银行账户余额查询与转账
-- 事务1:查询余额(快照读)
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 快照读,返回1000-- 事务2:转账(当前读)
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 当前读,加X锁
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;-- 事务1再次查询
SELECT balance FROM accounts WHERE id = 1; -- 快照读,仍返回1000(REPEATABLE READ)
COMMIT;
6. 常见问题
Q:为什么UPDATE操作要先当前读?
A:因为UPDATE需要基于最新数据进行修改,必须先获取最新值并加锁防止其他事务同时修改。
Q:快照读是否完全不加锁?
A:在特殊情况下(如查询遇到正在被更新的数据),快照读可能需要等待锁释放,但这是例外情况。
Q:如何强制使用当前读?
A:在普通SELECT语句中添加FOR UPDATE或LOCK IN SHARE MODE后缀。