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

mysql||事务相关知识

「可重复读(RR)下 select 和 update 的处理逻辑不一样」


1. 可重复读(RR)隔离级别下的“虚拟表”

  • 在 InnoDB 的 RR 下,普通 一致性读(Consistent Read)(例如 SELECT ... 没有 lock in share modefor update)会基于事务启动时的 快照(snapshot)
    👉 所以事务 A 在第一次 select id=5 时,如果这条记录不存在,就认为它在整个事务期间都不存在。

2. 那为什么还能更新?

这里要注意:

  • UPDATEDELETESELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE 属于 当前读(Current Read),它不是基于历史快照,而是要去读 最新版本,并且加锁。
  • 所以当事务 A 执行 UPDATE id=5 ... 时,它并不是在用“虚拟表”的快照结果,而是去 当前最新数据页 里找 id=5

如果此时事务 B 已经插入了 id=5 并提交,那么事务 A 的 update 能找到它并更新。


3. 幻读的本质

  • 一致性读(普通 select):基于旧快照 → 看不到事务 B 新插入的 id=5
  • 当前读(update / select for update):直接去读最新记录 → 能看到事务 B 新插入的 id=5

这就是你说的“违和感”:事务 A 在普通 select 看不到,但在 update 时却能“凭空更新”了。

于是再次查询(如果用当前读),事务 A 能看到事务 B 的插入。
👉 这就是 幻读 —— 同一事务里,前后两次读的结果不一致。


4. 再强调一下:

  • **虚拟表(快照)**只对一致性读有效。
  • 当前读必须看最新记录,不会受虚拟表限制。

所以 “为什么还能看到插入的新数据”
因为 update 属于当前读,不是走快照,而是直接访问最新数据页。

幻读发生情况:事务A查询第一次——>事务B插入数据——>事务A更新该条数据(记录的trx_id被改为事务A的id)——>事务A再次查询该数据(此时可见)

区间 [min_trx_id , max_trx_id )和m_ids关系是什么?

好问题 👍,很多人都会把 [min_trx_id , max_trx_id )m_ids 搞混,其实两者的关系是 包含与过滤的关系。


1. 定义

  • m_ids:Read View 生成时刻,系统里 真正活跃事务的 ID 列表
  • **min_trx_id **:m_ids 中最小的事务 ID。
  • **max_trx_id **:全局下一个即将分配的事务 ID(比当前最大事务 ID +1)。

所以:

[min_trx_id , max_trx_id )

这个区间是 可能活跃的事务 ID 的范围,但不是所有都活跃,
真正活跃的事务只在 m_ids 里


2. 举个例子

假设当前系统事务 ID 已经分配到 25,下一个是 26:

  • 活跃事务是 {20, 22, 24}

那么生成 Read View 时:

  • m_ids = {20, 22, 24}
  • min_trx_id = 20
  • low_limit_id = 26
  • 区间 [20, 26) = {20,21,22,23,24,25}

解释:

  • 区间 [20,26) 表示这些事务号可能在 Read View 生成时还没提交。
  • 但真正还没提交的只有 m_ids = {20,22,24}。
  • 其他号(21,23,25)说明它们虽然号在范围内,但事务已经在 Read View 生成时提交了。

3. 可见性判断逻辑联系

对某条记录的 trx_id

  1. 如果 trx_id < min_trx_id → 肯定已提交,可见。

  2. 如果 trx_id >= max_trx_id → 肯定是未来事务,不可见。

  3. 如果 min_trx_id <= trx_id < max_trx_id → 看它是否在 m_ids:

    • 在 m_ids → 当时还活跃,没提交 → 不可见。
    • 不在 m_ids → 当时已经提交 → 可见。

✅ 总结:

  • 区间 [min_trx_id , max_trx_id ) = “候选集合”
  • m_ids = “其中真正活跃的事务子集”

也就是说:m_ids ⊆ [min_trx_id , max_trx_id )

undo log中每个版本都会记录修改的事务id,是吗?那所有事务的id从头到尾都是单调递增的吗?从建立这张表之后就一直是递增的,不会重置?

对的,你理解得很准确 👍,我帮你把 InnoDB 里的 事务 id(trx_id)undo log 的关系说清楚:


1. undo log 里的 trx_id

  • InnoDB 采用 多版本并发控制(MVCC)

  • 每条记录有一个 隐藏列 trx_id,表示最近修改这条记录的事务 id。

  • 当事务更新一条记录时:

    • InnoDB 会把旧值写到 undo log,并且 undo log 里会记下生成这个版本的事务 id(也就是写入时的 trx_id)。
    • 这样,在做一致性读时,InnoDB 能根据 trx_id 判断这个版本对当前事务是否可见。

2. 事务 id 分配

  • 每开启一个新事务,InnoDB 都会给它分配一个全局唯一的 事务 id
  • 这个 id 是 单调递增的,全局的(不是针对单张表)。
  • 从 MySQL 实例启动之后就一直递增,不会因为某张表 truncate 或 drop 而重置。

3. 会不会重置?

  • 不会重置,即使表删掉重建,trx_id 也继续从上次的最大值往上加。
  • 因为事务 id 是 InnoDB 系统级别的,用来区分全局所有事务的先后顺序。
  • 就算表不存在了,其他表里的事务也必须保证顺序正确,所以 trx_id 不能按表重置。

4. 溢出问题

  • InnoDB 的事务 id 是一个 64 位无符号整数,上限非常大(2^64-1)。
  • 理论上几乎不可能在实际场景下溢出(就算每秒产生一百万个事务,也能用几十万年 😅)。

✅ 总结:

  • undo log 中每个版本都会带上生成它的事务 id。
  • 所有事务 id 在实例范围内全局唯一,并且单调递增。
  • 从实例启动后一直递增,不会因为表操作而重置。
  • 几乎不会溢出。

那查找版本链的时候,查找到最后一个能看到的版本记录是吗?因为之前的版本肯定能看到,此时需要查找到最新能看到版本。

完全正确 👍,这就是 MVCC 版本链查找的核心逻辑。我给你详细梳理一下:


1. 版本链结构

  • 每条记录(主记录)有一个 最新版本
  • 每次更新/删除都会生成一个 undo log 版本,并通过 回滚指针(roll_pointer) 链接到前一个版本。
  • 这样就形成了一条 版本链
最新版本 -> 上一个版本 -> 上上个版本 -> ... -> 初始版本

2. 一致性读(select)的查找逻辑

当事务 A 执行一致性读时(RR 隔离级别):

  1. 从最新版本开始向 undo log 方向回溯
  2. 对每个版本,判断它对事务 A 是否可见(用 trx_id + Read View 的 m_ids/up_limit_id/low_limit_id 规则)。
  3. 找到 第一个可见版本就返回。

⚠️ 注意:

  • 找到的版本不一定是最新的版本号(trx_id 最大的),而是 对当前事务可见的最新版本
  • 之前的版本肯定不可见(要么已经被覆盖,要么事务在生成 Read View 时还没提交)。

3. 为什么不直接返回最新版本?

因为 最新版本可能是由另一个事务生成且未提交,如果直接返回最新版本,会违反事务隔离。

  • 所以必须沿版本链回溯,确保返回的版本 既最新又对当前事务可见

✅ 总结:

  • 版本链按时间倒序存储(最新 -> 最旧)。
  • 一致性读沿链回溯,判断每个版本可见性。
  • 返回 第一个可见版本,就是事务当前能看到的最新数据。

undolog 日志进行回滚,那么回滚之后的trx_id设置之前的?还是现在执行回滚的事务id?

好问题 👍,这里容易搞混:回滚(rollback)的时候,记录的 trx_id 究竟算谁的?


1. 记录的 trx_id 是怎么来的

  • InnoDB 里,每条记录都有一个隐藏字段 trx_id,表示最后一次修改它的事务 id
  • 当事务 执行 DML(INSERT/UPDATE/DELETE) 时,这个 trx_id 会被设置为当前事务的 id。
  • 同时,InnoDB 把旧值写入 undo log,undo log 里也记录生成它的事务 id。

2. 回滚时的处理

  • 回滚时不会新写一个 trx_id,因为回滚的逻辑就是“恢复旧版本”。

  • InnoDB 做的操作是:

    • 顺着 undo log 找到之前的版本;
    • 把记录恢复为该版本的数据;
    • 并且把记录的 trx_id 恢复成当时生成旧版本的事务 id(即 原始的 trx_id)。

3. 为什么不能用“现在执行回滚的事务 id”?

  • 如果把回滚动作当成一次新更新,给记录设置“现在的事务 id”,就会导致 MVCC 逻辑出错。
  • 因为这样做相当于告诉别人“这条记录是新事务改的”,会影响可见性判断。
  • 实际上,回滚只是“撤销”,应该恢复到 回滚前就已经存在的版本,而不是制造一个“新版本”。

4. 举个例子

  1. 事务 T1(trx_id=100)

    update user set age=20 where id=1;
    
    • trx_id=100 写到最新版本。
    • 旧值写入 undo log,带着原来的 trx_id(比如 90)。
  2. T1 rollback

    • InnoDB 用 undo log 把记录恢复成 age=10, trx_id=90
    • 而不是 age=10, trx_id=101(rollback)

这样一来,其他事务再判断可见性时,就能正确认为“这条记录是事务 90 最后修改的”。


结论

  • 回滚后,记录的 trx_id 会恢复成旧版本的事务 id(原始的 trx_id)
  • Undo log 用来回溯,回滚不是产生新事务,而是“消除”当前事务的修改痕迹。
http://www.dtcms.com/a/335480.html

相关文章:

  • nertctl使用了解
  • Node.js导入MongoDB具体操作
  • IoT/HCIP实验-5/基于WIFI的智慧农业实验(LwM2M/CoAP+PSK+ESP8266 连接到 IoTDA)
  • python study notes[4]
  • Vue深入组件:Props 详解3
  • 【adb端口5555】烽火hg680-gy_烽火hg680-gc安卓9线刷烧录包 解决用一段时间就提示升级的问题
  • 回溯剪枝的 “减法艺术”:化解超时危机的 “救命稻草”(一)
  • 如何在 Ubuntu 24.04、22.04 或 20.04 Linux 中更改计算机名称
  • 智能化管理:开启海洋牧场新时代
  • 字节 Golang 大模型应用开发框架 Eino简介
  • Vue深入组件:Props 详解2
  • es7.17.x es服务yellow状态的排查查看节点,分片状态数量
  • 42 C++ STL模板库11-容器4-forward_list
  • C++算法竞赛:位运算
  • 线程(基本概念和相关命令)
  • CT01-反转链表(Java)
  • 从零开始:SpringBoot与KingbaseES的完美融合实践
  • 基于飞算JavaAI的可视化数据分析集成系统项目实践:从需求到落地的全流程解析
  • Java 大视界 -- Java 大数据分布式计算在基因测序数据分析与精准医疗中的应用(400)
  • Excel 表格数据自动填充
  • 【线程安全(二) Java EE】
  • 基于飞算JavaAI实现布隆过滤器防止缓存穿透:原理、实践与全流程解析
  • 【电路笔记 通信】AXI4-Lite协议 FPGA实现 Valid-Ready Handshake 握手协议
  • 【计算机网络面试】键入网址到网页显示期间,发生了什么?
  • Tomcat Connector连接器原理
  • Bee1.17.25更新Bug,完善功能.不支持NOSQL,分库分表Sharding(2.X版有)
  • Rust Async 异步编程(一):入门
  • AI评测的科学之道:当Benchmark遇上统计学
  • uniapp中uni.showToast和 uni.showLoading同时使用时出现提示中断冲突问题。
  • Maven 开发实践