← ReJava · 学习目录

REJAVA / LEARNING NOTES

MVCC(多版本并发控制)与可见版本

沿版本链和读视图判断普通查询读到哪个版本,分清两种隔离级别的视图时机。

Java 17 / 21更新于 2026.10.12约 23 分钟阅读

根据课程与真实学习交流整理;不是逐字稿

本次更新说明
  • 整理为公开学习笔记,保留练习与隔日复测。
  • 附课后问答:视图何时更换、上边界判断、长事务的代价。

热身 · 一 为什么需要旧值 · 二 版本怎样留下 · 三 读视图 · 四 沿链判断 · 五 两种级别 · 六 保留与边界 · 七 应用 · 检验 · 表达 · 追问 · 隔日复测

学完能做到什么,为什么现在学

给定一条数据的版本链、Read View(读视图)建立时的活跃事务名单和两个编号边界,你能从最新版本开始逐个判断,找出本次普通查询实际返回的值;还能解释为什么换一个读视图,结果可能不同。

第11课已经讲过两种常用隔离级别下的读数,以及重复审批不应继续修改。本课继续回答背后的“为什么”:不是只记可重复读会读旧值,而是把旧值从哪里来、怎样找到它讲清楚。本课属于 MySQL 事务专题,不需要先学 JVM(Java 虚拟机)。

前置: 知道事务何时提交,以及普通查询和锁定查询不同。 必学: 第一至七节、选择题与表达。第三节后可暂停,保持完整一课。只讨论普通一致性读和已有记录的更新;锁范围、死锁处理另讲,源码字段名及删除边界放拓展。

主读: 官方 InnoDB(事务存储引擎) Multi-Versioning(多版本存储)。先读开头两段、DB_TRX_ID(最近修改事务编号)与 DB_ROLL_PTR(回滚指针)的说明,再读“更新撤销信息何时可丢弃”。不要先背字段字节数。内部判断另核对了 mysql-8.4.0 标签的源码,链接见文末来源[4][5]。

学完正文后,可以对照本课末尾的课后问答,重点看视图何时更换、上边界怎样判断,以及为什么查询完成后要及时结束事务。

热身:回忆前几课(3分钟)

先凭记忆说一句,再展开核对。答不上来就记下来,接着往下学,这不是进入新课的门槛。

第11课:两张事务表在同一连接中先改状态、再新增记录,未显式开启事务且默认自动提交。第二步失败,为什么后来的回滚不能撤销第一步?

第一步已在自己的事务中成功提交,第二步属于另一事务。需要把两步放进同一显式事务,才能在提交前整组撤销。不要把原因换成“重复键只回滚一句”。见第11课课后问答。

第11课:A在同一个可重复读事务里先普通读到经办人7;B改成9并提交;A自己不写、不加锁,再普通读到谁?

仍为7,因为复用首次一致性读的快照。本课就解释数据库怎样还原和挑中这个旧版本。

第10课:一个二级索引已有编号、部门和状态,页面额外需要不在索引中的姓名,要多做什么?

为了取得姓名,按主键去聚簇索引取整行,即 table lookup(回表)。本课使用聚簇索引记录说明旧版本,先回忆这一层区别即可。

一、已经被修改了,为什么还能读到原来的值

“别人已经提交新数据,我的普通查询为什么还能返回旧数据?”

继续用办件表,但这次只看1201号办件的经办人。列 id 是办件编号,owner_id 是经办人编号。1201号原来由7号经办人负责,后来改成9、11、13。这些编号都是教学设定,只用来演示人员变化。

如果每次更新都彻底抹掉旧内容,A想按早先的快照查询时,旧值就无处可找。InnoDB(事务存储引擎)因此保存还原旧内容所需的信息:新查询可以读新版本,早先的普通查询可以按规则找到旧版本。这就是 MVCC(多版本并发控制)要支持的工作。[1]

整个过程有两件事,缺一不可: 先有办法还原以前的行,再决定这次查询允许看哪个版本。 只有旧数据而没有可见性规则,仍可能读到别人未提交的修改;只有规则而没保留旧信息,又无法返回旧值。

先自己说,再看“可以这样答”

数据库保存了还原旧行所需的信息,普通查询再按自己的可见性规则选择版本。MVCC(多版本并发控制)不是为每个事务复制整张表,而是把旧版本信息和版本选择规则结合起来。

二、先把一行数据的修改过程连起来

“版本链是什么?撤销日志只用于回滚吗?”

先把两个编号分清: 办件编号1201标识业务记录,写事务编号103标识一次修改者 。不同事务可以先后修改同一办件。事务编号不等于经办人编号,也不能按它直接推断提交先后。

以下是本课的完整时间顺序。A只读、不改数据,使用 REPEATABLE READ(可重复读),且一直保持同一显式事务。100号事务一直在修改另一条不相关记录,不碰1201;其他未列出的编号不影响结果。[2]

顺序谁做了什么1201号的经办人
①90号事务创建记录并提交7
②103号事务改成9并提交9,已提交
③105号事务改成11,暂未提交;此时A第一次普通查询,建立读视图最新修改为11,但A不能读未提交值,看到9
④105号事务提交;随后112号事务改成13并提交13,已提交
⑤A仍在原事务里第二次普通查询从当前版本开始查找允许看的旧值,过程见第四节

注意④的先后:105先提交,112才修改同一行。本例没有让两个未结束写事务同时改同一行。第三节会把③建立的读视图具体展开。

行里留下什么线索

对这里的聚簇索引记录,DB_TRX_ID(最近修改事务编号)告诉数据库“这个行版本是谁修改的”;DB_ROLL_PTR(回滚指针)指向相应的 undo log(撤销日志)记录。undo log(撤销日志)包含撤销最近修改、还原旧行所需的信息,既能用于事务回滚,也能用于一致性读还原旧版本。[1][3]

到了步骤⑤,可以把重建过程画成下面这条 version chain(版本链):

从新到旧谁写的还原出的经办人
当前记录112号13
↓ 沿撤销信息还原上一个版本105号11
↓ 再还原上一个版本103号9
↓ 更早版本,是否仍保留取决于需求90号7

这是逻辑上的还原过程,不是说每一行完整副本都单独存放一份。 为了A读到9,需要保留能还原到9的信息;它不要求7永远留着。本课实际查找到9就会停止。

拓展:隐藏字段的大小与主键

官方说明 DB_TRX_ID(最近修改事务编号)为6字节,DB_ROLL_PTR(回滚指针)为7字节;这些大小属于实现背景,本课不考背诵。另一个 DB_ROW_ID(隐藏行编号)用于数据库需要生成隐藏聚簇索引的情形,不要把它和业务主键、修改事务编号混为一谈。[1]

先自己说,再看“可以这样答”

当前行记录带着最近修改者和回滚指针。沿撤销信息可以逐步还原更早的行版本,形成逻辑上的版本链。撤销日志不只用于回滚,也支持普通一致性读;它保存的是还原所需的信息,不等于给每个事务备份整张表。

三、读视图:记住建立时,哪些修改还不能看

“有很多旧版本,数据库凭什么决定读哪一个?”

Read View(读视图)是一次可见性判断所用的信息集合。它记录建立时仍活跃的读写事务,以及编号边界等信息。这里“活跃”表示还没结束,修改不能被其他普通一致性读当成已提交结果。只读A没有修改1201,本例先专注其他事务的版本。[5]

在第二节步骤③,假设读视图捕获了下面这份信息。编号是教学设定,允许中间有未展示的事务,不要求连续。

人话名称本例的值用途
建立时的活跃名单100、105记下当时哪些读写事务还没结束;100改其他记录,也在名单里
下边界100活跃名单中最小的编号;比它还小的有效行版本可见
上边界110建立时捕获的下一可分配编号边界;编号达到或超过它的其他事务版本不可见
创建者A自己的修改要单独允许;本例A只读,没有自己的行版本需要检查

为什么名单里有100,而1201从没被100修改?因为读视图不只为这一行制作,它要能判断查询遇到的其他记录。因此不能把活跃名单当成“这条版本链上的作者名单”。

判断一个版本,依次做什么

先看它是否为本事务自己的修改。若是,自己的修改可见;若不是,使用下面三步。规则与固定版本源码的判断相符。[4]

  1. 修改者编号小于100: 对本例正在检查的有效行版本,可见。
  2. 修改者编号大于或等于110: 不可见。它超出了读视图建立时允许看到的编号范围。
  3. 编号在100到109之间: 再查建立时的名单。在名单里,不可见;不在名单里,可见。

拿103举例:它比100大,并不意味着不可见;它又小于110,且不在当时的活跃名单中,所以其已提交的版本可见。拿105举例:它在名单里,当时未完成,所以对这个读视图不可见。 看的是建立时的名单,不是查询这一刻重新问一遍“你现在提交了吗”。

拓展:源码字段名为什么容易记反

在核对的 mysql-8.4.0 源码中,m_ids(建立时活跃事务名单)对应名单,m_creator_trx_id(视图创建者编号)对应创建者。m_up_limit_id(可见性下边界)是100,m_low_limit_id(可见性上边界)是110;英文名字与直觉相反,别凭单词猜。它们是实现字段,不是业务查询要传的参数。[4][5]

若当时没有其他活跃读写事务,下边界会与上边界相同,仍然有“达到上边界则不可见”的限制;空名单不是任何未来版本都能看。自己的写入仍单独判断。

先自己说,再看“可以这样答”

读视图保留建立时的活跃事务名单和编号边界。对其他事务写出的版本,早于下边界的可见,达到上边界的不可见,中间的查当时名单:在名单里不可见,不在则可见。自己的修改另行可见。不能把事务编号当成提交顺序,也不能拿当前最新名单替换旧视图。

可以在这里暂停。先能解释103为什么可见、105为什么不可见,再沿整条版本链走一遍。

四、完整走一遍:13、11、9,最后为什么停在9

“A第二次查的时候,105和112都提交了,为什么还可能读到9?”

继续第二节步骤⑤,A复用步骤③的读视图:下边界100、上边界110、活跃名单100和105;A没有自己的写入。当前版本链是112写13→105写11→103写9。注意这些版本 现在 都已提交,但可见性仍按A原来的视图判断。

这一步检查谁用哪条规则接下来做什么
112写出的13112≥110,超出上边界不返回13,沿撤销信息还原105版本
105写出的11100≤105<110,而且105在建立时名单里不返回11,继续还原103版本
103写出的9100≤103<110,且103不在建立时名单里可见,返回9;停止,不再找更早的7

105后来提交,不会把它从A已有的名单中动态删掉。否则A第一次读到9,第二次就可能因为105提交而读到11,失去同一快照的读取规则。[2][4]

因此两种常见误解都要避开: 不是找“当前已经提交的最新值”,也不是找“最老的值” 。准确过程是从新向旧,找到第一个对本次读视图可见的版本,然后返回。

先自己说,再看“可以这样答”

A先检查112,超过视图上边界,不可见;退到105,它在视图建立时的活跃名单里,仍不可见;退到103,它位于两边界之间且不在名单里,可见,所以返回经办人9。105和112后来提交,不会改写这个旧读视图。

五、两种隔离级别:变的是视图的使用时机

“读已提交和可重复读的底层规则,是不是完全不同?”

在这里讨论的普通一致性读中,都需要判断版本可见性。核心区别是 何时建立、是否复用读视图 :READ COMMITTED(读已提交)每次一致性读使用新的快照;REPEATABLE READ(可重复读)在同一事务内复用首次一致性读建立的快照。[2]

现在把主例只改一个条件:A使用 READ COMMITTED(读已提交)。105未提交时,A第一次查询仍读到9。后来105、112依次提交,A第二次查询建立新的读视图。假设此时100仍活跃、其他事务均结束,新的上边界为113:

第二次查询所用读视图判断当前112版本
READ COMMITTED(读已提交)新名单只有100;下边界100,上边界113100≤112<113,112不在新名单里,返回13
REPEATABLE READ(可重复读)原名单100、105;下边界100,上边界110112≥110,继续向旧版本找,最终返回9

同一组修改,读数不同的原因就在这张表里。旧视图不接受后来完成的105,新视图在105已经完成后建立,所以不再把105列为活跃;当前112也已经提交并落在新视图可见范围中。

再回忆第11课:普通 START TRANSACTION(开始事务语句)不等于立刻建立读取快照。本课说的是 首次普通一致性读 的时点;开始时显式请求一致性快照的特殊语句另有规则。自己的写入也仍可见,不能把“读旧版本”理解成永远忽略自己刚改的内容。[2]

先自己说,再看“可以这样答”

两种级别都用版本可见性规则,主要区别是视图时机。读已提交每次普通一致性读使用新的视图,别人新提交的版本可能因此可见;可重复读在同一事务复用首次读的视图,所以旧名单和边界仍生效。还要说明是普通读、同一事务,以及自己的修改例外。

六、旧版本不能马上删,也不能用来代替加锁

“事务已经提交,为什么还要留撤销信息?一直开着只读事务有什么影响?”

105提交后,自己的回滚需求结束了,但A还可能需要把11还原成9。因此,支持这些旧快照所必需的 update undo(更新撤销信息)不能仅因写事务提交就马上删除。只有不再被相关读取或回滚需要时,才具备清理条件;purge(历史清理)在后台处理可清理的历史,并不保证一提交就立即完成。[1][6]

假设A一直不结束原来的可重复读事务,其他人不断修改。A可能仍需要早先的版本,相关历史就无法及时清理,历史积压和空间占用可能增长。 只读不等于没有代价 ,问题是它长期保留了旧视图。不能把事务开着去等待用户操作;查询完成后应及时结束事务。[1][6]

这与重复审批的保护是另一件事。snapshot read(快照读)回答“我现在可以看哪个版本”,不负责阻止其他人修改。SELECT … FOR UPDATE(查询并加更新锁)这样的 locking read(锁定读),要按最新可用记录和锁规则处理,不能直接用本课旧视图版本链决定审批资格。第11课已经讲过:取得保护后再核对状态。回看第11课。

拓展:删除、插入和覆盖查询的边界

本课主例都是已有记录的更新。删除会涉及删除标记与后续清理;对某个旧视图来说,一行可能仍存在,对较新的视图可能已不存在。新插入行若没有任何对该视图可见的旧版本,则查询不能凭空得到一行。先记“找不到可见记录也可能不返回行”,细节不放进本课数字题。[1][2]

第10课说“所需字段都在索引中”,描述字段覆盖关系。二级索引做多版本可见性检查时,在删除标记或页被较新事务修改等情况下,引擎仍可能访问聚簇索引核对版本。因此“无需为取得缺失字段回表”不能无限扩成“引擎绝不访问聚簇索引”。这不是要求你重学索引,而是补上版本检查的边界。[1]

先自己说,再看“可以这样答”

写事务提交后,别的旧快照可能仍要用撤销信息重建旧版本,不能立即全部清除。长期保留旧视图会拖住相关历史清理。MVCC(多版本并发控制)支持版本读取,但不能代替审批前的并发保护;普通读和锁定读需要分开。

七、完整应用:给你一份新视图,自己沿链找

“换一组数字,还能把每一步判断讲出来吗?”

这是一道独立教学题,不延续主例的编号。 A只读,在同一 REPEATABLE READ(可重复读)事务里复用旧视图;下边界200、上边界210、建立时的活跃名单为200和205。没有自己的修改、没有删除、没有结构变更。下面是另一条办件记录从新到旧的有效版本链,当前所列写事务均已提交;其中205在视图建立时尚未提交。

修改者编号经办人编号
2128
2056
2034
1902
  1. A返回哪个经办人编号?逐项解释212、205、203,不只给最终数字。
  2. 如果另一次普通查询使用新视图,名单只有200、下边界200、上边界213,版本链不变,返回谁?
完成后核对过程

原视图:212≥210,不可见;205在旧名单里,不可见;203位于200到209之间且不在名单,可见,返回4。到这里就停止,不继续取190写出的2。

新视图:212<213,且212不在仅含200的名单里,可见,直接返回8。这里明确给的是一个新视图,不是旧可重复读事务偷偷刷新了原视图。

可选双连接观察:先看到未提交修改之前的版本

需要已有的隔离测试库和两个独立连接。两个连接起初都开启自动提交且没有活动事务,按步骤切换,不能把整段粘进一个窗口。只用虚构数据;同名表已存在就停止核对,不删除或覆盖。任一步报错就停止后续步骤,在涉及连接中回滚并保留错误。

-- 准备:连接B,事务演示开始之前
CREATE TABLE teach_mvcc_0012 (
  id BIGINT PRIMARY KEY,
  owner_id INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO teach_mvcc_0012 VALUES (1201, 9);

-- ① 连接B:改成11,但先不提交
START TRANSACTION;
UPDATE teach_mvcc_0012 SET owner_id = 11 WHERE id = 1201;

-- ② 切到连接A:第一次普通查询,此刻建立读视图
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT owner_id FROM teach_mvcc_0012 WHERE id = 1201;
-- 预期9,不是尚未提交的11

-- ③ 切回连接B:先提交11,再开启另一次事务改成13
COMMIT;
START TRANSACTION;
UPDATE teach_mvcc_0012 SET owner_id = 13 WHERE id = 1201;
COMMIT;

-- ④ 切回连接A:仍是原事务
SELECT owner_id FROM teach_mvcc_0012 WHERE id = 1201;
-- 预期仍为9
COMMIT;
SELECT owner_id FROM teach_mvcc_0012 WHERE id = 1201;
-- 新事务查询预期13

验证目标:连接A的值序列9→9→13。事务编号由数据库分配,不能把实际编号认作主例的103、105、112。本实验只观察版本选择的外部结果,不提取内部字段,也不验证性能。这个实验没有在示例环境中运行过,以上是依据官方规则推导的预期;自己运行时以实际结果为准。实验语法与事务控制前提可回看第11课。

八、即时检验:合上正文,做一次

每题给出所需前提,点击后立即反馈。只做一次,再进入表达,不靠重刷猜答案。

  1. 本课已有行被更新后,普通查询需要还原原来的值,主要使用什么信息?
  • A. 为各事务复制的全表
  • B. 修改留下的撤销信息
  • C. 备份系统的整库备份
查看答案与解释

答案:B

撤销信息既用于回滚,也用于还原旧行。不是每个事务复制全表或恢复整库备份。

  1. A无自己的写入。旧视图下边界100、上边界110、建立时活跃名单100和105。已有行的有效更新版本链为112写13→105写11→103写9,现在都已提交,无删除。A返回哪个值?
  • A. 十三号经办人
  • B. 十一号经办人
  • C. 第九号经办人
查看答案与解释

答案:C

112超过上边界;105在建立时名单内;103位于两边界之间且不在名单,因此返回9。

  1. 105在A旧视图建立时尚未提交,后来已经提交。A仍复用原视图、自己不写。对105版本怎样判断?
  • A. 仍按建立时名单判断
  • B. 按当前提交状态判断
  • C. 自动改用最新的名单
查看答案与解释

答案:A

既有读视图不是实时名单,105后来提交不会动态修改这个视图,因此仍不可见。

  1. A无自己的写入,视图上边界为110。另一事务编号恰好是110,后来写出一个版本并提交。这个旧视图能看该版本吗?
  • A. 已经提交所以可见
  • B. 达到上界仍不可见
  • C. 等于上界直接可见
查看答案与解释

答案:B

上边界使用“大于或等于”排除,不是只有大于才排除。自己的修改是另一条规则,本题已排除。

  1. A自己不写,在同一读已提交事务中第一次普通查询读到9;随后105提交经办人11,再由112改成13并提交。没有其他修改或删除,A第二次普通查询为什么能读到13?
  • A. 原有视图名单被修改
  • B. 原有版本记录全删除
  • C. 本次读取使用新视图
查看答案与解释

答案:C

读已提交每次一致性读使用新视图。不是修改原有视图,也不是把旧版本立刻全删掉。

  1. 写事务已经提交,但另一个长事务的旧视图仍需要相关旧值。支持重建的更新撤销信息能马上全部丢弃吗?
  • A. 仍被旧视图需要须保留
  • B. 写事务一提交全部删除
  • C. 只读事务不会用旧信息
查看答案与解释

答案:A

自己的回滚需求结束不等于其他旧视图不再需要。满足可清理条件后仍由后台处理,不保证立刻完成。

九、用完整回答检验理解

先独立回答四问。可以写下来,也可以先口述再整理成文字;每一问都明确范围,不需要猜未给出的业务处理规则。

  1. 1201号的经办人从9改成11后,数据库凭什么还能还原9?分别说明当前行、修改者编号、回滚指针与撤销信息的作用。
  2. A只读并复用旧视图,下边界100、上边界110、建立时名单100和105;已有行的有效更新版本链112写13→105写11→103写9,无删除。现在写事务都已提交。逐步说明三个版本为什么被跳过或选中,最终返回哪个经办人?
  3. A在同一事务中只做普通查询、自己不写:第一次读到9,随后其他事务依次把经办人改成11、13并提交,没有其他变化。若A分别使用读已提交、可重复读,第二次各返回什么?说明两种级别何时建立或复用视图。
  4. 一段报表查询代码在可重复读下第一次查询后,仍把事务开着等待用户确认十分钟,同时其他人不断修改记录。可能拖住什么?应该怎样调整事务结束时机?这是一道设计题。

练习:请在自己的笔记中写出答案(也可附第七节逐步推演),再回到第二至六节的“可以这样答”核对。

可选的双连接实验不是继续学习的前提;能跟着讲解读懂、能独立讲出来、隔几天仍能答对,是三件不同的事。

十、集中追问:只问前面讲过的内容

先自己答,再展开

事务编号比较大,能直接说它提交得更晚吗?

不能。编号不等于提交次序,本课100一直活跃,103却已提交。判断需要视图边界和建立时名单。

修改事务编号在两边界之间,一定不可见吗?

不一定,还要查建立时名单。主例103不在名单可见,105在名单不可见。

为什么找到103写出的9后,不继续返回90写出的7?

从新向旧找到第一个可见版本就返回,不是选择最古老的版本;更早历史是否仍存在还取决于清理需求。

自己的更新也必须躲过旧视图的活跃名单才能看到吗?

不用。自己的修改另行可见,规则先判断创建者。主例专门排除了自己的写入,避免把两类情况混在一起。

读视图的名单为空,之后所有新版本都能读吗?

不能。仍有上边界,达到或超过上边界的其他事务版本不可见;空名单时上下边界相同。

有MVCC(多版本并发控制),就可以不再考虑写入时的锁了吗?

不可以。本课解决普通一致性读选版本;锁定读取和写入的并发保护仍要单独处理。读到待审批不等于阻止别人审批。

十一、隔日复测:换编号,保留判断方法

建议隔一天用5分钟回答;约一周后再换数据重做。刚看过答案就能复述,不等于已经记牢。

1. A只读且复用旧视图,建立时活跃名单300、305,下边界300,上边界310。已有记录的有效更新版本链为312写8→305写6→303写4;没有删除或结构变更,现在三个写事务均已提交,305提交后312才修改。A返回什么?各步为什么?

返回4:312≥310不可见;305在名单里不可见;303在边界内且不在名单里可见。

2. 305在视图建立后提交了,但A还用同一旧视图,上一题会自动改成返回6吗?

不会。旧名单记录建立时状态,不因305后来提交而动态改变。

3. 另一查询明确建立新视图:活跃名单只有300、下边界300、上边界313,版本链不变,返回什么?

返回8。312小于313且不在新名单里,当前版本可见。

第11课复习:同一显式事务中第一步更新成功、第二步普通插入重复键失败。只捕获并返回,未让数据库事务提交或回滚,能认为锁已经释放吗?

不能。代码返回不自动等于显式数据库事务结束;业务失败应走明确回滚流程。这里未假定有框架事务管理器自动完成收尾。

主来源与引用

本课速查。数字是教学设定;手工推演、源码核对和实际数据库运行结果要分开看。

  1. 多版本存储:隐藏字段、旧版本重建、更新撤销信息保留,以及二级索引的可见性检查。

  2. 一致性非锁定读:两种级别的快照时机、自己的写入例外、普通读与其他操作的边界。

  3. 撤销日志:还原最近修改与一致性读取旧数据。

  4. mysql-8.4.0:ReadView::changes_visible(版本可见性判断):创建者、两边界与活跃名单判断。

  5. mysql-8.4.0:ReadView::prepare(准备读视图):捕获名单与编号边界。

  6. 历史清理:后台清理条件、长事务对历史积压的影响。本课不要求调整清理参数。

学完后,换一组编号自己沿版本链走一遍,记下仍不确定的那一步,例如“105已经提交,为什么还不能返回11”,再回到第三、四节核对。锁住哪些记录、为什么等待,放在第13课讲。

课后问答:理解是怎样变化的

问题与结论默认可见。展开后可看理解过程;以下根据真实交流整理,并非逐字稿。

已有行被改过几次后,普通查询怎样找回旧值?主例中103写出的版本为什么可见?

最终结论: 沿回滚指针找到撤销信息,逐步还原更早的行版本。103可见的完整理由是:它处于下边界100与上边界110之间,且不在读视图建立时的活跃名单中。

展开理解过程与易混点

最初理解(归纳): 曾说明撤销信息可以恢复旧版本,并能跳过112、105,最终返回9;但对行记录与撤销日志的关系、103可见的完整理由说得不够精确。

纠正与解释: 当前聚簇索引记录带着修改事务编号 DB_TRX_ID(最近修改事务编号)和 DB_ROLL_PTR(回滚指针);回滚指针指向 undo log(撤销日志),不是把整份日志直接放在行里。判断103时,只说“它比100大”或“它已提交”都不够,要同时用上两条边界和建立时的活跃名单。

最终结论: 沿回滚指针找到撤销信息,逐步还原更早的行版本。103可见的完整理由是:它处于下边界100与上边界110之间,且不在读视图建立时的活跃名单中。

现有复盘未逐条保留中间追问,本页不补写对话。

A在同一个显式事务里自己不写:第一次普通查询读到9,随后其他事务依次把值改成11、13并提交。A第二次普通查询,读已提交和可重复读各返回什么?

最终结论: 读已提交:第二次使用本次新建的视图,返回13。可重复读:第二次复用首次普通读的旧视图,返回9。换不换视图取决于隔离级别和“是否又一次普通一致性读”,不取决于A自己是否提交。

展开理解过程与易混点

最初理解(归纳): 曾把读已提交下换用新视图的时机与“提交”挂钩,没有落到“每次普通一致性读”上。

纠正与解释: 即使A一直没有提交,READ COMMITTED(读已提交)下每次普通一致性读也会使用新视图。别人提交只是让其修改有机会被后续新视图看见,不会替A修改已有的旧视图;REPEATABLE READ(可重复读)则在同一事务中复用首次普通读建立的视图。

最终结论: 读已提交:第二次使用本次新建的视图,返回13。可重复读:第二次复用首次普通读的旧视图,返回9。换不换视图取决于隔离级别和“是否又一次普通一致性读”,不取决于A自己是否提交。

现有复盘未逐条保留中间追问,本页不补写对话。

Read View(读视图)里保存的是上一次的查询结果吗?

最终结论: 不是。读视图保存的是选择版本所需的判断信息(建立时的活跃名单、编号边界、创建者),不是装着查询结果的盒子。

展开理解过程与易混点

纠正与解释: 复用旧视图并不等于直接返回缓存的旧结果;数据每次仍要从当前行记录出发,必要时沿撤销信息还原。

最终结论: 不是。读视图保存的是选择版本所需的判断信息(建立时的活跃名单、编号边界、创建者),不是装着查询结果的盒子。

现有复盘未逐条保留中间追问,本页不补写对话。

A的旧视图上边界是110。另一个事务的编号恰好是110,后来修改了这行并提交。A复用旧视图、自己不写,能看到这个版本吗?

最终结论: 看不到。对其他事务写出的版本,编号大于或等于上边界110就不可见,无论它后来是否提交。

展开理解过程与易混点

纠正与解释: 上边界用“大于或等于”排除,编号等于110已经落在排除范围内。只问“它现在提交了吗”会得出错误结论:后来的提交不会改写已经建立的读视图。

最终结论: 看不到。对其他事务写出的版本,编号大于或等于上边界110就不可见,无论它后来是否提交。

现有复盘未逐条保留中间追问,本页不补写对话。

报表在可重复读事务中查完后,事务一直开着等用户操作十分钟,同时别人不断修改数据,会有什么问题?应该怎样调整?

最终结论: 报表查询完成后及时结束这次只读事务,把结果交给页面,等待用户操作期间不占着旧事务。之后若要写入,开启适当的新事务并重新核对必要状态。

展开理解过程与易混点

纠正与解释: A第一次读到9并保留旧视图;别人把这行改成11、13后,A若还在原事务里普通查询,仍可能需要9,所以还原9所需的更新撤销信息不能马上清掉。其他事务持续更新时,相关旧信息可能持续积压,拖住 purge(历史清理)并增加空间占用。关键不是报表读得慢,而是查完后仍不结束事务。结束A只是移除它自己的保留需求,其他读取仍可能需要历史,后台也不保证马上清完。

最终结论: 报表查询完成后及时结束这次只读事务,把结果交给页面,等待用户操作期间不占着旧事务。之后若要写入,开启适当的新事务并重新核对必要状态。

现有复盘未逐条保留中间追问,本页不补写对话。