Files
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

254 lines
18 KiB
JSON
Raw Permalink 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": "redis-persistence",
"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": [
"AOF",
"AOF重写",
"命令序列",
"状态快照"
],
"question": "请阅读下面这段 AOF 文件里按时间顺序追加的命令流。假设这些命令都已被 Redis 执行并写入 AOF,随后触发了一次 AOF 重写(BGREWRITEAOF)。",
"code": "SET user:1001 zhangsan\nSET user:1001 zhangsanfeng\nSET user:1002 lisi\nDEL user:1001\nINCR view:homepage\nSET counter 5\nINCRBY counter 1\nDEL counter",
"language": "plaintext",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "经过 AOF 重写后,新 AOF 文件里针对 user:1001 这条 key 记录的命令是什么?",
"options": {
"A": "SET user:1001 zhangsan 和 SET user:1001 zhangdanfeng(保留两条)",
"B": "DEL user:1001(因为最后被删除了)",
"C": "SET user:1001 zhangdanfeng(最终值)",
"D": "不写该 key(因为最终不存在)"
},
"answer": "D",
"explanation": "AOF 重写不再按命令逐条重放,而是遍历当前内存数据库的状态,为每个 key 生成一条能重建其最终值的命令。user:1001 的最后一条命令是 DEL,即该 key 当前已不存在,因此重写后根本不生成任何关于 user:1001 的命令。A 保留了被覆盖的旧命令,B 生成一条 DEL 是错误的(key 已无,无需再删),C 误以为留下中间值,都错。"
},
{
"index": 2,
"type": "short_answer",
"question": "重写后,counter 和 view:home 这两个 key 对应的命令分别是什么?为什么 AOF 重写能把这些命令从 8 条大幅精简?",
"answer": "重写后:counter 被最后一条 DEL 删除,key 已不存在,所以不生成命令;view:home 只剩 INCRBY view:home 1。\n\n能大幅精简的根本原因:AOF 存的是『命令序列』(append-only log),大量早期命令的中间结果会被后续命令覆盖(user:1001 被反复覆盖后删除、counter 被 INC/INCRBY 后再 DEL)。AOF 重写遍历当前内存哈希表,为每个 key 只生成一条『重建当前最终状态』的最小命令,把存储的具体『操作时间线』等价转换为『键值状态快照』,冗余的历史操作被丢弃,文件因此变小,恢复也更快。",
"explanation": "本题考察 AOF 重写的本质:将『命令序列』重新组织为『状态快照』。counter 终态不存在故无命令;只有仍存在的 key 才生成最小重建命令。压缩的核心依据是:大量命令对同一 key 是互相覆盖的,只保留最终值就能恢复一致状态。",
"keywords": [
"不存在",
"状态快照",
"命令序列",
"重建最终值",
"最小命令",
"覆盖",
"压缩"
],
"scoring_rubric": "①答出 counter 不存在、view 为 INCRBY 得 1 分;②答出 counter key 已删不生成命令得 1 分;③说明重写是把命令序列转换为键值状态快照、丢弃被覆盖的中间命令、文件变小得 3 分。"
}
],
"explanation": "AOF 本身是一条一条 append 命令的时间线(log 结构),AOF 重写则遍历当前内存把时间线精简为哈希的 key→最终状态。凡是被删除或被后续命令覆盖的中间命令全部丢弃,达到『压缩』。这是理解 AOF 能压缩的本质(log 结构→状态快照)。"
},
{
"id": "cr-002",
"type": "code_reading",
"difficulty": 3,
"tags": [
"AOF",
"RDB",
"恢复",
"持久化配置"
],
"question": "下面是一台 Redis 实例的持久化配置(精简展示)。请阅读配置并回答进程崩溃重启时的数据恢复行为。",
"code": "# redis.conf 片段\nsave 900 1\nsave 300 10\nappendonly no",
"language": "bash",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "以上配置下,若 Redis 距离上一次 RDB 快照 10 分钟时进程宕机,最多可能丢失多少数据?",
"options": {
"A": "0,因为 Redis 每次写都实时落盘",
"B": "最近 5 分钟内写入的所有数据(有 save 300 的兜底)",
"C": "从上一次成功完成 RDB 快照之后到宕机前写操作的所有数据",
"D": "最后 30 秒的数据"
},
"answer": "C",
"explanation": "本配置中 appendonly 为 no(关闭),只靠 RDB 快照持久化。RDB 是『定时的全量快照』,save 300 1 表示 300 秒内至少有 1 次写才触发一次生成点。宕机时只恢复到最近一次成功快照,快照之后的所有写操作全部丢失。所以『最长可能丢失上一次快照到宕机之间的全部写数据』(选 C)。A 错误,Redis 不是每次写都落盘;B 混淆了 save 触发间隔与丢数据范围;D 是 AOF 场景不适用于这里。"
},
{
"index": 2,
"type": "short_answer",
"question": "同样的业务若想几乎不丢数据,应如何调整配置?为什么?",
"answer": "开启 AOF(将 appendonly 改为 yes),并根据数据重要程度选择同步策略,例如 appendfsync always(每条写命令都 fsync,最安全但性能低,几乎不丢)或 everysec(每秒同步,最多丢约 1 秒数据)。原因:RDB 是周期快照,快照间隔之间的数据没有任何日志兜底;AOF 是追加写命令日志,崩溃后通过重放命令恢复,丢失窗口被压缩到 appendfsync 决定的粒度(always=几乎不丢,everysec≤1s,如果需要可用 appendfsync no 让系统决定)。",
"explanation": "本题考察 RDB vs AOF 在数据安全上的取舍。开启 AOF 后,丢失窗口会从『上一次快照点』缩小到『appendfsync 策略决定的粒度』。平常推荐 everysec(性能/安全平衡),绝对不可丢的选 always。",
"keywords": [
"appendonly yes",
"appendfsync",
"everysec",
"always",
"重放",
"丢失窗口"
],
"scoring_rubric": "①答出开启 appendonly/AOF 得 1 分;②答出 appendfsync 取值及对应丢失窗口得 2 分;③说明 AOF 基于命令重放还原、丢失窗口从 RDB 快照周期缩小到同步粒度得 2 分。"
}
],
"explanation": "RDB 快照与 AOF 日志在『丢数据范围』上差异极大:RDB 丢到上次快照,AOF 丢到 appendfsync 粒度。本题考查在给定持久化配置下计算最大丢数据量、以及如何调整降低丢失。"
},
{
"id": "cr-003",
"type": "code_reading",
"difficulty": 3,
"tags": [
"redo log",
"环形缓冲",
"崩溃恢复",
"循环复用"
],
"question": "下面用伪代码描述 InnoDB redo log 的『环形』复用思想。阅读并回答:为什么 redo log 不像 AOF 那样无限膨胀、也不像 AOF 需要主动重写压缩。",
"code": "# 伪代码:redo 环形缓冲区覆盖逻辑\ndef write_redo(head_lsn, tail_lsn, capacity):\n # 环形缓冲区大小固定(如 1GB)\n while (head_lsn - tail_lsn) >= capacity:\n flush_dirty_page_to_disk() # 强制刷脏页,推进 rev per tail/checkpoint\n append_record(page_lsn)\n head_lsn = head_lsn + 1",
"language": "readtext",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "redo log 采用『环形固定大小』覆盖最旧记录,覆盖旧记录最典型的前提条件是什么?",
"options": {
"A": "只要文件满了就直接覆盖最旧记录,不考虑数据页是否已落盘",
"B": "被覆盖的记录所对应的脏页必须先成功刷回磁盘(checkpoint 推进),否则崩溃会丢数据",
"C": "先把 redo log 文件长度翻倍再覆盖",
"D": "每次启动都清空整个 redo 文件"
},
"answer": "B",
"explanation": "redo log 用于崩溃后重放:它记录的是尚未安全刷盘的数据页修改。覆盖旧记录的前提是,这些旧记录对应的脏页已经成功刷到数据文件(即 checkpoint 位置推进),否则一旦被覆盖而对应页面还没落盘,崩溃后就无法重放、造成永久丢失。所以先触发脏页刷盘(推进 checkpoint)再覆盖。A 直接覆盖会丢数据,C/D 与场景无关。"
},
{
"index": 2,
"type": "short_answer",
"question": "从『数据结构(覆盖 vs 追加)』角度,说明 redo log 为何天然不膨胀、不需要像 AOF 那样主动重写;它们对中间过程的保留策略分别是什么?",
"answer": "redo log 是固定大小的环形缓冲(记录按 LSN 排序),当旧记录对应的脏页已落盘后,新记录就覆盖旧记录,天然丢弃了『已安全落盘、无需再重放』的物理页变更,只保留尚未落盘的部分,文件体积固定、不膨胀,无需主动重写。AOF 是末尾追加、累进的命令序列,命令一 append 就永久保留,会随时间无限膨胀,才需要 BGREWRITEAOF 主动把命令序列压缩成状态快照。\n对中间过程:redo 是物理层覆盖合并,不保留每次中间变更,重放只针对尚未落盘的最终页状态;AOF 则保留全部命令历史,直到重写时才丢弃被覆盖/删除的命令。",
"explanation": "二者数据结构(环形覆盖 vs 末尾追加)决定了是否需要主动压缩。redo『环形+checkpoint 推进』→ 天然不膨胀;AOF『append 追加』→ 需显式重写。这正是题目伪代码(后半是覆盖)与 AOF(后半是追加)的对照。",
"keywords": [
"环形缓冲",
"覆盖",
"checkpoint",
"脏页落盘",
"LSN",
"不膨胀",
"追加",
"重写",
"状态快照"
],
"scoring_rubric": "①答出 redo 是环形固定大小、覆盖已落盘旧页、天然不膨胀得 2 分;②答出 AOF 是末尾追加、积累全部命令、才需显式重写得 2 分;③说到中间过程差异(物理覆盖合并 vs 保留历史到重写)得 1 分。"
}
],
"explanation": "关键在『覆盖 vs 追加』的数据结构差异:redo 环形覆盖天然合并中间过程、体积固定,AOF 追加增长→需要主动重写。这正是 AOF 为何要压缩而 redo 不用压缩的根因。"
},
{
"id": "cr-004",
"type": "code_reading",
"difficulty": 2,
"tags": [
"binlog",
"PITR",
"时间点恢复",
"mysqlbinlog"
],
"question": "某运维在 14:00 误删除了一张表,希望恢复到 13:59 的状态。已有 12:00 的全量备份。阅读下面的恢复思路并回答。",
"code": "# 1. 先恢复 12:00 的全量备份\ntar -xzf backup_1200.tar.gz && mysql < backup_1200.sql\n\n# 2. 再用 binlog 做增量重放,停在误操作前\nmysqlbinlog --start-position=<pos1> --stop-position=<pos2> binlog.000123 | mysql",
"language": "bash",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "binlog 做时间点恢复时,'' 为什么用 '--stop-position'(文件+偏移)比单纯用 '--stop-datetime'(时间字符串)更能保证精确?",
"options": {
"A": "--stop-datetime 只对 ROW 格式生效,对 statement 格式失效",
"B": "时间戳是按 SQL 语句粗略过滤的,可能在一条事务/语句内部截止,导致该事务只执行了一半;而 position 是 binlog 里精确的字节偏移,可对准完整事件边界",
"C": "--stop-position 参数并不存在,MySQL 只支持 --stop-datetime",
"D": "--stop-position 会把日志里的所有事务一起截断,无法只保留部分"
},
"answer": "B",
"explanation": "binlog 中能唯一定位重放位置的是文件的物理偏移(position),而非时间。--stop-datetime 按语句/事务的记录时间过滤,可能把一个未结束的事务切在中间(一半重放一半不重放),破坏事务原子性;position 能精确对准一个事件边界,所以做 PITR 用它最安全。A、D 说法无依据,C 错误(--stop-position 存在)。"
},
{
"index": 2,
"type": "short_answer",
"question": "『从全量备份 + binlog 增量重放』这一整套恢复操作官方叫什么?为什么 AOF 做不到像 binlog 这样精准重放到任意历史点?",
"answer": "这套操作叫 point-in-time recovery(PITR,时间点恢复)。binlog 为 PITR 设计:逐条记录事务(含文件号+字节位置),配合全量备份可精确拼到任何一个历史提交点;mysqlbinlog 支持 --start-position/--stop-position(字节精确)和 --stop-datetime(时间近似)。AOF 是 Redis 的持久化日志,虽然记录了全部写命令,但它是『从文件头按顺序重放到文件尾』,命令本身没有内建可精确到事务边界的 position 切片接口,也没有标准的『从中间某条开始/停止』重放工具,要从历史某一时刻恢复需要手工截断文件再重放,非常不便,因此只说『可大致回到历史某个时间点、但需手动截断』,不像 binlog 原生支持精确切片。",
"explanation": "PITR 是 binlog 的核心能力,靠每条记录的位置+完整事务边界。AOF 虽也是命令日志,但其定位是『崩溃后从头把内存状态重放到当前』,而非『精确停在历史某一事务横切』,加上缺少原生 position/时间点切片命令,所以只能手动截断。",
"keywords": [
"PITR",
"时间点恢复",
"position",
"全量备份",
"增量重放",
"从头顺序重放",
"无时间切片"
],
"scoring_rubric": "①答出 PITR 得 1 分;②说明 binlog 用备份+position 增量重放实现精确到点得 2 分;③点出 AOF 只能从头顺序重放到末尾、缺少原生时间点重放而需要手动截断得 2 分。"
}
],
"explanation": "考察 binlog 与 AOF 在时间点恢复上的差异。核心:binlog 支持 position/位点(PITR 精确),AOF 只能从头到末尾顺序重放、无原生时间点切片。"
},
{
"id": "cr-005",
"type": "code_reading",
"difficulty": 3,
"tags": [
"AOF",
"appendfsync",
"丢失窗口",
"AOF重写",
"配置"
],
"question": "下图为某 Redis 的 AOF 相关配置片段。请理解其含义并回答相应问题。",
"code": "appendonly yes\nappendfsync everysec\nauto-aof-rewrite-percentage 100\nauto-aof-rewrite-min-size 64mb",
"language": "bash",
"source": null,
"related": [],
"sub_questions": [
{
"index": 1,
"type": "single_choice",
"question": "在 appendfsync everysec 下,当进程宕机(不是主机断电)时,最多大约丢失多少数据?",
"options": {
"A": "1 秒的写入数据",
"B": "0 数据,一条都不丢",
"C": "距离上一次自动重写之间的全部写入数据",
"D": "最多 64MB 数据"
},
"answer": "A",
"explanation": "everysec 表示每 1 秒把 AOF 缓冲 fsync 到磁盘一次,因此最多丢失『最后一次 fsync 到崩溃之间』这一秒的写入。'always' 才是一条不丢(B 错);C 混淆了 AOF 重写间隔与丢失窗口;D 把 auto-aof-rewrite-min-size 当成丢数据量,错误。"
},
{
"index": 2,
"type": "short_answer",
"question": "auto-aof-rewrite-percentage 100 和 auto-aof-rewrite-min-size 64mb 这两个参数控制什么行为?这里的『重写』做了什么?",
"answer": "它们控制 AOF 自动重写(BGREWRITEAOF)的触发条件:当当前 AOF 文件体积相对上次重写后的文件增长幅度超过 100%(即翻倍),并且当前文件大小已经超过 64MB 的下限时,Redis 自动在后台触发一次 AOF 重写。\n重写的行为:遍历当前内存数据库,为每个 key 只重新生成一条重建其最终状态的最小命令(可配合前缀批量,尽量小段),丢弃掉大量被覆盖或已删除的旧命令,把膨胀的命令日志压缩为接近『状态快照』的小体积日志,文件变小,重启重放更快。",
"explanation": "auto-aof-rewrite-percentage/min-size 是 AOF 自动重写的双阈值:相对上一轮的增长百分比 + 最小文件下限,两者同时满足才自动触发。重写本质是把『命令日志 → 状态快照』压缩,避免 AOF 无限膨胀。",
"keywords": [
"auto-aof-rewrite-percentage",
"100%增长",
"64MB下限",
"BGREWRITEAOF",
"状态快照",
"压缩"
],
"scoring_rubric": "①解释 percentage 是相对上一轮增长幅度、min-size 是最小下限、需要同时满足得 2 分;②说明重写把命令日志转成最小状态快照、避免膨胀得 3 分。"
}
],
"explanation": "考查 AOF 配置阈值与后台重写。appendfsync 决定丢失窗口,rewrite 阈值决定何时压缩 AOF 文件,两者结合掌握 AOF 的核心运维。"
}
]
}