MqSQL中的《快照读》和《当前读》
目录
1、MySQL读取定义
1.1、锁的分类
1.2、快照读与当前读
1.3、使用场景
1.4、区别
2、实现机制
2.1、实现原理
2.2、隔离级别和快照联系
1、隔离级别
2、快照读
2.3、快照何时生成
3、SQL场景实现
3.1、快照读
3.2、当前读
4、锁的细节(与当前读相关)
5、影响 / 并发行为
5.1、快照读:
5.2、当前读:
6、注意事项与常见误区
前言
MySQL读取(主要指 InnoDB 存储引擎)中“快照读(snapshot read,也叫一致性读/consistent read)”和“当前读(current read)”。
如下所示:
这两种读取方式在事务隔离级别、并发控制和数据一致性方面有着本质的区别。主要区别在于是否加锁以及数据一致性。
1、MySQL读取定义
1.1、锁的分类
1、共享锁(S锁):
共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。
如果事务T仅对数据A进行读取,那么会对数据A加上共享锁,之后则其他事务如果要读取数据A的话可以对其继续加共享锁,但是不能加排他锁(也就是无法修改数据)。获准共享锁的事务只能读数据,不能修改数据。
2、排他锁(X锁):
用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
如果事务T对数据A要进行修改,则需要对其添加排它锁,加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
SQL代码示例:
共享锁
select * from table id = 1 lock in share mode;排他锁
select * from table where id = 1 for update;
1.2、快照读与当前读
1、快照读
(Snapshot Read / Consistent Read):基于事务快照(MVCC),读取的是“某个时间点”的已提交版本,不加锁、非阻塞,多个读返回一致的历史版本。
REPEATABLE READ 下(基于事务开始时的快照):同一事务内多次 SELECT 返回相同结果;
READ COMMITTED 下每个语句建立新的快照。
SQL示例如下:
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id = 1; -- 快照读,读取事务开始时的数据-- 事务 B
START TRANSACTION;
UPDATE users SET name = 'Alice' WHERE id = 1; -- 更新数据并提交
COMMIT;-- 事务 A
SELECT * FROM users WHERE id = 1; -- 仍然读取事务开始时的数据(快照读)
COMMIT;
2、当前读
(Current Read):直接读取当前最新数据(buffer pool/磁盘)并且读取之后还要保证其他并发事务不能修改当前记录,对读取的记录加锁。
当前读:select…lock in share mode,select…for update。
当前读:update,delete,insert。
SQL代码示例如下:
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 当前读,获取最新数据并加锁-- 事务 B
START TRANSACTION;
UPDATE users SET name = 'Alice' WHERE id = 1; -- 被阻塞,等待事务 A 释放锁-- 事务 A
COMMIT; -- 提交后,事务 B 的更新操作继续执行
1.3、使用场景
1、当前读适用场景
- 需要获取最新数据的实时查询
- 需要保证数据一致性的金融交易
- 先读后写的业务逻辑
- 需要防止并发修改的场景
- 只读查询(报表、数据分析)
- 对实时性要求不高的查询
- 大查询,不希望阻塞其他操作
- 需要高并发的读场景
1.4、区别
如下所示:
2、实现机制
2.1、实现原理
1、快照读:通过MVCC+undolog实现;
如果最新版本不是当前事务可见,InnoDB 会从 undo log 找到符合快照时间点的版本(所以快照读可能读取 undo 中的旧版本)。不会设置锁。
不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。
所以快照读都是去读取undolog中链首的最新的旧记录。
更多mvcc和undolog的介绍,可参考:探讨LRU和MVCC在场景的实践-CSDN博客文章浏览阅读827次,点赞33次,收藏29次。MVCC 可以有效防止脏读,因为它提供一致的快照显示已提交修改的数据。它通过版本控制和快照隔离处理多用户并发访问,从而提高数据库的高并发性能。1、彻底拿下InnoDB的MVCC快照机制_innodb 的 快照读 如何提取快照-CSDN博客。https://dyclt.blog.csdn.net/article/details/147588118?spm=1011.2415.3001.5331
2、当前读:通过共享锁+排他锁+Next-Key Lock实现;
当前读通过next-key锁(记录锁+间隙锁)来实现:行锁(Record Lock):锁定索引记录
间隙锁(Gap Lock):锁定索引记录之间的间隙
next-key锁:行锁+间隙锁,锁定一个范围并锁定记录本身
例如执行DELETE FROM T WHERE age = 7;时,MySQL会在age索引上加锁(4,10)区间,防止其他事务
插入age=7的记录,从而避免幻读问题。
如下所示:
- 每次对行数据进行读取的时候,加共享锁。此时就不允许修改,但是允许其他事务读取,所以每次都可以读到最新的数据。
- 每次对行数据进行修改的时候,加排他锁,不允许其他事务读取和修改。这种情况下其他事务读取的数据也一定是最新的数据。
- 每次对范围行数据进行读取的时候,对这个范围加一个范围共享锁。
- 每次对范围行数据进行修改的时候,读这个范围加一个范围排它锁。
- 基于上述锁机制,实现当前读,确保每次读取的都是最新的数据。
⚠️注意:
next-key包括两部分:行锁和间隙锁。行锁是加在索引上的锁(而非数据行),间隙锁是加在索引之间的。
2.2、隔离级别和快照联系
1、隔离级别
1、读未提交(READ UNCOMMITTED)
快照读:不存在,总是读取最新数据(包括未提交的)。
当前读:读取最新已提交数据并加锁。
2、读已提交(READ COMMITTED)
快照读:每次SELECT都会生成新的ReadView,读取最新已提交数据。
当前读:读取最新已提交数据并加锁。
3、可重复读(REPEATABLE READ)
快照读:事务第一次SELECT时生成ReadView,后续复用,保证读取一致性。
当前读:读取最新已提交数据并加锁。
4、串行化(SERIALIZABLE)
快照读:退化为当前读(加共享锁)。
当前读:读取最新已提交数据并加锁。
2、快照读
REPEATABLE READ(默认):
事务开始时建立一次快照,整个事务内普通 SELECT 都基于这个快照(repeatable)。
READ COMMITTED:
每个语句单独建立快照(statement-level),因此同一事务内不同语句可能看到不同已提交数据(避免脏读,但允许不可重复读)。
Undo log 提供历史版本;如果旧版本被 purge 掉,读取老快照可能失败。
2.3、快照何时生成
1、在读未提交隔离级别下,快照是什么时候生成的?
没有快照,因为不需要,怎么读都读到最新的,不管是否提交。
2、在读已提交隔离级别下,快照是什么时候生成的?
SQL语句开始执行的时候。
3、在可重复读隔离级别下,快照是什么时候生成的?
事务开始的时候(可能会有很多条select SQL语句执行,快照生命周期是到事务结束的时候)
4、在串行化隔离级别下,快照是什么时候生成的?
“写”会加“写锁”,“读”会加“读锁”,读的的数据都是当前最新的数据(没有快照,当前读)
3、SQL场景实现
3.1、快照读
1、 快照读不会看到后续提交(REPEATABLE READ):事务级快照读
事务 T1:
BEGIN; SELECT value FROM t WHERE id=1;
-- 假设值为 A (从快照读)(此时 T1 未提交,继续执行)
事务 T2:
BEGIN; UPDATE t SET value='B' WHERE id=1; COMMIT;
回到 T1:在 REPEATABLE READ 下仍然看到 A(快照不变)
SELECT value FROM t WHERE id=1;
COMMIT;
2、 READ COMMITTED 下快照行为(每语句快照):语句级快照读
事务 T1:
BEGIN; SELECT value FROM t WHERE id=1;
-- 看到 A
事务 T2:
BEGIN; UPDATE t SET value='B' WHERE id=1; COMMIT;
T1 再次执行:
SELECT value FROM t WHERE id=1;
-- 在 READ COMMITTED 下可能看到 B(新的语句快照)
3.2、当前读
1、当前读会立即看到并与写冲突/加锁:
事务 T1:
BEGIN; SELECT value FROM t WHERE id=1 FOR UPDATE;
-- 当前读并对该行加排它锁(此时 T1 锁住该行)
事务 T2(并发执行):
BEGIN; UPDATE t SET value='B' WHERE id=1; COMMIT;
-- 将在 T1 提交前被阻塞,直到 T1 提交或回滚
4、锁的细节(与当前读相关)
更多关于mysql的锁介绍:MySQL的事务和锁机制的详细介绍_mysql锁机制与事物-CSDN博客文章浏览阅读1.3k次,点赞47次,收藏10次。MySQL事务与锁机制详解 摘要: 本文详细解析MySQL的事务特性和锁机制。首先介绍事务的ACID特性(原子性、一致性、隔离性、持久性)和四种隔离级别(读未提交、读提交、可重复读、串行化),重点分析多线程并发事务可能产生的脏读、不可重复读和幻读问题。其次深入探讨MVCC多版本并发控制原理及其解决并发问题的方式。然后系统讲解MySQL锁的分类,包括独占锁、共享锁及其兼容性,以及InnoDB引擎特有的记录锁、间隙锁、临键锁等实现细节。最后提供锁监控方法和优化建议,包括缩短事务长度、合理设计索引等,并比较不同隔_mysql锁机制与事物https://dyclt.blog.csdn.net/article/details/149141972?spm=1011.2415.3001.5331
1、SELECT ... FOR UPDATE:
对选中的记录加排它锁(record lock),在 REPEATABLE READ 下通常是 next‑key lock(record+gap),能防幻读;在 READ COMMITTED 下可能只加 record lock(实现差异)。
2、SELECT ... LOCK IN SHARE MODE:
共享锁,允许并发读取但阻止写入。
UPDATE/DELETE 语句本质是当前读(读取并锁定匹配行),而非快照读。
5、影响 / 并发行为
5.1、快照读:
不阻塞其他事务的写操作(不会加锁)。
不被子后续提交所影响(在 REPEATABLE READ 内)不会阻塞写者,但写者提交不会让已存在事务的快照变更。
5.2、当前读:
会加锁,可能阻塞其他事务(或被其他事务阻塞),用于实现悲观锁定和防止幻读/并发冲突。SELECT ... FOR UPDATE 会锁定读取到的记录(并可能使用 next-key lock 以避免幻读,取决于隔离级别和索引类型)。
6、注意事项与常见误区
1、 “快照读不会造成任何索引访问” 不准确:
快照读也会走索引来定位行,但读取逻辑是基于版本可见性(从 undo 中回溯),它不设置锁。
2、长事务会保留较多 undo 数据:
影响 undo log 大小并增加 purge 压力;长时间的快照读(长事务)会阻止旧版本被清理。
3、不稳定结果
与快照/当前读无直接关系,但与隔离级别和锁有关,使用 skip/limit 的分页在并发写入下可能产生。
总结
普通 SELECT(无 FOR UPDATE/LOCK)是快照读(基于 MVCC,非锁定),在 REPEATABLE READ 下为事务级快照,同一事务内多次 SELECT 返回一致结果;READ COMMITTED 下每语句建立快照。
SELECT ... FOR UPDATE / UPDATE / DELETE 等是当前读,会读取最新数据并加锁,可能阻塞其他事务写入。
选择隔离级别与是否加锁,需要在“数据一致性需求(可重复读/防止幻读)”与“并发性能(锁竞争/阻塞)”之间权衡。
参考文章:
1、Mysql中的快照读和当前读_mysql快照读-CSDN博客文章浏览阅读1k次,点赞9次,收藏10次。本文介绍了MySQL的两种读取模式:当前读和快照读。当前读通过共享锁、排他锁和Next - Key Lock实现,每次读取最新数据;快照读通过MVCC和undolog实现,读写不冲突。还阐述了相关知识,如undolog、共享锁和排他锁,并分析了不同隔离级别下快照读的差异。https://blog.csdn.net/aberwang9/article/details/135185331?ops_request_misc=&request_id=&biz_id=102&utm_term=mysql%E7%9A%84%E5%BD%93%E5%89%8D%E8%AF%BB%E5%92%8C%E5%BF%AB%E7%85%A7%E8%AF%BB&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-135185331.142^v102^control&spm=1018.2226.3001.4187
2、【MYSQL】当前读和快照读_mysql 当前读-CSDN博客文章浏览阅读821次,点赞3次,收藏9次。复习下隔离级别:1、读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。2、读提交:一个事务提交之后,它做的变更会被其他事务看到3、可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。未提交的数据对其他事务不可见4、串行化:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。_mysql 当前读https://blog.csdn.net/xiazi0721/article/details/141175805?ops_request_misc=&request_id=&biz_id=102&utm_term=mysql%E7%9A%84%E5%BD%93%E5%89%8D%E8%AF%BB%E5%92%8C%E5%BF%AB%E7%85%A7%E8%AF%BB&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-141175805.142^v102^control&spm=1018.2226.3001.4187