当前位置: 首页 > news >正文

InnoDB一致性读与锁定读全解析

以下内容是 MySQL 官方文档中关于 InnoDB 存储引擎的“一致性非锁定读(Consistent Nonlocking Reads)”和“锁定读(Locking Reads)” 的详细说明,主要涉及事务隔离级别、MVCC(多版本并发控制)、快照读、当前读、锁机制等核心数据库知识。

下面我将用通俗易懂的语言,结合例子,帮你系统地理解这些概念。


🌟 一、核心目标:在高并发下保证数据一致性和性能

InnoDB 需要在多个事务同时操作数据时,做到:

  • 数据不冲突(一致性)
  • 性能不下降(并发性)

为此,它采用了两种主要机制:

  1. MVCC(Multi-Version Concurrency Control) → 实现“一致性非锁定读”
  2. 行级锁 + 锁定读语句 → 实现“精确控制并发修改”

🔹 1. 什么是 Consistent Nonlocking Read(一致性非锁定读)?

✅ 定义:

一致性读 = 你的 SELECT 查询看到的是数据库在过去某个时间点的快照,而不是实时状态。

这就像给数据库拍了一张“照片”,你所有的查询都基于这张照片来看,不管别人怎么改,你看的都是这张照片里的内容。

🧠 关键点:

概念说明
不加锁SELECT 不会阻塞其他事务写入,也不会被别人阻塞。
基于快照看到的是事务开始或第一次读时的“历史版本”。
多版本控制(MVCC)老版本的数据通过 undo log 保存,可以重建出来。

📌 隔离级别的影响

(1)默认:REPEATABLE READ
  • 整个事务中所有 SELECT 都使用同一个快照
  • 即使别的事务提交了新数据,你也看不到(直到你提交事务)。
-- Session A
SET autocommit=0;
SELECT * FROM t; -- 看到的是“时间点T”的快照-- Session B
INSERT INTO t VALUES (1,2);
COMMIT;-- Session A 再次查询
SELECT * FROM t; -- 仍然看不到 B 插入的行!
-- 因为还是看“时间点T”的快照COMMIT;SELECT * FROM t; -- 提交后重新开始事务,快照更新,现在能看到 (1,2)

⚠️ 这就是文档里说的:你看到的可能是一个从未真实存在过的数据库状态
比如你更新了一些行,然后 SELECT 能看到自己改的,但其他未改的行是旧版本 —— 整体看起来像是“拼接”出来的状态。

(2)READ COMMITTED
  • 每次 SELECT 都会创建一个新的快照
  • 所以你能看到其他事务已经提交的最新数据。
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;-- 每次 SELECT 都刷新快照
SELECT * FROM t; -- 可能看到刚被别人提交的数据

📌 小结:

  • REPEATABLE READ:事务内 SELECT 总是看到一样的数据(强一致性)
  • READ COMMITTED:每次 SELECT 都看最新的已提交数据(弱一致性,但更“新鲜”)

❗ 注意:DML 操作(UPDATE/DELETE)不受快照限制!

文档中的重点提示:

一致性读只适用于 SELECT,不适用于 DML!

这意味着:

-- 你在 REPEATABLE READ 下
SELECT * FROM t WHERE c1 = 'xyz'; -- 返回空(你看不到别人刚插入的)DELETE FROM t WHERE c1 = 'xyz';   -- 却可能删掉别人刚插入的记录!

为什么?因为 DELETE 是 DML,它会去读最新的数据版本(当前读),并施加锁。

💡 这就是所谓的“幻读”场景之一 —— 你看不到某行,但它能被你删掉。


🔐 2. 什么时候需要 Locking Read(锁定读)?

当你不只是“看”数据,而是要“基于数据做后续操作”时,快照读就不安全了。

❌ 问题示例:丢失引用完整性

-- 你想插入一个子记录,先查父记录是否存在
SELECT * FROM parent WHERE id = 100; -- 找到了-- 但在这瞬间,另一个事务删除了 parent.id=100
-- 你插入 child 记录就会破坏外键约束!
INSERT INTO child (...) VALUES (...);

👉 快照读无法防止这种情况。

✅ 解决方案:使用 FOR SHAREFOR UPDATE

(1)SELECT ... FOR SHARE
  • 给读取的行加共享锁(S锁)
  • 别人不能修改或删除这些行,直到你提交
  • 适合“检查是否存在”的场景
SELECT * FROM parent WHERE id = 100 FOR SHARE;
-- 如果别人正在删这行,你会等待
-- 保证你看到的 parent 是“受保护”的
(2)SELECT ... FOR UPDATE
  • 给读取的行加排他锁(X锁)
  • 别人不能读(在某些隔离级别)、不能改、不能删
  • 适合“马上要更新”的场景
SELECT counter FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter = counter + 1;
-- 确保没人能同时读取并更新 counter

⚠️ 注意:必须关闭 autocommit 才能生效!

START TRANSACTION;
SELECT ... FOR UPDATE;
-- 其他事务在此期间无法获取该行的锁
COMMIT; -- 锁释放

🛑 3. 特殊选项:NOWAITSKIP LOCKED

当你不希望等待锁时,可以用:

(1)NOWAIT

  • 立即失败,不等待
  • 适用于不想阻塞的应用逻辑
SELECT * FROM queue_table FOR UPDATE NOWAIT;
-- 如果行被锁,直接报错:ERROR 3572 (HY000): Do not wait for lock.

(2)SKIP LOCKED

  • 跳过被锁的行,只返回未被锁的行
  • 常用于“任务队列”消费场景
SELECT * FROM tasks FOR UPDATE SKIP LOCKED LIMIT 1;
-- 多个 worker 并发执行,各自拿不同的任务,互不等待

⚠️ 警告:SKIP LOCKED 返回的是不一致的数据视图,不能用于金融类事务!


🧩 4. DDL 对一致性读的影响

某些 DDL 会破坏 MVCC 快照机制:

DDL是否影响一致性读原因
DROP TABLE❌ 不可用表都没了,快照也无法访问
ALTER TABLE(重建表)❌ 可能出错新表的行不在原快照中,可能报错 ER_TABLE_DEF_CHANGED

所以建议:不要在长事务中执行 DDL!


🔄 5. 特殊 SELECT:INSERT INTO … SELECT 等

这类语句中的 SELECT 默认行为更强:

INSERT INTO t2 SELECT * FROM t1;
  • 即使在 REPEATABLE READ,这个 SELECT 也会像 READ COMMITTED 一样,每次读最新快照
  • 目的是减少锁冲突,提高性能

如果你想避免锁,可以设置:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 或
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

🎯 总结:一张表帮你理清思路

场景推荐方式原因
只想读数据,不关心是否最新SELECT(一致性读)高并发,无锁
想读最新已提交数据SET TRANSACTION READ COMMITTED + SELECT每次都刷新快照
查数据后要插入/更新相关数据SELECT ... FOR SHARE防止别人改
要更新计数器、分配ID等SELECT ... FOR UPDATE防止并发竞争
消费任务队列SELECT ... FOR UPDATE SKIP LOCKED高并发取任务
不想等锁SELECT ... FOR UPDATE NOWAIT失败快速返回
获取最新数据并加锁SELECT ... FOR UPDATE当前读,读最新版本

💡 一句话总结

  • SELECT(无锁):看的是“过去的照片”(MVCC 快照)
  • SELECT ... FOR UPDATE/SHARE:看的是“现在的实景”,并且“占住不让动”(加锁)
  • NOWAIT/SKIP LOCKED:不想等人,要么立刻失败,要么跳过

如果你有具体的业务场景(比如“如何安全地生成唯一编号”、“如何避免超卖”),我可以结合这些机制给出完整方案。欢迎继续提问!

http://www.dtcms.com/a/398731.html

相关文章:

  • Oracle归档及数据库存储空间查询
  • 怎么用wordpress建外贸网站华丽的网站模板
  • 如何在Linux系统里将新添加磁盘进行分区挂载
  • 公司网站案例免费域名建站
  • 抓包解析MCP协议:基于JSON-RPC的MCP host与MCP server的交互
  • 一“网”跨协议,万“设”皆可通!耐达讯自动化Modbus TCP转Profibus ,让控制无界,让能源有道。
  • 江门网站优化公司衡水seo网站建设优化排名
  • [2025CVPR-域泛化方向]:通过改进损失景观实现更好的域泛化
  • 网站开发商怎么关闭图片显示公司网站费怎么做分录
  • ABAC权限模型实战:实现“上班才能访问财务系统”!
  • 深入解析:使用递归计算整数各位数字之和的C语言实现
  • 第1章:初识Linux系统——第4节:文件操作命令2
  • 众云网联做的网站效果好吗深圳网站设计公司排名榜
  • wordpress修改教程网站优化制作公司代理
  • 【LeetCode】82. 删除排序排序链表中的重复元素 II
  • 如何设计一个企业级消息推送系统架构?
  • 使用IOT-Tree消息流实现实时数据同步:标签实时数据--关系数据库表
  • 国外做网站公司能赚钱备案网站多长时间
  • 淘宝网站是谁做的好wordpress 分类信息主题
  • Scikit-learn Python机器学习 - 回归分析算法 - 岭回归 (Ridge Regression)
  • 【mysql】内部技术架构
  • 马来西亚股票数据API对接文档
  • 【C++实战㉟】解锁C++面向对象设计:里氏替换原则实战指南
  • 邮件系统的未来趋势:技术革新与智能化的未来
  • 解决MySQL的sql_mode=only_full_group_by错误提示
  • phpcms 网站名称标签建设政协网站的意义
  • 【langgraph】docker镜像查看langraph-api相关版本
  • Datawhale25年9月组队学习:llm-preview+Task3:提示词工程
  • RunnableLambda
  • 记录一次windows资源管理器崩溃,任务栏无法打开任何软件