feat: add database topic group with 105 questions
Deploy Examination / deploy (push) Successful in 4s

- 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 校验
This commit is contained in:
2026-09-07 19:17:38 +08:00
parent 0015449dcf
commit 515acbcc7f
16 changed files with 2951 additions and 1 deletions
@@ -0,0 +1,257 @@
{
"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 及以上级别的普通读不读未提交数据,不发生脏读。"
}
]
}
@@ -0,0 +1,201 @@
{
"topic": "mysql-transaction-isolation",
"type": "fill_blank",
"schema_version": "1.0.0",
"generated": "2026-09-07T00:00:00+08:00",
"questions": [
{
"id": "fb-001",
"type": "fill_blank",
"difficulty": 1,
"tags": [
"隔离级别"
],
"question": "MySQL InnoDB 支持的四级事务隔离级别从低到高分别是:____(读未提交)、____(读已提交)、____(可重复读)、____(串行化),其中 MySQL 默认的隔离级别是____。",
"answer": [
"READ UNCOMMITTED",
"READ COMMITTED",
"REPEATABLE READ",
"SERIALIZABLE",
"REPEATABLE READ"
],
"answer_rule": "ordered",
"explanation": "ISO 定义的四隔离级别:READ UNCOMMITTED(允许脏读)、READ COMMITTED(只读已提交,出现不可重复读)、REPEATABLE READ(可重复读 + 间隙锁防幻读)、SERIALIZABLE(所有读加锁串行)。MySQL InnoDB 默认级别是 REPEATABLE READ。",
"source": null,
"related": []
},
{
"id": "fb-002",
"type": "fill_blank",
"difficulty": 2,
"tags": [
"脏读",
"不可重复读",
"幻读"
],
"question": "事务并发读取产生的三种异常现象分别是:____(读到其他事务未提交的数据)、____(同一事务中同一行两次读取的值不同)、____(同一事务中满足同一条件的行数发生变化,由其他事务插入造成)。",
"answer": [
"脏读",
"不可重复读",
"幻读"
],
"answer_rule": "ordered",
"explanation": "脏读(Dirty Read)读到未提交数据;不可重复读(Non-Repeatable Read)同一行两次值不一致;幻读(Phantom Read)满足条件的行数量发生变化(靠插入)。幻读比不可重复读高级,需要防止插入。",
"source": null,
"related": []
},
{
"id": "fb-003",
"type": "fill_blank",
"difficulty": 2,
"tags": [
"MVCC",
"版本链"
],
"question": "InnoDB 中每行数据通常隐藏着两个关键列:____(最后修改该行的事务 ID)与 ____(指向 undo log 中该行旧版本的指针),多个旧版本通过该指针串成一条____。",
"answer": [
"DB_TRX_ID",
"DB_ROLL_PTR",
"版本链"
],
"answer_rule": "ordered",
"explanation": "DB_TRX_ID 记录最后修改该行的事务 ID,DB_ROLL_PTR 指向 undo log 中的旧版本记录,从而将同一逻辑行的所有历史版本串联成版本链。MVCC 靠 ReadView 沿版本链回溯找到对当前事务可见的版本。",
"source": null,
"related": []
},
{
"id": "fb-004",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"Read View",
"MVCC"
],
"question": "Read View 主要由四个字段构成:____(快照创建时未提交的事务 ID 集合,通常简称 m_ids)、____(该集合里最小的事务 ID)、____(当前已分配的最大事务 ID 加一或最大、用于判断新启动的事务)、____(当前事务的 ID)。",
"answer": [
"m_ids",
"min_trx_id",
"max_trx_id",
"creator_trx_id"
],
"answer_rule": "ordered",
"explanation": "ReadView 的四个核心字段:m_ids(未提交事务集合)、min_trx_id(其中最小 trx_id)、max_trx_id(下一条将要分配的事务 id,或最大 trx_id)、creator_trx_id(创建该 ReadView 的事务自身 id)。可见性判断以这四者为依据。",
"source": null,
"related": []
},
{
"id": "fb-005",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"Read View",
"版本链"
],
"question": "设某行版本的事务 ID 为 trx_id。Read View 判断可见性的顺序中:若 trx_id 等于当前创建 Read View 的事务 ID,则____;若 trx_id < min_trx_id,说明该版本事务已提交,____;若 trx_id 大于等于 max_trx_id,说明该事务在快照创建后才启动,____;若 trx_id 处于 m_ids 集合中,说明该事务还未提交,____。",
"answer": [
"可见",
"可见",
"不可见",
"不可见"
],
"answer_rule": "ordered",
"explanation": "可见性判断顺序:① creator 自己(本事务)修改的版本可见;② trx_id < min_trx_id:已提交,可见;③ trx_id >= max_trx_id:快照之后才启动的事务,不可见;④ trx_id 在 m_ids 中:还未提交,不可见;⑤ 否则可见。若版本链头不可见,则沿 DB_ROLL_PTR 继续向前回溯找可见版本。",
"source": null,
"related": []
},
{
"id": "fb-006",
"type": "fill_blank",
"difficulty": 2,
"tags": [
"快照读",
"当前读"
],
"question": "普通 SELECT(不加锁,基于 MVCC 读一致性快照)称为____;SELECT ... FOR UPDATE / UPDATE / DELETE / INSERT 读取最新已提交数据并加锁,称为____。",
"answer": [
"快照读",
"当前读"
],
"answer_rule": "ordered",
"explanation": "快照读(Snapshot Read,也称一致性非锁定读)走 MVCC,不加锁;当前读(Current Read)读到最新已提交数据并对读取行加锁。当前读在 REPEATABLE READ 下配合间隙锁/临键锁可防止幻读。",
"source": null,
"related": []
},
{
"id": "fb-007",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"隔离级别",
"Read View"
],
"question": "在 ____ 级别下,每执行一次普通 SELECT 都会新建 Read View,因此同一事务两次读取同一行可能得到不同值(不可重复读);在 ____ 级别下,事务的第一条 SELECT 建立 Read View 并在整个事务内复用,从而保证可重复读。",
"answer": [
"READ COMMITTED",
"REPEATABLE READ"
],
"answer_rule": "ordered",
"explanation": "RC:每次快照读新建 ReadView,能看到其他事务最新提交,导致不可重复读。RR:首个 SELECT(或首次访问行数据)创建 ReadView 后复用,整个事务看到的是一致快照,故可重复读。",
"source": null,
"related": []
},
{
"id": "fb-008",
"type": "fill_blank",
"difficulty": 2,
"tags": [
"行锁",
"间隙锁",
"临键锁"
],
"question": "InnoDB 的三种主要锁类型分别是:只锁定某条具体记录的____、锁定索引区间但不锁定具体记录的____(为防其他事务在此区间插入)、两种组合而成的____(同时锁定记录与其之前的区间)。",
"answer": [
"Record Lock(行锁)",
"Gap Lock(间隙锁)",
"Next-Key Lock(临键锁)"
],
"answer_rule": "ordered",
"explanation": "Record Lock 锁定单条记录;Gap Lock 锁定索引间的空隙(防止插入造成幻行,本身不保护已存在的具体行);Next-Key Lock 是 Record Lock 与 Gap Lock 的组合(前开后闭区间),既锁记录又锁间隙,是防幻读的核心手段。",
"source": null,
"related": []
},
{
"id": "fb-009",
"type": "fill_blank",
"difficulty": 4,
"tags": [
"隔离级别",
"幻读"
],
"question": "REPEATABLE READ 下,通过两条路径防止幻读:普通 SELECT 走 MVCC____,读取一致快照;而 SELECT ... FOR UPDATE 等____读则会使用临键锁/间隙锁锁定范围,阻止并发事务在范围内____(插入数据)从而产生幻读。",
"answer": [
"快照读",
"当前读",
"插入"
],
"answer_rule": "ordered",
"explanation": "RR 下防幻读是『快照读一致性』与『当前读加间隙锁』的结合:快照读读到的都是固定的旧快照,不会看到别的新插入行;当前读则通过 Gap Lock / Next-Key Lock 阻塞范围内的并发插入,保证当前读也不会读到幻行。注意这两种机制分开来看,单纯 MVCC 快照读其实不能完全阻止其他事务对旧区间('对本事务而言)被修改而产生的幻行,因此间隙锁仍是必要的。",
"source": null,
"related": []
},
{
"id": "fb-010",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"隔离级别",
"MVCC"
],
"question": "MySQL 事务隔离性本身是通过『锁 + ____』两种机制共同实现的:锁用于解决____(写写)冲突以及当前读场景下的幻读防止(间隙锁/临键锁),而____用于实现多版本并发控制,使普通读(快照读)与写操作可以互不阻塞并行执行。",
"answer": [
"MVCC",
"写",
"MVCC"
],
"answer_rule": "ordered",
"explanation": "锁解决写写互斥与区间防插入,MVCC(Multi-Version Concurrency Control)解决读写之间不互斥(快照读不加锁)。两者叠加才完整支撑 InnoDB 的隔离语义。",
"source": null,
"related": []
}
]
}
@@ -0,0 +1,41 @@
{
"slug": "mysql-transaction-isolation",
"name": "MySQL 事务与隔离机制",
"description": "四种隔离级别、脏读/不可重复读/幻读、MVCC Read View 选版本逻辑、Repeative Read 与 Next-Key Lock",
"tags": [
"MVCC",
"Read View",
"undo log",
"不可重复读",
"临键锁",
"幻读",
"当前读",
"快照读",
"版本链",
"脏读",
"行锁",
"间隙锁",
"隔离级别"
],
"difficulty_range": [
1,
4
],
"schema_version": "1.0.0",
"updated": "2026-09-07",
"question_files": [
"single_choice",
"fill_blank",
"code_reading",
"short_answer"
],
"stats": {
"total": 35,
"by_type": {
"single_choice": 10,
"fill_blank": 10,
"code_reading": 5,
"short_answer": 10
}
}
}
@@ -0,0 +1,213 @@
{
"topic": "mysql-transaction-isolation",
"type": "short_answer",
"schema_version": "1.0.0",
"generated": "2026-09-07T00:00:00+08:00",
"questions": [
{
"id": "sa-001",
"type": "short_answer",
"difficulty": 1,
"tags": [
"隔离级别"
],
"question": "请列出 MySQL InnoDB 支持的四种事务隔离级别,并按隔离强度从低到高说明它们的名称(中英文均可)。",
"answer": "四种隔离级别从低到高依次是:READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读,MySQL 默认)、SERIALIZABLE(串行化)。",
"keywords": [
"READ UNCOMMITTED",
"READ COMMITTED",
"REPEATABLE READ",
"SERIALIZABLE",
"默认"
],
"scoring_rubric": "正确列出四个名称各 2 分(共 8 分);答对从低到高顺序并指出 REPEATABLE READ 是 MySQL 默认级别得 2 分。",
"explanation": "四个隔离级别的强度递增:RU 最低允许脏读;RC 只读已提交但出现不可重复读;RR 额外用 MVCC 快照与间隙锁/临键锁防止幻读(MySQL InnoDB 默认);SERIALIZABLE 最高,所有读都加锁串行。"
},
{
"id": "sa-002",
"type": "short_answer",
"difficulty": 2,
"tags": [
"脏读",
"不可重复读",
"幻读"
],
"question": "请分别解释事务并发过程中出现的三个异常现象:脏读、不可重复读、幻读。",
"answer": "脏读:一个事务读到了另一个事务尚未提交(可能回滚)的数据。不可重复读:同一事务内,两次读取同一行数据得到不同的值,因为期间有其他事务修改并提交了该行。幻读:同一事务内,两次执行同一条件查询得到的行数不同,因为另一事务在期间插入或删除了满足条件的行(幻读主要由插入产生)。",
"keywords": [
"脏读",
"不可重复读",
"幻读",
"未提交",
"行数变化"
],
"scoring_rubric": "三个现象各 3 分,正确描述现象并点出各自关键特征(脏读=未提交、不可重复读=同一行值变化、幻读=行数变化)得满分;部分答对按要点给分。",
"explanation": "三者隔离强度递进:脏读最严重(连提交状态都不判断);不可重复读是同一行值变化;幻读是结果集行数变化。SERIALIZABLE 能杜绝全部三种;RR 靠快照读 + 间隙锁基本防住幻读;RC 会发生不可重复读。"
},
{
"id": "sa-003",
"type": "short_answer",
"difficulty": 3,
"tags": [
"隔离级别",
"脏读",
"不可重复读",
"幻读"
],
"question": "请分别说明四种隔离级别各自允许哪些并发异常(脏读、不可重复读、幻读)。",
"answer": "READ UNCOMMITTED:三种异常都可能出现(脏读、不可重复读、幻读)。READ COMMITTED:不允许脏读,但可能出现不可重复读;对幻读在 MySQL 的语义下基本不设防。REPEATABLE READ:不允许脏读与不可重复读,通过 MVCC 快照读与间隙锁/临键锁基本防止了幻读。SERIALIZABLE:三种异常都不允许,所有读都加锁,事务串行执行。",
"keywords": [
"脏读",
"不可重复读",
"幻读",
"各隔离级别",
"SERIALIZABLE"
],
"scoring_rubric": "每个级别正确描述其允许/禁止的异常得 2.5 分(共 10 分),需同时说对允许或禁止哪些现象。",
"explanation": "隔离级别由低到高允许的异常:RU 全允许;RC 除脏读外其余可发生;RR 基本不允许脏读和不可重复读,幻读由 MVCC+锁基本消除;SERIALIZABLE 全禁止。"
},
{
"id": "sa-004",
"type": "short_answer",
"difficulty": 3,
"tags": [
"MVCC",
"版本链",
"undo log"
],
"question": "简述 MySQL InnoDB 的 MVCC(多版本并发控制)是什么?它依赖哪些隐藏列形成版本链?有何作用?",
"answer": "MVCC 即多版本并发控制,核心思路是让普通 SELECT(快照读)通过读取历史版本与写操作并行且不互斥。每行会隐藏两个列:DB_TRX_ID(最后修改该行的事务 ID)和 DB_ROLL_PTR(指向 undo log 中旧版本的指针),多个历史版本通过 DB_ROLL_PTR 串成一条版本链。快照读依据 Read View 沿版本链找到对当前事务可见的那个版本。作用是实现读写不互斥,提高并发度,并支撑 RC 的读已提交与 RR 的可重复读。",
"keywords": [
"MVCC",
"DB_TRX_ID",
"DB_ROLL_PTR",
"版本链",
"undo log",
"读写不互斥"
],
"scoring_rubric": "说出 MVCC 使读写不互斥(2 分)、两个隐藏列 DB_TRX_ID/DB_ROLL_PTR(各 2 分)、版本链与 undo log(2 分)、讲清作用(2 分)。",
"explanation": "MVCC 是不加锁并发读的重要机制。undo log 保存旧版本,DB_ROLL_PTR 指向它们,从而把同一行的所有历史版本串成一条版本链。"
},
{
"id": "sa-005",
"type": "short_answer",
"difficulty": 4,
"tags": [
"Read View",
"版本链"
],
"question": "请描述 Read View 的四个核心字段,并用它们说明 MVCC 判断某行版本可见性的规则。",
"answer": "Read View 四个字段:m_ids(创建快照这一刻尚未提交的事务 ID 集合)、min_trx_id(m_ids 中最小的事务 ID)、max_trx_id(下一条将要分配的事务 ID 或最大事务 ID)、creator_trx_id(当前创建 ReadView 的事务自身 ID)。可见性判断顺序:一是版本 trx_id 等于 creator_trx_id(自己改的)则可见;二是 trx_id 小于 min_trx_id(该事务已提交且在快照前)则可见;三是 trx_id 大于等于 max_trx_id(快照之后才启动的事务)则不可见;四是 trx_id 处于 m_ids 集合中(尚未提交)则不可见;五是否则可见。若当前版本不可见,则沿 DB_ROLL_PTR 向版本链更旧版本回溯继续判断。",
"keywords": [
"m_ids",
"min_trx_id",
"max_trx_id",
"creator_trx_id",
"可见性"
],
"scoring_rubric": "正确写出四个字段各 1 分(共 4 分);可见性规则每条(自己可见、已提交可见、未提交不可见、新事务不可见)约 1.5 分,共约 6 分。",
"explanation": "ReadView 是 MVCC 可见性判断的核心,关键在于顺序判断:trx_id 小于 min 可见、大于等于 max 不可见、在 m_ids 中不可见。掌握它才能理解 RC 与 RR 的差异。"
},
{
"id": "sa-006",
"type": "short_answer",
"difficulty": 4,
"tags": [
"隔离级别",
"Read View"
],
"question": "为什么 READ COMMITTED 会发生不可重复读,而 REPEATABLE READ 不会?请从 Read View 的创建时机角度解释。",
"answer": "因为两者创建 ReadView 的时机不同。READ COMMITTED 下每执行一次普通 SELECT(快照读)都会新建一个 ReadView,因此后一次的 SELECT 能看到其他事务最新提交的数据,导致同一事务内两次读到不同值,即产生不可重复读。REPEATABLE READ 下,事务的第一条 SELECT 才创建 ReadView,之后整个事务内所有快照读都复用这一份 ReadView,其他事务的提交对这份固定快照不可见,因此多次读取结果一致,即满足可重复读。",
"keywords": [
"新 ReadView",
"复用",
"每次 SELECT",
"不可重复读"
],
"scoring_rubric": "指出 RC 每次都新建 ReadView(3 分)、RR 复用首次的 ReadView(3 分)、并正确连接到不可重复读与可重复读现象(4 分)。",
"explanation": "这正是 RC 与 RR 在 MVCC 上最本质的区别:ReadView 的创建时机与复用策略决定了可见版本集合的刷新频率。"
},
{
"id": "sa-007",
"type": "short_answer",
"difficulty": 3,
"tags": [
"快照读",
"当前读"
],
"question": "请解释 MySQL 中快照读与当前读的区别,并各举一个例子。",
"answer": "快照读(Snapshot Read,也称一致性非锁定读):普通的 SELECT,不加任何锁,通过 MVCC 读取一致性快照(依据 ReadView),不会因其它事务的写操作而阻塞。例子:SELECT * FROM t(不带 FOR UPDATE 或 LOCK IN SHARE MODE)。当前读(Current Read):读取数据的最新已提交版本,并在读取后被访问的影响记录加锁,属于加锁读。例子:SELECT ... FOR UPDATE(排X锁)、SELECT ... LOCK IN SHARE MODE(共享锁),以及 UPDATE、DELETE、INSERT 语句本身就是当前读。核心区别:快照读走 MVCC 不加锁,当前读走锁、读最新提交数据。",
"keywords": [
"快照读",
"当前读",
"MVCC",
"加锁",
"FOR UPDATE"
],
"scoring_rubric": "将快照读解释为 MVCC 不加锁(3 分)、当前读解释为加锁读最新(3 分)、各举正确例子(4 分)。",
"explanation": "快照读与当前读是理解 MySQL 并发读与锁行为的关键。注意 UPDATE/DELETE/INSERT 必须读最新提交并加锁,避免基于旧数据覆盖。"
},
{
"id": "sa-008",
"type": "short_answer",
"difficulty": 4,
"tags": [
"临键锁",
"间隙锁",
"行锁",
"幻读"
],
"question": "解释 Record Lock、Gap Lock、Next-Key Lock 三者的区别,以及它们如何配合防止幻读。",
"answer": "Record Lock(行锁):只锁定某一条具体记录,阻止对该行的修改。Gap Lock(间隙锁):锁定某个索引区间内的空隙(不含已存在的记录),阻止其他事务在这个空隙内插入新行,但它本身不保护具体记录,且间隙锁之间通常不互相排斥。Next-Key Lock(临键锁):是 Record Lock 与 Gap Lock 的组合,防护左开右闭区间,同时锁定记录本身及其之前的间隙。防幻读机制:REPEATABLE READ 下的当前读(如 SELECT ... FOR UPDATE、UPDATE、DELETE)会加 Next-Key 锁,把范围内所有可能插入的位置锁住,从而阻止并发事务插入新行,避免出现幻行。",
"keywords": [
"Record Lock",
"Gap Lock",
"Next-Key Lock",
"防幻读",
"间隙"
],
"scoring_rubric": "分别描述三种锁的特征各 2 分(共 6 分);解释临键锁如何通过间隙锁阻止插入防幻方 4 分。",
"explanation": "Next-Key Lock 等于 Record Lock 加 Gap Lock,是 RR 下当前读防幻读的关键。间隙锁专为堵插入而设计。"
},
{
"id": "sa-009",
"type": "short_answer",
"difficulty": 2,
"tags": [
"隔离级别"
],
"question": "如果业务需要最严格的隔离保证(不允许脏读、不可重复读、幻读),你会选择哪个隔离级别?代价是什么?",
"answer": "选择 SERIALIZABLE(串行化)。它是最高隔离级别,会强制将所有普通 SELECT 也转为加锁读(等价于给读取范围加共享锁并配合临键锁/间隙锁),读写完全互斥、事务近乎串行,从而杜绝脏读、不可重复读和幻读。代价是并发度大幅下降,读操作之间也互斥,容易引发锁竞争、阻塞甚至死锁,吞吐量明显低于其他级别;因此若非必要一般不直接使用,可在业务层用更精细的锁或优化 SQL 替代。",
"keywords": [
"SERIALIZABLE",
"串行化",
"加锁读",
"并发下降",
"代价"
],
"scoring_rubric": "答对选择 SERIALIZABLE(3 分);说明其加锁机制保证全隔离(3 分);指出以并发与性能为代价(4 分)。",
"explanation": "SERIALIZABLE 是安全但昂贵的:全部读都加锁,牺牲了 MVCC 的读写并发优势,只适合极苛刻场景。"
},
{
"id": "sa-010",
"type": "short_answer",
"difficulty": 3,
"tags": [
"MVCC",
"隔离级别",
"快照读"
],
"question": "InnoDB 的 MVCC 为什么在 READ COMMITTED 与 REPEATABLE READ 下都能工作?两者基于 MVCC 提供的可读行为有何区别?",
"answer": "MVCC 是 InnoDB 中通过每行版本链(DB_TRX_ID + DB_ROLL_PTR)与 Read View 判断可见性的通用机制,因此在 RC 与 RR 两个隔离级别下都会启用 MVCC。行为区别:RC 每次普通 SELECT 新建 ReadView,能读其他事务最新已提交的版本,产生不可重复读;RR 在事务第一条 SELECT 建立 ReadView 并全程复用,因此整个事务内快照一致,满足可重复读。两者 MVCC 基础设施相同,但 ReadView 的创建/复用规则不同,导致对外行为不同。",
"keywords": [
"MVCC",
"ReadView",
"RC 每次",
"RR 复用",
"可重复读"
],
"scoring_rubric": "说明 MVCC 在两个级别均启用(3 分);RC 每次新建 ReadView 的行为(3 分);RR 每次复用 ReadView 的行为(4 分)。",
"explanation": "MVCC 是基础,真正区分 RC/RR 语义的是 ReadView 的创建时机与复用策略,理解这点就掌握了 MySQL 并发读的核心。"
}
]
}
@@ -0,0 +1,211 @@
{
"topic": "mysql-transaction-isolation",
"type": "single_choice",
"schema_version": "1.0.0",
"generated": "2026-09-07T00:00:00+08:00",
"questions": [
{
"id": "sc-001",
"type": "single_choice",
"difficulty": 1,
"tags": [
"隔离级别",
"脏读"
],
"question": "MySQL InnoDB 默认的事务隔离级别是下列哪一项?",
"options": {
"A": "READ UNCOMMITTED",
"B": "READ COMMITTED",
"C": "REPEATABLE READ",
"D": "SERIALIZABLE"
},
"answer": "C",
"explanation": "MySQL InnoDB 存储引擎的默认隔离级别是 REPEATABLE READ(可重复读)。在该级别下,同一事务内多次普通 SELECT 读取到的数据是一致的(快照读复用 Read View),并通过 MVCC + 间隙锁/临键锁在一定程度上防止了幻读。注意:虽然 InnDB 默认是可重复读,但 MySQL 官方文档同时指出 InnoDB 在该级别下结合唯一索引的临键锁几乎已能达到可串行化效果。",
"source": null,
"related": []
},
{
"id": "sc-002",
"type": "single_choice",
"difficulty": 1,
"tags": [
"脏读"
],
"question": "下列关于「脏读」的描述,正确的是?",
"options": {
"A": "事务 A 读到事务 B 已提交的新数据",
"B": "事务 A 读到事务 B 尚未提交(可能回滚)的数据",
"C": "同一事务内两次读同一行的值发生变化",
"D": "同一事务内满足条件的行数发生变化"
},
"answer": "B",
"explanation": "脏读(Dirty Read)指一个事务读取到了另一个事务尚未提交的数据。由于该事务可能回滚,读到的值很可能是『脏』的、最终不存在的数据,因此称为脏读。选项 A 描述的是已提交数据,不算脏读;选项 C 是不可重复读;选项 D 是幻读。脏读只在最低的 READ UNCOMMITTED 隔离级别下发生。",
"source": null,
"related": []
},
{
"id": "sc-003",
"type": "single_choice",
"difficulty": 2,
"tags": [
"隔离级别",
"不可重复读"
],
"question": "在 READ COMMITTED(读已提交)隔离级别下,下列哪种现象仍然可能发生?",
"options": {
"A": "脏读",
"B": "不可重复读",
"C": "幻读",
"D": "以上都不会发生"
},
"answer": "B",
"explanation": "READ COMMITTED 通过只读已提交的数据消除了脏读,但每次普通 SELECT 都会新建 ReadView,因此同一事务内两次读取同一行,若期间有其他事务提交了更新,读取的值会不同,即产生不可重复读。幻读在某些定义下在 RC 级别并不存在(RC 只会插入符合条件的新行且事务无法保证前者被读出来的行数),但按照 MySQL 隔离级别标准,RC 允许不可重复读;选项 C 幻读严格发生在 REPEATABLE READ 以下,RC 级别在并发插入时也会出现类似幻读现象,但本题考察的核心隔离级别行为差异,正确答案选 B。",
"source": null,
"related": []
},
{
"id": "sc-004",
"type": "single_choice",
"difficulty": 2,
"tags": [
"幻读",
"间隙锁",
"临键锁"
],
"question": "REPEATABLE READ(可重复读)隔离级别下,InnoDB 主要依靠什么机制来防止幻读?",
"options": {
"A": "完全是普通的行锁 Record Lock",
"B": "MVCC 快照读 + 间隙锁(Gap Lock)/ 临键锁(Next-Key Lock)",
"C": "每次查询都加全表锁",
"D": "完全依靠组合唯一约束"
},
"answer": "B",
"explanation": "幻觉读(Phantom Read)是指同一事务内,满足相同条件的行数发生了增减(通过其他事务的插入实现)。InnoDB 在 REPEATABLE READ 下通过两条路径防止幻读:(1) 普通 SELECT 走快照读的 MVCC,读到的是一次快照;(2) 当前读(SELECT ... FOR UPDATE / UPDATE / DELETE)使用间隙锁 Gap Lock 与临键锁 Next-Key Lock(行锁 + 间隙锁)锁定区间,阻止并发事务在区间内插入新行。选项 A 忽略了间隙锁部分,选项 C 不准确,D 与本题无关。",
"source": null,
"related": []
},
{
"id": "sc-005",
"type": "single_choice",
"difficulty": 2,
"tags": [
"隔离级别",
"脏读"
],
"question": "下列哪个隔离级别允许脏读发生?",
"options": {
"A": "READ UNCOMMITTED",
"B": "READ COMMITTED",
"C": "REPEATABLE READ",
"D": "SERIALIZABLE"
},
"answer": "A",
"explanation": "READ UNCOMMITTED(读未提交)是对隔离要求最低的级别,它允许事务读取到其他事务尚未提交的数据,即允许脏读。该级别虽然并发性能较高,但语义上几乎难以保证正确性,因此上生产环境通常不用它。BCD 三个级别都不允许脏读。",
"source": null,
"related": []
},
{
"id": "sc-006",
"type": "single_choice",
"difficulty": 3,
"tags": [
"MVCC",
"Read View",
"版本链"
],
"question": "下列关于 MySQL InnoDB MVCC 的说法,正确的是?",
"options": {
"A": "MVCC 使得读写操作完全相互排斥,性能下降",
"B": "每一行数据通常隐藏 DB_TRX_ID(最后修改事务 ID)与 DB_ROLL_PTR(指向 undo log 旧版本)两个隐藏列,共同构成版本链",
"C": "MVCC 只在 SERIALIZABLE 隔离级别下有效",
"D": "每次 UPDATE 都会覆盖旧版本数据,不保留历史版本"
},
"answer": "B",
"explanation": "MVCC(多版本并发控制)的核心思想是让普通读(快照读)与写操作不用互相阻塞,从而显著提高并发性能,故 A 错误。InnoDB 会为每行隐藏 DB_TRX_ID(最后修改该行的事务 ID)与 DB_ROLL_PTR(指向 undo log 中旧版本的指针),由此形成一条版本链,快照读通过与 ReadView 比对找到可见的版本,故 B 正确。MVCC 在 READ COMMITTED 与 REPEATABLE READ 级别下都发挥作用,并非只在 SERIALIZABLE 下有效,故 C 错误。UPDATE 实际上会给行生成新版本并通过 DB_ROLL_PTR 指向旧版本,并不会无条件覆盖历史版本,故 D 错误。",
"source": null,
"related": []
},
{
"id": "sc-007",
"type": "single_choice",
"difficulty": 3,
"tags": [
"Read View",
"快照读"
],
"question": "关于 MySQL 中快照读(Snapshot Read)与当前读(Current Read)的说法,正确的是?",
"options": {
"A": "普通 SELECT 属于当前读,会加锁",
"B": "SELECT ... FROM t FOR UPDATE 属于快照读,走 MVCC 不加锁",
"C": "普通 SELECT 属于快照读,走 MVCC 不加锁;SELECT ... FOR UPDATE / UPDATE / DELETE / INSERT 属于当前读,走锁读最新已提交数据",
"D": "快照读和当前读都会对读取的行加排他锁"
},
"answer": "C",
"explanation": "快照读(快照读)即普通 SELECT,它借助 MVCC 读取某个一致性快照,不加锁,因此称为非锁定读;而当前读是指需要读取数据最新已提交版本并在读取后对该行加锁的读取方式,包括 SELECT ... FOR UPDATE(加排他锁)、SELECT ... LOCK IN SHARE MODE(加共享锁)、以及 UPDATE / DELETE / INSERT。快照读在 REPEATABLE READ 下复用同一 ReadView,实现可重复读;当前读无论隔离级别如何都会读到最新已提交(或本事务)的数据,并可能触发间隙锁等。故 C 正确。",
"source": null,
"related": []
},
{
"id": "sc-008",
"type": "single_choice",
"difficulty": 4,
"tags": [
"Read View",
"版本链",
"MVCC"
],
"question": "假设 Read View 的快照包含:m_ids = {100, 101},min_trx_id = 100,max_trx_id = 102,creator_trx_id = 99。对于一个事务 ID 为 100 的事务修改过的数据行,该快照读时该行的可见性判断是?",
"options": {
"A": "可见,因为 100 小于 max_trx_id",
"B": "不可见,因为 100 在 m_ids 集合中(尚未提交)",
"C": "可见,因为在 m_ids 中属于已提交事务",
"D": "不可见,因为没有该行数据"
},
"answer": "B",
"explanation": "MVCC Read View 可见性判断规则依次为:① 当前事务自己修改的(叶子 trx_id == creator_trx_id)可见;② trx_id < min_trx_id 说明已早期提交,可见;③ trx_id >= min_trx_id 不可见;④ trx_id 在 m_ids 集合中说明该事务尚未提交,不可见;⑤ 否则可见。本题中 trx_id = 100 恰好等于 min_trx_id 且在 m_ids 集合中,属于未提交事务,因此该行对当前 Read View 不可见(但是对自身事务可见)。B 正确。",
"source": null,
"related": []
},
{
"id": "sc-009",
"type": "single_choice",
"difficulty": 4,
"tags": [
"隔离级别",
"Read View"
],
"question": "下列关于 READ COMMITTED 与 REPEATABLE READ 在 ReadView 上的区别,陈述正确的是?",
"options": {
"A": "RC 每次普通查询都新建 ReadView,RR 在事务第一条 SELECT 时建立 ReadView 并复用",
"B": "RC 在事务开始时建立 ReadView,RR 每次查询都新建",
"C": "二者都复用首个 ReadView",
"D": "二者每次查询都新建 ReadView"
},
"answer": "A",
"explanation": "ReadView 的创建时机是 RC 与 RR 的关键差异:READ COMMITTED 下每执行一次普通 SELECT(快照读)都会新建一个 ReadView,因此可以读到其他事务最新提交的数据,产生不可重复读;REPEATABLE READ 下,事务第一条 SELECT(或数据变更触发时)构建 ReadView,之后整个事务中所有快照读都复用这份 ReadView,从而实现对同一条快照的一直读,满足可重复读。这也是 InnoDB 中 RR 通过快照实现可重复读的基础。",
"source": null,
"related": []
},
{
"id": "sc-010",
"type": "single_choice",
"difficulty": 2,
"tags": [
"隔离级别",
"脏读",
"临键锁"
],
"question": "SERIALIZABLE(串行化)隔离级别与 MySQL InnoDB 实现中最相符的描述是?",
"options": {
"A": "仅对写操作加锁,读完全不加锁",
"B": "使所有普通 SELECT 也被强制转为加锁读(等价当前读),并对范围加临键锁,实时上所有读写操作都是互斥串行执行",
"C": "该级别也依赖 MVCC 快照读实现幻读防止",
"D": "只阻止脏读,不阻止幻读,所有读写通用 CS 频繁冲突"
},
"answer": "B",
"explanation": "SERIALIZABLE 是最严格的隔离级别,它本质上将所有普通 SELECT 也自动升级为加锁读(等价于 SELECT ... LOCK IN SHARE MODE 的行为),读写完全相互阻塞,并配合间隙锁/临键锁串行化整个事务执行,因此既不会出现脏读、不可重复读,也不会出现幻读。选项 C 表面上在说 MVCC,但该级别下快照读会被锁读替代,不再单纯依赖 MVCC 快照,因此不准确;选项 D 错误。",
"source": null,
"related": []
}
]
}