Files
examination/topics/database/mysql-transaction-isolation/code_reading.json
T
wonder 515acbcc7f
Deploy Examination / deploy (push) Successful in 4s
feat: add database topic group with 105 questions
- mysql-acid (35): undo/redo log, WAL, 崩溃恢复三阶段, 两阶段提交
- mysql-transaction-isolation (35): 四隔离级别, 三异常, Read View, 快照读/当前读, next-key lock
- redis-persistence (35): RDB/AOF/混合持久化, AOF 重写, vs binlog/redo log
- 每子主题: single_choice×10 + fill_blank×10 + short_answer×10 + code_reading×5
- 全部通过 question.schema.json 校验
2026-09-07 19:17:38 +08:00

257 lines
14 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"topic": "mysql-transaction-isolation",
"type": "code_reading",
"schema_version": "1.0.0",
"generated": "2026-09-07T00:00:00+08:00",
"questions": [
{
"id": "cr-001",
"type": "code_reading",
"difficulty": 2,
"tags": [
"隔离级别",
"脏读",
"READ UNCOMMITTED"
],
"question": "隔离级别为 READ UNCOMMITTED 时的脏读场景。假设 stocks 表初始只有一行:id=1, price=100。两个会话按下列顺序执行(期间会话 A 尚未 COMMIT),请回答会话 B 的普通 SELECT 读到什么。",
"code": "-- 会话 B\nSET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;\n-- 会话 A\nSET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;\nBEGIN;\nUPDATE stocks SET price=50 WHERE id=1; -- A 执行,未提交\nSELECT price FROM stocks WHERE id=1; -- B 的普通 SELECT(快照读)\nROLLBACK; -- A 回滚",
"language": "sql",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "short_answer",
"question": "会话 B 的普通 SELECT 读到的 price 是多少?为什么会是这个值?这属于哪种并发异常?",
"answer": "读到 50。因为 READ UNCOMMITTED 允许读取其他事务尚未提交的最新修改,因此 B 的普通 SELECT 会直接读到 A 刚 UPDATE 但还未 ROLLBACK 的值 50,这就是脏读。",
"keywords": [
"READ UNCOMMITTED",
"脏读",
"未提交",
"50",
"ROLLBACK"
],
"scoring_rubric": "答出读到 50(得 50%);说明这是脏读(得 25%);说明原因是未判断未提交(得 25%)。",
"explanation": "READ UNCOMMITTED 下普通 SELECT 不做可见性过滤,直接读到 A 已修改但未提交的值 50;A 随后 ROLLBACK,说明 B 读到的数据从未真正存在过,这正是脏读。"
},
{
"index": 2,
"type": "single_choice",
"question": "关于脏读发生的隔离级别,下列说法正确的是?",
"answer": "A",
"options": {
"A": "只有读未提交级别会出现脏读",
"B": "READ COMMITTED 级别也会允许读到未提交数据",
"C": "脏读在本场景只有在 A 提交后才会发生",
"D": "该场景不会发生脏读,因为没有 commit"
},
"explanation": "脏读只出现在 READ UNCOMMITTED 级别。READ COMMITTED 及以上的快照读不会读到未提交数据。脏读与是否 COMMIT 无关,反而是「未提交仍被读到」才叫脏读。",
"keywords": [
"脏读",
"READ UNCOMMITTED"
]
}
],
"explanation": "本案例展示最低隔离级别的脏读:B 的快照读在 READ UNCOMMITTED 下没有可见性过滤,直接读了 A 未提交的最新值 50,而 A 随后回滚,B 读到的就是脏数据。"
},
{
"id": "cr-002",
"type": "code_reading",
"difficulty": 3,
"tags": [
"隔离级别",
"不可重复读",
"READ COMMITTED"
],
"question": "隔离级别为 READ COMMITTED,scores 表初始有 id=1, score=80。判断 T-B 在同一事务内两次 SELECT 的返回结果。",
"code": "-- 会话 B\nSET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;\n-- 会话 A\nSET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;\nBEGIN;\nUPDATE scores SET score=90 WHERE id=1;\nCOMMIT;\nSELECT score FROM scores WHERE id=1; -- B 第一次 SELECT\nSELECT score FROM scores WHERE id=1; -- B 第二次 SELECT",
"language": "sql",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "short_answer",
"question": "会话 B 在同一个事务内两次 SELECT 分别读到什么值?为什么会出现这种结果?",
"answer": "第一次读到 80(A 提交前的旧值),第二次读到 90(A 已提交后的新值)。因为 READ COMMITTED 每次普通 SELECT 都新建 ReadView,能看到最新已提交的版本,故同一事务内两次结果不同,即不可重复读。",
"keywords": [
"READ COMMITTED",
"不可重复读",
"新 ReadView",
"80",
"90"
],
"scoring_rubric": "正确写出第一次 80、第二次 90(各 3 分);指出原因在于每次 SELECT 新建 ReadView(2 分),说明这是不可重复读(2 分)。",
"explanation": "READ COMMITTED 每次普通 SELECT 都新建 Read View,因此第二次读能看到 A 已提交的新值 90,同一事务内两次读结果不同,即不可重复读。"
},
{
"index": 2,
"type": "single_choice",
"question": "关于本场景产生不可重复读的根本原因,下列说法正确的是?",
"answer": "B",
"options": {
"A": "RC 级别不会产生不可重复读",
"B": "由于两次 SELECT 都新建 ReadView,因此结果不同",
"C": "由于 RR 复用 ReadView,所以本场景结果相同",
"D": "以上都不对"
},
"explanation": "RC 下每次快照读新建 ReadView 是产生不可重复读的根本原因。本场景隔离级别是 RC 而非 RR,不能套用 RR 的 ReadView 复用逻辑。",
"keywords": [
"不可重复读",
"ReadView"
]
}
],
"explanation": "经典不可重复读:READ COMMITTED 每次 SELECT 新建 ReadView,第一次读到 A 提交前的 80,第二次读到 A 提交后的 90,两次结果不一致。"
},
{
"id": "cr-003",
"type": "code_reading",
"difficulty": 4,
"tags": [
"隔离级别",
"可重复读",
"REPEATABLE READ",
"Read View"
],
"question": "隔离级别为 REPEATABLE READ(MySQL 默认),scores 表 id=1, score=80。B 在同一事务内先建立 ReadView,再多次 SELECT,判断每次能否看到 A 提交的新值。",
"code": "-- 会话 B\nSET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;\n-- 会话 A\nSET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;\nBEGIN;\nSELECT score FROM scores WHERE id=1; -- B 第一条 SELECT,建立 ReadView\nUPDATE scores SET score=50 WHERE id=1;\nCOMMIT;\nSELECT score FROM scores WHERE id=1; -- B 快照读,复用 ReadView\nSELECT score FROM scores WHERE id=1; -- B 快照读,仍复用同一 ReadView",
"language": "sql",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "short_answer",
"question": "会话 B 的两次快照读分别读到什么值?请结合 Read View 的创建时机说明原因。",
"answer": "两次快照读都读到 80,而不是 A 提交的 50。因为 RR 下事务 B 第一条 SELECT 构建的 ReadView 在整个事务内复用,A 的修改发生在该 ReadView 建立之后,A 在 B 的 ReadView 中属于不可见事务,因此 B 沿版本链始终读到旧值 80,实现了可重复读。",
"keywords": [
"REPEATABLE READ",
"ReadView 复用",
"80",
"可重复读",
"快照读"
],
"scoring_rubric": "答出两次都读 80(3 分);说明原因是 ReadView 复用且 A 不可见(3 分)。",
"explanation": "REPEATABLE READ 下事务 B 的第一条 SELECT 创建 Read View 并在整个事务内复用;A 的修改事务 ID 大于等于该视图的 max_trx_id(或在 m_ids 中),对 B 不可见,于是 B 沿版本链始终读到旧值 80。"
},
{
"index": 2,
"type": "short_answer",
"question": "如果把隔离级别换成 READ COMMITTED,会话 B 第二次读的结果会变成什么?两种级别行为差异的本质是什么?",
"answer": "如果换成 READ COMMITTED,B 的第二次读就会新建 ReadView,看到 A 已提交的 50,产生不可重复读;而 RR 下复用第一个 ReadView,全程读到 80。两者行为差异的本质就在于 ReadView 的创建时机。",
"keywords": [
"READ COMMITTED",
"每次新建",
"50",
"不可重复读"
],
"scoring_rubric": "指出换成 RC 第二次读到 50(3 分);强调 RC 每次新建 / RR 复用 ReadView 的差异(3 分)。",
"explanation": "差异本质在 Read View 的创建时机:RC 每次快照读都新建,RR 只在事务首次快照读时创建并复用。"
}
],
"explanation": "RC 与 RR 的对照:RR 复用首个 ReadView,快照读看到固定旧值 80,满足可重复读;换成 RC 每次新建 ReadView,则会看到 50。"
},
{
"id": "cr-004",
"type": "code_reading",
"difficulty": 4,
"tags": [
"幻读",
"当前读",
"间隙锁",
"REPEATABLE READ"
],
"question": "REPEATABLE READ 下,T-A 对区间执行当前读 SELECT ... FOR UPDATE,然后 T-B 尝试在该区间内插入一行,考察间隙锁/临键锁对插入的阻塞。表 orders(id, order_no) 已有数据 id=2、id=4(id 不连续)。",
"code": "-- 会话 A\nSET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;\nBEGIN;\n-- 表 orders(id, order_no) 已有数据 id=2 和 id=4\nSELECT * FROM orders WHERE id BETWEEN 1 AND 4 FOR UPDATE;\n-- 会话 B 尝试插入 id=3\nINSERT INTO orders(id, order_no) VALUES(3, 'phantom');",
"language": "sql",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "short_answer",
"question": "会话 B 的 INSERT 会立即成功还是被阻塞?请说明 A 加的是什么锁、锁住了哪个范围。",
"answer": "会话 B 的 INSERT 会因 T-A 持有的间隙锁/临键锁而被阻塞而无法即时成功。SELECT ... FOR UPDATE 是当前读,RR 下会对扫描区间内不存在记录的空隙施加 Gap Lock / Next-Key Lock,B 的 id=3 落在被锁的间隙 (2,4) 内,会被阻塞等待,直到 A 提交或回滚。",
"keywords": [
"间隙锁",
"临键锁",
"阻塞",
"FOR UPDATE",
"幻读"
],
"scoring_rubric": "答出插入被阻塞(3 分);说明原因是临键锁/间隙锁锁住 (2,4) 区间(3 分)。",
"explanation": "SELECT ... FOR UPDATE 是当前读,RR 下会对扫描区间加临键锁(记录锁 + 间隙锁),锁住 (2,4) 等间隙;B 要插入的 id=3 落在被锁间隙内,因此被阻塞直到 A 提交或回滚。"
},
{
"index": 2,
"type": "single_choice",
"question": "关于会话 B 插入 id=3 的执行结果,下列说法正确的是?",
"answer": "B",
"options": {
"A": "B 插入立即成功,因为当前读只锁已存在的行",
"B": "B 插入被阻塞,因为当前读的临键锁锁住了包含 (2,4) 的间隙区间",
"C": "B 插入成功,因为 RR 不会发生幻读所以无需加锁",
"D": "SELECT ... FOR UPDATE 不加锁,不会影响插入"
},
"explanation": "RR 下 FOR UPDATE 当前读会加临键锁(行锁 + 间隙锁),把区间间隙锁住。B 想插入 id=3,落在被锁的 (2,4) 区间内,因此被阻塞。这正是防幻读的锁机制。A、C、D 均错误。",
"keywords": [
"临键锁",
"间隙",
"阻塞"
]
}
],
"explanation": "该例演示 RR 下非连续索引区间利用临键锁锁住间隙,阻断并发插入,实现当前读的幻读防护。"
},
{
"id": "cr-005",
"type": "code_reading",
"difficulty": 3,
"tags": [
"隔离级别",
"脏读",
"快照读"
],
"question": "隔离级别为 REPEATABLE READ,判断 B 能否读到 A 未提交的脏数据(与 READ UNCOMMITTED 对比)。",
"code": "-- 会话 B\nSET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;\n-- 会话 A\nSET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;\nBEGIN;\nUPDATE accounts SET balance=0 WHERE id=1; -- A 执行,未提交\nSELECT balance FROM accounts WHERE id=1; -- B 的普通 SELECT(快照读)",
"language": "sql",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "short_answer",
"question": "会话 B 的普通 SELECT 读到的 balance 是多少?请用 Read View 的可见性判断规则说明原因。",
"answer": "读到的是旧值(例如原 balance),不会读到 A 未提交的 0。因为 REPEATABLE READ 及以上级别不允许脏读,普通 SELECT 走快照读会用 ReadView 判断:A 未提交(在 m_ids 中)不可见,于是沿版本链读到已提交的旧版本。",
"keywords": [
"不可读脏",
"未提交",
"ReadView",
"旧版本"
],
"scoring_rubric": "答读不到未提交(3 分);说明 ReadView 判定 A 不可见(3 分)。",
"explanation": "A 的修改尚未提交,其事务 ID 在 B 的 Read View 的 m_ids 中,判定为不可见;B 沿 roll_ptr 找到上一个已提交版本,因此读到的是旧值而非 A 改的 0,不会发生脏读。"
},
{
"index": 2,
"type": "single_choice",
"question": "关于 REPEATABLE READ 级别下普通 SELECT 的读取行为,下列说法正确的是?",
"answer": "B",
"options": {
"A": "普通 SELECT 一定读到最新未提交的值",
"B": "此级别下事务快照读只可能读到已提交(对本事务可见)的历史版本",
"C": "脏读在 RR 下也会发生",
"D": "普通 SELECT 会加排他锁以防读到脏数据"
},
"explanation": "RR(及 RC)下普通 SELECT 是快照读,只读到通过 ReadView 判定可见的已提交版本,不会读到未提交数据,因此脏读在该级别不会发生。B 正确。",
"keywords": [
"快照读",
"脏读"
]
}
],
"explanation": "对比 READ UNCOMMITTED 与 REPEATABLE READ,RR 及以上级别的普通读不读未提交数据,不发生脏读。"
}
]
}