← ReJava · 学习目录

REJAVA / LEARNING NOTES

MySQL(关系型数据库)事务边界与隔离级别

用审批流程讲事务边界、失败回滚、读已提交与可重复读的快照差别和锁定读。

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

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

本次更新说明
  • 整理为公开学习笔记,保留练习与隔日复测。
  • 附课后问答:自动提交与单句回滚、两种隔离级别的读数、防重复审批、等锁后的处理。

热身 · 一 事务边界 · 二 四项属性 · 三 失败处理 · 四 并发读 · 五 隔离级别 · 六 读写边界 · 七 应用 · 检验 · 表达 · 追问 · 隔日复测

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

面对“修改办件状态+保存审批记录”,你能划定一次本地 transaction(事务)的范围,说明成功与失败各怎么结束;给出两个数据库连接的读写顺序时,能判断 READ COMMITTED(读已提交)与 REPEATABLE READ(可重复读)下普通查询的结果,并说清判断前提。

和前几课的联系: 第10课讲了怎样为一条查询提出索引候选。接下来从“怎样查得合适”转向“多步操作和并发查询是否正确”。本课仍用办件例子,不依赖 JVM(Java 虚拟机)的内容。一次真实作答中容易混淆的地方,整理在本课末尾的课后问答。

前置: 知道一行是一条记录,能读懂按编号查询、修改状态和新增记录。 范围: 同一数据库连接、两张 InnoDB(事务存储引擎)表。必学为编号正文、一次应用与表达;拓展可稍后读。第三节后可暂停。MVCC(多版本并发控制)的版本判定细节、锁范围和框架事务留到后续深入。

主读: 官方 Consistent Nonlocking Reads(一致性非锁定读)。只读前五段,核对“自己的写入”“首次读建立快照”“每次读建立快照”;先完成第五节时间线再读原文更容易。速查表可随时查。

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

先凭记忆回答,再展开核对。如果刚学完第10课,可以隔一天再答这两道变式;刚看完就答,检验不出隔日记忆。答不上来也不影响学习新课。

第10课变式:查部门30、已办结、9月提交的记录。索引按“部门、时间、状态”排列,为什么仍可能使用索引,却还要检查状态?

先沿索引圈出部门30的9月时间区间,其中可能同时有待审批和已办结,需要继续判断状态。差别在扫描区间里包含哪些记录,不是先筛选后才用索引。对照第10课课后问答。

第10课变式:索引已有编号、部门、状态、时间,现在页面要多显示“经办人姓名”,索引没有姓名,会多做哪一步?

为了取得姓名,按主键编号去聚簇索引读取整行,即 table lookup(回表)。这与范围后的条件过滤是两件事。

第8课短复习:异常时返回替代值,和只记日志,分别用哪种方法?假设日志回调本身不抛异常。

用 exceptionally(异常兜底方法)返回替代值;whenComplete(完成回调方法)可观察成功或失败,在本题前提下保留原结果。完整差别见第8课异常处理。

一、事务边界:先找出哪些操作必须成组

“办件已显示审批通过,但审批记录没保存,怎么避免?”

教学场景:1101号办件原来是“待审批”,没有审批通过记录。点击通过后要做两件事:在办件表把状态改成“已通过”;在审批记录表新增一条“1101号已通过”。本例业务规则是: 提交成功后,状态与对应记录必须匹配 。如果第二步失败,不能只留下第一步的结果。

transaction(事务)把一组数据库操作放在同一个处理单元。用 START TRANSACTION(开始事务语句)开始,全部完成后用 COMMIT(提交语句)确认;需要放弃时用 ROLLBACK(回滚语句)撤销本次未提交修改。它们属于 当前数据库连接 :第一步用连接甲、第二步用连接乙,不能靠甲的一次提交把两边自动包起来。[1][2]

autocommit(自动提交)默认开启。没有显式开始事务时,普通的单条数据修改通常各自构成一个事务,成功后分别提交。这样“修改状态”可能已经提交,后面的“新增记录”失败也撤不回前一条。 不是没有事务,而是两条语句没在同一个事务里。 [1]

安排两步都成功第二步失败
默认逐条自动提交两条先后各自提交第一条可能已提交,只留下状态变化
同一显式事务确认两步成功后统一提交走回滚分支,撤销本次状态修改;不能继续提交

事务边界应围绕这组必须一起完成的数据库修改,不能把“等待用户点确认”也包进去。事务期间可能持有锁;长时间不结束还可能让旧版本保留更久。具体锁在第六节解释。[3][8]

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

我会把状态更新和审批记录插入放进同一连接的同一个事务,两步成功才提交,任何一步失败就结束成功流程并回滚。默认自动提交会让两条语句分别提交,不能保证这两步一起完成。

二、ACID(事务四项属性):把名字对应到这次审批

“事务的四项属性是什么?原子性和一致性有什么区别?”

先用具体动作理解,再记名字。下面“一致性”的业务规则是我们的教学设定;数据库不会自动理解“审批必须写记录”,仍要靠正确的程序、数据约束和事务使用。[4]

属性本次审批里的含义别误解
atomicity(原子性)一次事务中的修改作为整体提交,或整体回滚不是任意异常都自动替应用回滚整组,第三节讲失败范围
consistency(一致性)操作前后仍满足定义好的约束与业务规则,例如审批通过和审批记录相互匹配不是只要开了事务,业务代码写错也会被自动修好
isolation(隔离性)多个事务同时读写时,按所选级别限制彼此可见内容与干扰不是每种级别都禁止所有中途变化
durability(持久性)已成功提交的结果应能在故障恢复后保留实际保证依赖日志刷盘设置、存储硬件等条件;不能无条件承诺任何故障都不丢

可以这样区分前两项:atomicity(原子性)问“这组修改是否一起生效”;consistency(一致性)问“生效后是不是合法的业务状态”。如果代码把状态和记录都写成错误内容,再完整提交,仍可能违反业务规则。本课先理解职责,恢复日志和刷盘参数另讲。[4]

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

ACID(事务四项属性)是原子性、一致性、隔离性、持久性。放到审批里,先保证状态与记录一起提交或撤销,再由正确逻辑和约束保证两者匹配;并发读写按隔离规则处理;成功提交后的保存能力还要结合可靠性配置。事务不替业务代码决定该写什么。

三、失败处理:报错后必须知道撤销到了哪一层

“第二条语句插入重复键报错,第一条状态更新会自动撤销吗?”

不能这样假定。InnoDB(事务存储引擎)有的错误只回滚失败语句,有的会回滚整个事务。普通 INSERT(插入语句)产生 duplicate-key error(重复键错误)且未使用忽略错误选项时,只回滚该语句;之前成功的状态更新仍可能留在当前事务中。此时再提交,仍会留下不完整业务结果。[3]

实现风险: 下面是客户端控制流程的伪代码,不是可以整段粘贴执行的 SQL(结构化查询语言)。假设表支持事务、使用同一连接,固定办件号仅作演示;实际输入先核对权限与状态,再用绑定参数传值。错误必须保留并向调用方报告,不能捕获后继续走成功分支。

开始事务
尝试:
    更新1101号办件状态,并检查是否确实更新了预期记录
    如果业务条件不成立:进入失败分支
    插入1101号审批记录
    两步都成功:提交事务
失败分支:
    请求回滚本次事务
    如果回滚也失败:保留原始错误和回滚错误,交由连接管理处理
    向调用方报告失败,不继续提交

验证思路:模拟三种结果:两步成功→状态与记录都保存;第二步普通重复键错误→显式回滚后两步都不保留;未找到可审批记录→不插入通过记录并结束事务。本例检查失败分支,不把伪代码当成运行过的数据库程序。

业务判断失败也可能没有数据库异常,例如办件早已处理完成。这时应用要检查执行结果,不能仅凭“没有抛异常”就继续写通过记录。

拓展:死锁、超时、已提交和改表操作的边界

deadlock(死锁)是多个事务互相等对方释放资源,无法继续推进;数据库检测后会选择一个事务作为受害者并回滚整个事务。lock wait timeout(锁等待超时)是在等待锁时超过时限,默认只撤销等待失败的语句,整事务回滚可由配置改变。两种情况不能混记。[3]

已经成功提交的事务,不能靠后来的回滚撤销。CREATE TABLE(建表语句)、ALTER TABLE(改表语句)等通常还会触发 implicit commit(隐式提交),因此建表准备应放在演示事务之外;也不要用再次开始事务来“嵌套”一个新事务。[2][9]

提交请求发出后若连接断开,应用未必知道提交到底是否完成;不能仅凭没收到响应就认定全部回滚,也不能直接无条件重做审批。先保留“结果待核对”,以后用业务编号和防重复方案处理。本课可选实验只覆盖连接正常时的确定结果。

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

第二条语句报错不保证第一条也被撤销。普通重复键错误只回滚失败语句,所以业务操作失败后我会明确结束成功分支,请求回滚整个事务并报告失败;不会捕获异常后继续提交。已提交的数据也不能靠下一次回滚撤销。

可在这里暂停。先能说明“两步一起提交+失败明确回滚”,再看下一部分的并发读取。

四、并发读取:先看发生什么,再给现象取名字

“脏读、不可重复读、幻读有什么区别?”

数据库连接A负责查看办件,连接B负责审批。每个连接可以有自己的事务,两个事务会交错执行。下表是三种 现象定义 ,不是说所有隔离级别都会发生。先关注B有没有提交,以及A看到的是同一行的变化,还是符合条件的记录集合变化。[12]

发生了什么现象风险
B把1101状态改为已通过但未提交;A读到已通过;B随后回滚dirty read(脏读)A依据一个后来被撤销的未提交结果做决定
A先读到1101待审批;B改成已通过并提交;A在同一事务再读该行,读到已通过non-repeatable read(不可重复读)同一事务里,同一行先后读到不同的已提交值
A先查部门10的待审批办件;B新增一条符合条件的办件并提交;A在同一事务重复查询,多了一条记录phantom read(幻读)同样条件两次得到不同的记录集合,不只是一行内容改变

不要只背“幻读是条数变化”:即使条数恰好相同,结果集合的成员也可能变化。这里选新增一行,是为了把现象讲清楚。[11]

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

脏读读到了别人尚未提交的修改;不可重复读关注同一事务重复读同一行时,别人提交的修改使值变化;幻读关注按同样条件重复查询时,符合条件的记录集合变化。讨论时要先交代提交顺序和查询方式。

五、隔离级别:两次查询用哪个时间点看数据

“读已提交和可重复读有什么区别?快照什么时候建立?”

先定义 consistent read(一致性读):数据库按规则选择这次查询能看到的已提交版本,也能看到本事务此前的修改。为了容易推演,下文称这个可见版本范围为 snapshot(快照)。它不是每次复制一整张表,而是按可见性规则利用旧版本还原结果。[6][8]

InnoDB(事务存储引擎)默认级别为 REPEATABLE READ(可重复读),但实际连接配置可能被修改,应以连接设置为准。四个级别先掌握下表;本课主要练中间两个。[5]

级别本课先记住的读取规则
READ UNCOMMITTED(读未提交)可能读到其他事务未提交的修改,存在脏读
READ COMMITTED(读已提交)每次一致性读建立新的快照,可能看到两次读取之间别人新提交的修改
REPEATABLE READ(可重复读)同一事务的普通一致性读复用首次此类读取建立的快照;本事务自己的修改仍可见
SERIALIZABLE(可串行化)施加更强隔离。关闭自动提交时,普通查询会转成共享锁定读,可能增加等待;不代表所有事务实际上只用一个线程执行

时间线:第二次读为什么不同

共同前提: 1101初始状态为0(待审批)。A保持同一个显式事务,只执行普通 SELECT(查询语句),不加锁、不修改数据。B改成1(已通过)并提交;没有其他写入。以下是基于官方规则的教学推演。

步骤连接A连接B
①开始事务;第一次查1101,得到0尚未修改
②事务仍未结束修改1101为1,然后提交
③同一事务第二次查1101已经结束

在 READ COMMITTED(读已提交)下,③建立新快照,能读到B已提交的1。在 REPEATABLE READ(可重复读)下,③仍按①建立的快照读取,得到0。A结束事务后再开新事务读取,才能在这个例子里得到更新的1。[6]

首次读,和开始事务,不是同一个动作。 普通 START TRANSACTION(开始事务语句)之后,如果A还没做第一次一致性读,B就提交了1,那么A第一次读会看到1;不能把事务开始时间直接当成快照时间。[6]

拓展:显式建立快照、自己的修改与幻读

START TRANSACTION WITH CONSISTENT SNAPSHOT(开始事务并建立一致性快照语句)可以在开始时请求建立快照;这个用法在 REPEATABLE READ(可重复读)下生效。它与普通开始事务不同,本课时间线未使用它。[2]

本事务自己更新过的行,之后普通查询可以看到自己的修改。所以“可重复读=所有行永远保持第一次读的值”仍然不准确;自己的写入与较老的其他行版本可能一起出现在结果里。[6]

对本课“同一快照上的普通查询、自己不写”的前提,别人后来插入并提交的新行不会突然出现在第二次结果里。不能因此推断所有读写方式下都没有幻读;锁定读用另一套规则,范围保护后续讲。不要用一张抽象级别表代替具体实现。[5][11]

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

对于普通一致性读,读已提交每次读用新快照,可重复读在同一事务复用首次一致性读建立的快照。所以别人两次读之间提交修改,前者可能读到新值,后者仍读旧值。要说明同一事务、普通查询、自己没有写入;普通开始事务也不等于已经建立快照。

六、读写边界:看见待审批,不等于拿到了审批权

“普通查询读到了待审批,下一步直接更新,就不会重复审批了吗?”

不保证。普通一致性读给你可见的版本,其他连接仍可修改对应记录。两个经办人可能先后都读到待审批;稳定地看到旧值,不等于阻止了别人处理。

MVCC(多版本并发控制)保留还原旧行版本所需的信息,让一致性读可以按快照看到合适版本。与之不同,locking read(锁定读)要读取并锁住相应记录,为后续检查和修改提供保护。以按主键找1101为例,SELECT … FOR UPDATE(查询并加更新锁)会读取最新可用数据并加锁;遇到冲突的未结束事务,可能等待。[7][8]

START TRANSACTION;
SELECT status FROM case_item WHERE id = 1101 FOR UPDATE;
-- 取得结果后,仍要确认存在且状态为待审批
-- 再按第三节流程更新状态、插入审批记录、提交;失败则回滚

验证思路:在两个测试连接中按主键查询同一条记录。A在事务内取得更新锁后不结束,B的冲突更新应等待;A结束后B才能继续。实际执行时必须依第三节补全结束分支,不能复制这段后让事务一直挂着;本段是读取步骤示意,不是完整审批程序。

这就是常说的 snapshot read(快照读)与 current read(当前读)的差别:前者按可见版本读;后者用于本文的锁定读取和写操作,不能套用旧快照的普通查询结论。 UPDATE(更新语句)也不能简单理解为只更新第一次查询曾看到的那些旧行。 即使需要等待,取得保护后仍要重新核对状态,不能复用更早一次普通查询的判断。[6][7]

本课不要求背出每种查询锁住哪个范围。先记住:按主键锁定读取只解决本事务的数据库并发保护;锁在提交或回滚后释放。短信已经发出、另一个服务已经执行的动作,不会被本数据库的回滚自动撤销,因此它们不能被当成普通的同库修改。[7][2]

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

普通快照查询不阻止别人审批。需要根据状态决定后续更新时,可以在同一事务里先按主键做更新锁定读取,拿到结果后核对状态,再更新并记审批记录。另一方可能等待,但等到后也必须重新判断业务状态。快照读、锁定读和更新不能混用同一个“永远读旧值”的说法。

七、完整应用:先判断失败,再推演读数

“你会怎样处理审批失败,又怎样复现两种读取结果?”

先做这两个纸上判断,再展开答案;不用从头编写数据库程序。

  1. 同一事务中,状态更新成功,新增记录发生普通重复键错误。代码捕获后打印日志,继续提交。最终可能留下什么?应把流程改在哪里?
  2. A在可重复读的事务里先读到状态0;B改成1并提交;A再普通查询。结果是什么?若只把A改为读已提交,结果又是什么?前提是A不写、不加锁。
核对应用答案

第一题可能留下已通过状态却没有本次记录,因为失败语句被撤销不等于整个事务撤销。失败后应停止成功流程,请求整个事务回滚并报告错误。

第二题可重复读为0,读已提交为1。解释必须落到首次读取的快照是否复用,不能只答“级别高所以安全”。这两项都是已说明前提下的教学推演。

可选实践:两个独立测试连接验证普通查询(约10分钟)

使用已有的隔离测试库与两个独立连接,先确认两个连接均开启自动提交且没有未结束事务。此处一张表只演示读取语义,不是完整审批实现。新表名称固定,若已存在就停止核对,不覆盖或删除;所有数据虚构。任何命令报错都停止后续步骤,在对应连接回滚并保留错误。

-- 准备阶段:连接B,确认处于自动提交模式且没有活动事务
CREATE TABLE teach_tx_0011 (
  id BIGINT PRIMARY KEY,
  status TINYINT NOT NULL
) ENGINE=InnoDB;
INSERT INTO teach_tx_0011 VALUES (1101, 0), (1102, 0);

验证:准备完成后查询两行都应为0;建表放在事务演示之前。1101用于可重复读,1102用于读已提交,避免为了复测覆盖上一轮数据。

-- 步骤1:连接A;先执行到第一次查询,停下
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT status FROM teach_tx_0011 WHERE id = 1101;
-- 预期0

-- 步骤2:切到连接B,单条修改在自动提交模式下成功提交
UPDATE teach_tx_0011 SET status = 1 WHERE id = 1101;

-- 步骤3:回连接A
SELECT status FROM teach_tx_0011 WHERE id = 1101;
-- 同一事务预期仍为0
COMMIT;
SELECT status FROM teach_tx_0011 WHERE id = 1101;
-- 新的查询事务预期为1

验证:预期序列0→0→1。不是整段粘到一个窗口运行,必须按标注切换连接。SET TRANSACTION(设置下一事务特征)不带会话或全局修饰,只影响下一次事务;前提是设置时没有活动事务。[10]

再用1102重复步骤,把A的设置改为 READ COMMITTED(读已提交),预期0→1→1。两轮中B都需成功提交后A才第二次读;保持A没有自己的写入,也不使用锁定读。记录实际结果和连接级别,不能用本段预期冒充已经运行。本课没有提供数据库实测输出,请以自己测试库的结果为准。

推广到其他业务: 办件状态+流转记录、材料状态+审核记录,都可能有成组修改需求。可以另找一组必须一起提交的数据,说明事务边界怎么划;如果只是拟定方案,就明确说“如果这样设计”。本课伪代码是教学示意,不是线上实现。

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

6 道题,每题选项长度一致。先回答,再展开核对;不会的题记下来回到对应小节,不要求反复刷到全对。

  1. 默认开启自动提交,未显式开始事务。先更新状态成功,再插入记录失败,会怎样?
  • A. 两条修改必定全撤销
  • B. 前条修改可能已提交
  • C. 两条语句自动成一组
查看答案与解释

答案:B

默认逐条提交不等于两条合成同一事务。前一条成功后已经可以提交,后面的失败撤不回它。

  1. 显式事务中第二条普通插入报重复键错误,未使用忽略错误选项。应用应怎样处理这次业务失败?
  • A. 停止成功流程并回滚
  • B. 捕获之后继续去提交
  • C. 认定全部自动被撤销
查看答案与解释

答案:A

重复键通常只撤销失败语句。应用应停止成功流程并请求回滚整个业务事务,保留错误。

  1. A在同一可重复读事务里先普通查询到0;B改成1并提交。A不写、不加锁,第二次普通查询读到什么?
  • A. 这次读到一
  • B. 必定读失败
  • C. 仍然读到零
查看答案与解释

答案:C

同一事务的普通一致性读复用首次读取建立的快照;题目前提排除了自己的写入与锁定读。

  1. 第3题只把A改成读已提交,第二次普通查询读到什么?
  • A. 仍然读到零
  • B. 这次读到一
  • C. 必定读失败
查看答案与解释

答案:B

每次一致性读使用新快照,B已在第二次查询前提交,因此这次看见1。

  1. A在可重复读下只执行普通开始事务,尚未读取。B改成1并提交,A这时第一次普通查询,如何判断?
  • A. 开始时锁定旧值
  • B. 开始时复制全表
  • C. 首次读取看到一
查看答案与解释

答案:C

普通开始事务不等于已经建立读取快照。题目未使用开始时显式建立一致性快照的特殊语句。

  1. 普通查询读到待审批,就能保证其他连接无法审批同一办件吗?
  • A. 不能保证写入独占
  • B. 自动锁住整张表格
  • C. 自动阻止其他审批
查看答案与解释

答案:A

普通一致性读不提供这种写入独占。要结合锁定读取或其他并发控制方式,并在取得保护后核对业务状态。

九、用完整回答检验理解

连续回答四问,先不看参考答案。可以写下来,也可以先口述再整理成文字;每问只处理一组相关问题,第二问不再把不同主题挤进一句。写完再对照各节的参考回答。

  1. 审批状态和审批记录怎样一起保存?默认自动提交为什么不够?
  2. 第二条插入普通重复键报错时,你的失败分支怎么结束这次事务?
  3. A先读、B修改并提交、A再读。读已提交和可重复读各看到什么?请明确“同一事务、普通查询、自己不写”这三个前提。
  4. 举一个需要两步成组修改的业务场景。为什么普通查询到待审批还不够,怎样保护后续审批?

练习:请在自己的笔记中写出答案,再展开参考内容核对。

十、集中追问:先说,再展开

答案都在前文

把两条语句分别发给两个数据库连接,再提交其中一个,可以保证一起成功吗?

不能。本课本地事务属于当前连接,要把两步放进同一连接、同一个事务。其他服务或连接不能被自动包含。

事务完整提交了错误的业务数据,是不是只要有原子性就合法?

不是。原子性保证成组提交或撤销,不保证业务逻辑正确。数据还需要满足约束和定义好的规则。

A还在旧快照上读,B是不是必须停止写入等A结束?

本课普通一致性读不以这种方式阻止B修改。数据库可用旧版本提供A的结果;冲突锁定读或写入要另看锁规则。

可重复读事务里,自己先更新了某一行,再普通查询这一行,还只能看到旧值吗?

不是,本事务之前的写入可见。因此解释稳定读数时,要说明自己没有修改被查询数据。

业务事务执行一半去等待用户确认,为什么不合适?

事务可能长时间占锁,并让旧版本不能及时清理。应缩短事务内工作时间;不能只图代码整齐就把所有等待放进去。

读未提交、读已提交、可重复读、可串行化,怎样各说一句?

读未提交可能脏读;读已提交每次一致性读建立新快照;可重复读在同一事务复用首次一致性读的快照,自己的修改例外;可串行化施加更强隔离,在关闭自动提交时普通查询转成共享锁定读。具体行为仍需说明读写方式。

十一、隔日复测:换条件再提取(约5分钟)

隔一天、开始新内容前回答;约一周后再换数字复述。不要在刚看完答案时立刻连续重做。

1. 把办件改成材料审核:材料状态更新成功,审核记录普通插入重复键失败。只打印日志再提交,会有什么风险?

可能只保留材料状态的更新。需要失败分支请求整个事务回滚,不把单条错误当成整组已经撤销。

2. A开始可重复读事务后尚未查询;B把经办人从7改为9并提交;A才第一次普通查询,看到谁?

9。普通开始事务与首次一致性读建立快照分开;题目没有开始时显式建快照。

3. A先普通读到经办人7,B改为9并提交。A在同一事务不写、不加锁,再查一次:两种常用级别各看到谁?

读已提交看到9;可重复读仍看到7。A结束后新事务再读,在没有其他修改时看到9。

第5课延迟复习:核心数3、最大数7,已有3个工作线程都忙,容量6的队列里有2个任务。新任务先去哪?

正常运行且接纳成功的前提下先入队,因为已到核心数而队列还有空间。不直接扩到最大数。见第5课接纳流程。

主来源与引用

本课末尾的课后问答:自动提交、回滚与并发审批。

事务速查表。本课办件、状态编码和动作顺序为教学设定,不是线上项目证据。

  1. 自动提交、提交和回滚:默认模式与成组修改。

  2. 事务控制语句:提交、回滚、连接范围和显式快照。

  3. 错误处理:单语句与整个事务的回滚范围。

  4. 事务四项属性:可靠性职责与配置条件;审批业务规则是课堂应用。

  5. 事务隔离级别:默认值、四个级别及具体读写差异。

  6. 一致性非锁定读:快照建立时机、自己的写入例外。

  7. 锁定读:查询后修改的保护、等待与释放。

  8. 多版本存储:保留旧版本信息、还原旧行与长事务代价。

  9. 隐式提交:改表与开始新事务的边界。

  10. 设置事务特征:只改下一事务的实验设置。

  11. 幻影行:现象与范围保护。

  12. 官方术语表:三种读现象定义,具体级别行为以前述实现章节为准。

学完后,可以沿第五节的时间线,用自己的话解释“B提交以后,A为什么还是0”;卡住时回到时间线逐步核对两个连接的动作。下一课继续 MVCC(多版本并发控制),之后是锁的专题。

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

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

更新办件状态和插入审批记录,默认 autocommit(自动提交)下为什么不能保证两步一起保存?第二条插入报普通重复键错误时,事务又该怎样结束?

最终结论: 默认自动提交不够,是因为两步没有放在同一个事务里,会各自提交。正确做法是在同一连接中显式开始事务,两步都成功才 COMMIT(提交);第二步报错时,停止成功流程,请求 ROLLBACK(回滚)整个事务并报告错误,不能捕获后继续提交。

展开理解过程与易混点

最初理解(归纳): 曾提出显式开始事务、两步成功后提交、失败后回滚整组;但在解释“默认自动提交为什么不够”时,用的是“普通重复键错误只撤销失败语句”这条理由。

纠正与解释: 这是两个不同的问题。默认自动提交、没有显式成组时,第一步状态更新成功后已经可以独立提交,后面的回滚撤销不了一个已提交的事务。放进同一连接的显式 transaction(事务)后,第一步仍是本事务内未提交的修改;这时普通重复键错误只撤销失败的那条语句,应用还要自己请求整组回滚。

最终结论: 默认自动提交不够,是因为两步没有放在同一个事务里,会各自提交。正确做法是在同一连接中显式开始事务,两步都成功才 COMMIT(提交);第二步报错时,停止成功流程,请求 ROLLBACK(回滚)整个事务并报告错误,不能捕获后继续提交。

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

A在同一事务里先普通查询,B修改该行并提交,A再普通查询。READ COMMITTED(读已提交)和 REPEATABLE READ(可重复读)下,A第二次各读到什么?为什么?

最终结论: 读已提交每次一致性读都建立新快照,所以第二次读到B已提交的新值;可重复读在同一事务中复用首次一致性读建立的快照,所以仍看到第一次的旧值。

展开理解过程与易混点

最初理解(归纳): 曾给出两种级别下的读数,但没有说明背后的快照规则。

纠正与解释: 判断要落到 consistent read(一致性读)的快照规则上,而不是只说“级别更高所以读不到”。前提是:同一事务、普通查询、A自己没有写入。

最终结论: 读已提交每次一致性读都建立新快照,所以第二次读到B已提交的新值;可重复读在同一事务中复用首次一致性读建立的快照,所以仍看到第一次的旧值。

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

两个经办人可能同时审批同一件办件。普通查询读到“待审批”后直接更新,能防止重复审批吗?应该怎样保护?

最终结论: 在同一事务内按办件主键执行 SELECT … FOR UPDATE(查询并加更新锁),取得记录后核对是否仍为待审批;满足条件才更新状态、写入审批记录并提交,失败则回滚。另一方如果在等锁,取得锁后必须重新检查本次读到的状态,不能沿用等待之前的判断;若已审批完成,按业务规则返回“已处理”,不重复写通过记录。

展开理解过程与易混点

最初理解(归纳): 曾认为问题出在默认隔离级别,按业务需要调整隔离级别即可。

纠正与解释: 只调隔离级别没有给出保护流程。即使使用读已提交,A和B也可能在任何人更新之前都读到“待审批”;两次读到的值都是已提交的,仍可能基于同一个旧判断继续执行。普通查询看到待审批,不代表阻止了别人处理。

最终结论: 在同一事务内按办件主键执行 SELECT … FOR UPDATE(查询并加更新锁),取得记录后核对是否仍为待审批;满足条件才更新状态、写入审批记录并提交,失败则回滚。另一方如果在等锁,取得锁后必须重新检查本次读到的状态,不能沿用等待之前的判断;若已审批完成,按业务规则返回“已处理”,不重复写通过记录。

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

规则是一件只能审批通过一次、只生成一条通过记录。A、B各自在事务内按主键锁定查询→核对状态→更新→新增记录→提交。A先完成并提交,B取得锁后读到“已通过”。B还要修改吗?B的事务怎样结束?

最终结论: B读到“已通过”后不再修改,向调用方返回“该办件已处理”,并显式以 COMMIT 或 ROLLBACK 结束自己的事务、释放锁;不能只从代码分支返回而让事务一直挂着。

展开理解过程与易混点

最初理解(归纳): 判断B不应继续修改,应正常结束事务并释放锁。

关键追问(归纳): 原题没有写清业务规则,作答时指出了这个缺口;随后补上规则:一件只能审批通过一次、只生成一条通过记录,重复请求提示“该办件已处理”,不再修改。

纠正与解释: B这次没有修改任何数据,所以用 COMMIT(提交)或 ROLLBACK(回滚)都可以结束事务并释放锁。需要注意的是,程序只是从某个代码分支返回,并不自动等于数据库事务已经结束。

最终结论: B读到“已通过”后不再修改,向调用方返回“该办件已处理”,并显式以 COMMIT 或 ROLLBACK 结束自己的事务、释放锁;不能只从代码分支返回而让事务一直挂着。