[小技巧43]MySQL MVCC 深度解析:快照读 vs 当前读

一、快照读(Snapshot Read)与当前读(Current Read)

1.1 快照读(Snapshot Read)

快照读是指事务在执行 SELECT 查询时,读取的是事务启动时的数据快照。它不会阻塞其他写操作,也不会被其他写操作阻塞。这是因为快照读依赖于 MVCC 机制,通过读取历史版本来实现一致性视图。

特点:

  • 无锁:快照读不需要加任何锁。
  • 一致性视图:读取的是事务开始时的数据快照。
  • 适用场景:适用于大多数只读查询场景。

示例:

sql 复制代码
SELECT * FROM users WHERE id = 1;

1.2 当前读(Current Read)

当前读是指事务在执行某些特定查询时,需要获取最新的数据视图,因此会加锁以确保数据的一致性。

常见的当前读操作包括 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE

特点:

  • 加锁:当前读需要加锁,防止其他事务修改数据。
  • 最新数据:读取的是当前时刻的最新数据。
  • 适用场景:适用于需要保证数据一致性的写操作或高并发场景下的读操作。

示例:

sql 复制代码
SELECT * FROM users WHERE id = 1 FOR UPDATE;

核心区别概览

特性 SELECT ... FOR UPDATE SELECT ... LOCK IN SHARE MODE
中文名 排他锁(写锁) 共享锁(读锁)
锁类型 X 锁(Exclusive Lock) S 锁(Shared Lock)
是否阻塞其他事务读? ❌ 不阻塞普通 SELECT(快照读) ✅ 阻塞其他 FOR UPDATE / LOCK IN SHARE MODE ❌ 不阻塞普通 SELECT ❌ 不阻塞其他 LOCK IN SHARE MODE ✅ 阻塞 FOR UPDATE
是否阻塞其他事务写? ✅ 阻塞所有写操作(UPDATE/DELETE) ✅ 阻塞写操作(因为写需要 X 锁)
典型用途 "我要修改这条数据,请别人别动" "我要确保这条数据不被改,但允许多人同时读"
隔离级别要求 所有隔离级别均生效 所有隔离级别均生效

一句话总结

  • FOR UPDATE = "我独占,谁也别碰" (连"读锁"都不让别人加)
  • LOCK IN SHARE MODE = "你可以读,但不能改"(允许别人加同样的"读锁")

锁机制详解

1. SELECT ... FOR UPDATE(排他锁 / X 锁)
  • 对查询命中的每一行X 锁(排他锁)
  • X 锁特性
    • 与其他任何锁(S 或 X)互斥
    • 同一时刻只能有一个事务持有某行的 X 锁
  • 效果
    • 其他事务无法对该行执行 UPDATEDELETEFOR UPDATELOCK IN SHARE MODE
    • 普通 SELECT(快照读)仍可执行(因为不走当前读,不受锁影响)

示例:

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 加 X 锁
-- 此时事务 B 执行以下语句会阻塞:
--   UPDATE accounts SET balance = 100 WHERE id = 1;
--   SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
--   SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE;
-- 但以下语句**不会阻塞**:
--   SELECT * FROM accounts WHERE id = 1; -- 快照读,走 MVCC
2. SELECT ... LOCK IN SHARE MODE(共享锁 / S 锁)
  • 对查询命中的每一行S 锁(共享锁)
  • S 锁特性
    • 与 S 锁兼容(多个事务可同时持有)
    • 与 X 锁互斥
  • 效果
    • 其他事务可以执行 LOCK IN SHARE MODE(并发读)
    • 但无法执行 FOR UPDATEUPDATEDELETE(因为这些需要 X 锁)

示例:

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM products WHERE id = 100 LOCK IN SHARE MODE; -- 加 S 锁

-- 事务 B 可以:
SELECT * FROM products WHERE id = 100 LOCK IN SHARE MODE; -- 成功(S-S 兼容)

-- 事务 B 不能:
SELECT * FROM products WHERE id = 100 FOR UPDATE;        -- 阻塞(S-X 冲突)
UPDATE products SET price = 99 WHERE id = 100;           -- 阻塞(需要 X 锁)

关键注意事项

  1. 两者都属于"当前读",会绕过 MVCC
  • 它们读取的是最新已提交的数据(不是快照)
  • 即使在 REPEATABLE READ 隔离级别下,也会看到其他事务已提交的最新值
  1. 锁的范围取决于 WHERE 条件和索引
  • 如果 WHERE 条件能命中索引 → 只锁命中的行
  • 如果全表扫描 → 锁住所有扫描过的行(甚至间隙锁!)
  • REPEATABLE READ 下,还会加 Next-Key Lock(行锁 + 间隙锁) 防止幻读

示例(RR 隔离级别):

sql 复制代码
-- 假设 id 是主键
SELECT * FROM t WHERE id = 5 FOR UPDATE; 
-- 只锁 id=5 这一行

-- 假设 name 无索引
SELECT * FROM t WHERE name = 'Alice' FOR UPDATE;
-- 可能锁住整个表(或大量无关行)!

MySQL 8.0+ 已废弃 LOCK IN SHARE MODE

  • 官方推荐使用 FOR SHARE 替代(语法更清晰):

    sql 复制代码
    SELECT * FROM t WHERE id = 1 FOR SHARE; -- 等价于 LOCK IN SHARE MODE
  • 但功能完全一致,旧语法仍支持。

使用场景对比

SELECT ... FOR UPDATE 适用场景

  • 资金转账 :确保账户余额在读取后不被他人修改

    sql 复制代码
    BEGIN;
    SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE;
    -- 检查余额足够
    UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
    COMMIT;
  • 库存扣减:防止超卖

  • 状态机更新:如订单状态变更

SELECT ... LOCK IN SHARE MODE 适用场景

  • 报表生成 :确保统计期间数据不被修改

    sql 复制代码
    BEGIN;
    SELECT SUM(amount) FROM orders 
    WHERE status = 'paid' LOCK IN SHARE MODE;
    -- 生成报表(其他人可读,但不能改订单状态)
    COMMIT;
  • 父子数据一致性校验:如检查订单与商品是否存在

但注意 :由于 S 锁容易引发死锁(多个事务互相持有 S 锁,再申请 X 锁),现代应用更倾向于用 FOR UPDATE + 业务逻辑优化 ,而非 LOCK IN SHARE MODE

死锁风险对比

场景 FOR UPDATE LOCK IN SHARE MODE
死锁概率 中(典型:A→B, B→A) (S 锁 + 后续 X 锁易形成环)
典型案例 两个事务交叉更新两行 事务 A 持 S 锁想升级 X 锁,事务 B 也在等

建议 :除非明确需要"多人并发读+防写",否则优先使用 FOR UPDATE

总结:如何选择?

你的需求 推荐语句
"我要读完就改,别让别人干扰" SELECT ... FOR UPDATE
"我只需要确保数据不被改,允许多人同时读" ⚠️ SELECT ... FOR SHARE(谨慎使用)
"我只是查一下,不需要锁" ✅ 普通 SELECT(走 MVCC 快照读)

终极建议

  • 90% 的场景用 FOR UPDATE 足够
  • 尽量避免 LOCK IN SHARE MODE,除非你清楚其并发模型
  • 务必使用索引,避免意外锁表
  • 事务尽量短,减少锁持有时间

1.3 快照读 vs 当前读:对比表

特性 快照读(Snapshot Read) 当前读(Current Read)
加锁
数据一致性 事务启动时的快照 当前时刻的最新数据
并发性能 中等
常见操作 SELECT SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE

二、MVCC 仅适用于快照读,不适用于当前读

2.1 MVCC 的作用边界

MVCC 主要应用于快照读 ,通过为每个事务分配一个唯一的事务 ID(DB_TRX_ID),并维护多个版本的历史数据,使得不同事务可以读取到不同的数据版本。然而,MVCC 并不适用于当前读,因为当前读需要获取最新的数据视图,这通常涉及到加锁操作。

关键点:

  • 快照读:使用 MVCC 提供一致性视图。
  • 当前读:绕过 MVCC,直接访问最新数据并加锁。

三、总结

MVCC 是 MySQL InnoDB 实现高并发读写的基石,通过快照读提供了无锁的一致性视图,极大地提升了系统的并发性能。

然而,MVCC 并不适用于所有类型的读操作,特别是那些需要获取最新数据的当前读操作。

关键知识点:

  • 快照读:无锁,读取事务启动时的数据快照。
  • 当前读:加锁,读取当前时刻的最新数据。
  • MVCC:适用于快照读,不适用于当前读。
  • Undo Log:记录历史版本,支持多版本存储和事务回滚。

相关命令与工具

  1. 查看活跃事务

    sql 复制代码
    SELECT trx_id, trx_state, trx_started 
    FROM information_schema.innodb_trx 
    ORDER BY trx_started DESC;
  2. 查看锁定信息(MySQL 8.0+):

    sql 复制代码
    SELECT * FROM performance_schema.data_locks;
相关推荐
使者大牙3 小时前
【单点知识】 Python装饰器介绍
开发语言·数据库·python
数智工坊3 小时前
【操作系统-文件管理】
数据结构·数据库
oioihoii4 小时前
Oracle迁移KingbaseES实战
数据库·oracle
wniuniu_4 小时前
增加依据。。
服务器·网络·数据库
爱敲代码的小鱼4 小时前
事务核心概念与隔离级别解析
java·开发语言·数据库
Mr.徐大人ゞ4 小时前
6.用户及权限管理
数据库·postgresql
赵渝强老师4 小时前
【赵渝强老师】Oracle多租户容器数据库
数据库·oracle
IT技术分享社区4 小时前
GTID 结构升级 + JSON 视图强化,MySQL 9.6 创新版带来哪些性能提升?
数据库·程序员
阿杰 AJie4 小时前
MySQL 聚合函数
android·数据库·mysql