Skip to content

02 · 月度自测卷(30 题) ​

闭卷。不翻资料。限时 60 分钟。 目的不是打分,是找出"以为懂了其实没懂"的地方。

使用说明 ​

① 先做题,把答案写在纸上(不要只在脑子里过,写出来才算)
② 做完再对答案,每题的"评分标准"告诉你答到哪算对
③ 统计错题,回到对应篇目重读
④ 一周后只重做错题

评分标准(每题按"答到几层"给分):

0 分:答不出,或者完全说错
1 分:只答出"是什么"(第一层)
2 分:能答出"为什么这样设计"(第二层)
3 分:能答出"不这样会怎么坏"(第三层)

第一部分 · 操作系统(1-8 题) ​

1. 进程和线程的本质区别是什么?为什么线程切换比进程切换便宜?

2. 画出进程的 5 个状态,并说明"阻塞能否直接转到运行",为什么?

3. 虚拟内存解决了哪三个具体问题?各举一个"不这么做会出的事故"。

4. 缺页中断的完整处理流程是什么?(至少 4 步)

5. 死锁的四个必要条件是什么?最实用的破坏手段是哪个?

6. 自旋锁和互斥锁分别适合什么场景?为什么单核 CPU 上不该用自旋锁?

7. 为什么说前四种 IO 模型都是"同步 IO"?

8. epoll 比 select 快在哪三点?什么情况下 epoll 不一定更快?


第二部分 · 计算机网络(9-16 题) ​

9. 三次握手的过程?为什么不是两次?(要举出一个具体会出事的场景)

10. 四次挥手为什么是四次?什么情况下会变成三次?

11. TIME_WAIT 为什么必须等 2MSL?两个理由分别是什么?

12. 流量控制和拥塞控制的区别?分别用什么实现?

13. 超时重传和快速重传的处理有什么不同?为什么要区别对待?

14. TCP 队头阻塞是什么?HTTP/2 为什么解决不了?HTTP/3 怎么解决的?

15. 强缓存和协商缓存的区别?no-cache 和 no-store 有什么不同?

16. HTTPS 为什么要"非对称 + 对称"混用?证书解决的是什么问题?


第三部分 · 数据结构与算法(17-23 题) ​

17. 数组和链表都是 O(n) 遍历,为什么实测差 10 倍?

18. 哈希表的 O(1) 是平均还是最坏?装太满会怎样?

19. 为什么数据库索引用 B+ 树,而不是哈希、红黑树、B 树?(四个都要说)

20. 堆为什么用数组存?建堆为什么是 O(n) 而不是 O(n log n)?

21. 为什么无权图的最短路用 BFS,而不是 DFS?

22. 并查集的两个优化是什么?为什么优化后接近 O(1)?

23. 快排最坏是 O(n²),什么时候会发生?怎么避免?


第四部分 · 数据库(24-30 题) ​

24. 2000 万行的表,B+ 树大概几层?一次查询几次磁盘 IO?(要能算出过程)

25. 什么是回表?什么是覆盖索引?

26. 联合索引 (a,b,c),WHERE a > 1 AND b = 2 能用到 b 吗?为什么?

27. ACID 分别由什么机制实现?为什么说"C 不是数据库实现的"?

28. redo log 和 binlog 的四个区别?为什么需要两阶段提交?

29. MVCC 靠哪三样东西实现?RC 和 RR 的唯一区别是什么?

30. 一条 SQL 很慢,你的排查顺序是什么?(至少 5 步)



答案与评分标准 ​

先做完再看。

第一部分 · 操作系统 ​

1. 进程和线程的区别

  • 1 分:进程是资源分配单位,线程是调度单位;线程共享地址空间。
  • 2 分:+ 线程独占栈和寄存器,共享堆和代码段。
  • 3 分:+ 切换成本的差别在页表——进程切换要换 CR3、导致 TLB 失效,线程共享页表不用换。
  • 命门:能不能说到 TLB。

2. 进程 5 状态

  • 状态:新建、就绪、运行、阻塞、终止。
  • 阻塞不能直接到运行:因为 CPU 可能被别的进程占着,事件到达只是"具备运行条件",还要重新排队等调度。
  • 3 分:+ 能说清"运行>就绪"是被动(时间片),"运行>阻塞"是主动(发起 IO)。
  • 命门:"阻塞 > 就绪 > 运行"这条路径。

3. 虚拟内存的三个价值

  • ① 隔离:每个进程独立地址空间,不会互相踩。
  • ② 超卖:虚拟总量可以超过物理内存,按需调页。
  • ③ 免连续:物理页可以散落,不需要连续的大块内存。
  • 3 分:每个都能举出"不这么做会出的事故"。
  • 命门:三个都要说,只说"扩大内存"是 0 分。

4. 缺页中断流程

  • ① 找空闲页框(没有就按置换算法选一个牺牲页)
  • ② 牺牲页脏了先写回磁盘
  • ③ 从磁盘/文件读入需要的页
  • ④ 更新页表(存在位=1,填物理页号)
  • ⑤ 重新执行触发缺页的那条指令
  • 3 分:+ 能说清"哪些是硬件做的、哪些是内核做的"。
  • 命门:第 ⑤ 步"重新执行"不能漏。

5. 死锁四条件

  • 互斥、持有并等待、不可剥夺、循环等待。
  • 最实用:破坏循环等待——所有资源按全局顺序申请。
  • 3 分:+ 能解释"为什么按顺序申请就不会有环"。
  • 命门:说清"破坏哪个"比背四条重要。

6. 自旋锁 vs 互斥锁

  • 临界区极短 + 多核 > 自旋(省内核切换);可能阻塞 > 互斥。
  • 单核不能用自旋:占着唯一的核空转,持有锁的线程没机会运行来释放锁。
  • 3 分:+ 提到"读写锁适合读多写少"。
  • 命门:"单核为什么不行"这半句。

7. 前四种都是同步 IO

  • 因为第二阶段(内核缓冲区 > 用户缓冲区)都是阻塞的,都要自己等拷贝完成。
  • 只有 AIO 第二阶段也不阻塞。
  • 命门:判据是"第二阶段谁完成、完不完成通知你"。

8. epoll 快的三点

  • ① 不用每次全量拷贝 fd 集合(注册一次)
  • ② 不用遍历(回调把就绪的挂到链表)
  • ③ 只返回就绪的 fd
  • 不一定更快:fd 少且全活跃时,select 遍历开销小,epoll 的回调注册反而是额外成本。
  • 命门:能说出"什么时候 epoll 不占优"。

第二部分 · 计算机网络 ​

9. 三次握手

  • SYN(seq=x) > SYN+ACK(seq=y, ack=x+1) > ACK(ack=y+1)。
  • 为什么不是两次:如果只有两次,服务器收到一个延迟到达的历史 SYN 就会立刻认为连接建立、分配资源;而客户端根本不认这个连接,会回 RST 或忽略 > 服务器白占资源。三次握手的第三次让服务器确认"客户端真的收到了我的 SYN"。
  • 3 分:+ 提到"第三次可以带数据,前两次不能"。
  • 命门:历史 SYN 这个场景要讲完整。

10. 四次挥手

  • 因为全双工,两个方向要独立关闭。服务器收到 FIN 时可能还有数据没发完,所以 ACK 和 FIN 不能合并。
  • 变成三次:服务器确实没数据了,ACK+FIN 合并发送(延迟确认)。
  • 命门:"还有数据要发"这个理由。

11. TIME_WAIT 两个理由

  • ① 保证最后一个 ACK 能到达(丢了对方会重发 FIN,还能再回 ACK)
  • ② 让旧连接的残留报文在网络中消亡,避免污染使用相同四元组的新连接
  • 为什么 2MSL:覆盖"我的 ACK 到对方"+"对方的 FIN 到我"一个完整往返。
  • 命门:两个理由都要说。

12. 流量控制 vs 拥塞控制

  • 流量控制:端到端,接收方通告窗口,防止发送方淹没接收方。用滑动窗口。
  • 拥塞控制:全局,发送方推测网络拥堵,防止压垮网络。用 cwnd。
  • 实际窗口 = min(rwnd, cwnd)。
  • 命门:"一个怕对方收不下,一个怕路上堵"。

13. 超时 vs 快速重传

  • 超时:连 ACK 都没回来 > 判断严重拥塞 > cwnd 降到 1,重新慢启动。
  • 快速重传:3 个重复 ACK > 个别丢包 > cwnd 减半,快速恢复。
  • 为什么区别对待:一个是"路断了",一个是"掉了个包裹"。
  • 命门:这个类比要能说出来。

14. TCP 队头阻塞

  • TCP 保证按序交付,前面一段丢了,后面到的也要等。
  • HTTP/2 的多路复用是在同一条 TCP 连接上做,TCP 层丢包会卡住所有流——反而更糟。
  • HTTP/3 用 QUIC(UDP),每流独立排序和重传。
  • 命门:区分"应用层 HOL"和"TCP 层 HOL"。

15. 强缓存 vs 协商缓存

  • 强缓存:Cache-Control: max-age,不发请求,直接用本地。
  • 协商缓存:If-None-Match / If-Modified-Since,要发请求,没变回 304(不带 body)。
  • no-cache:可以缓存,但每次必须校验(不是"不缓存")。
  • no-store:完全禁止缓存。
  • 命门:no-cache 的陷阱。

16. HTTPS 混合加密

  • 非对称(RSA/ECDHE)安全但慢,只用来协商会话密钥;对称(AES)快,用来传数据。
  • 证书解决的问题:确认"这个公钥确实属于这个域名",靠 CA 签名 + 证书链逐级验证到浏览器内置的根证书。
  • 3 分:+ 提到"用户信任了恶意根证书时 HTTPS 也防不住"。
  • 命门:证书解决的是"公钥归属"。

第三部分 · 数据结构与算法 ​

17. 数组比链表快 10 倍

  • CPU 按缓存行(64 字节)读内存。数组连续,读一个元素会把邻居一起载入缓存 > 后续访问命中缓存。
  • 链表节点散落,每次 next 大概率缓存未命中,要去主存取(约 100 周期)。
  • 3 分:+ 提到"硬件预取器能识别顺序访问,对随机跳转无能为力"。
  • 命门:缓存行 + 预取器。

18. 哈希表的 O(1)

  • 平均 O(1),最坏 O(n)(全部冲突)。
  • 装太满 > 冲突概率上升 > 链表变长 > 查找退化。
  • 解法:负载因子(0.75)+ 扩容(2 倍)+ 链表转红黑树(Java 8,≥8 个)。
  • 命门:"平均"这两个字。

19. 为什么 B+ 树(四问四答)

  • vs 哈希:不支持范围查询、排序、最左前缀。
  • vs 红黑树:树高 24 层 = 24 次磁盘 IO(每次 10ms)。
  • vs B 树:B 树数据存在所有节点,非叶子能存的键变少,树高更高;且范围查询要在树上反复上下跳。
  • B+ 树的优势:数据全在叶子(非叶子能存更多键 > 树高更低)、叶子有链表(范围查询顺序扫)、每次查询都到叶子(IO 次数稳定)。
  • 命门:所有比较都落到"磁盘 IO 次数 = 树高"。

20. 堆

  • 为什么用数组:完全二叉树可以用下标算父子(2i+1、2i+2),不需要指针,缓存友好。
  • 建堆 O(n):从最后一个非叶子节点往前下沉。大部分节点在底层、下沉距离短,总代价是 n/2×0 + n/4×1 + n/8×2 + ... 收敛到 O(n)。
  • 对比"插入 n 次"是 O(n log n),因为每个都从底部上浮全程。
  • 命门:"越往下节点越多但下沉越少"。

21. BFS 找无权最短路

  • BFS 按层扩展,第一次到达目标时边数一定最少(所有更短的都在更早的层)。
  • DFS 是"一条路走到黑",可能先返回一条长路径。
  • 前提:无权(或边权相同)。有权重必须用 Dijkstra。
  • 命门:那个"前提"。

22. 并查集

  • 两个优化:路径压缩(查找时把路径上所有节点直接挂到根)+ 按秩合并(矮树挂到高树下)。
  • 复杂度 O(α(n)),α 是反阿克曼函数,实际 ≤ 4,可视为 O(1)。
  • 命门:两个优化都要说,只说一个只能得 1 分。

23. 快排最坏 O(n²)

  • 每次 pivot 都是极值,分区极不均衡,递归深度 n。
  • 什么时候:数组已排序/逆序 + 固定选首/尾元素当 pivot。
  • 避免:随机 pivot、三数取中、三路快排、小区间用插入排序。
  • 命门:"已排序的数组最慢"这个反直觉结论。

第四部分 · 数据库 ​

24. B+ 树层数

  • 非叶子:键 8B + 指针 6B = 14B,一页 16KB > 约 1170 个键。
  • 叶子:一行约 1KB > 一页约 16 行。
  • 三层:1170 × 1170 × 16 ≈ 2200 万行。
  • 所以 2000 万行 = 3 层,一次查询最多 3 次 IO。
  • 命门:能写出 1170 这个数是怎么来的。

25. 回表 / 覆盖索引

  • 回表:二级索引叶子只存主键,查到主键后要再去聚簇索引查整行 > 两次 B+ 树查找。
  • 覆盖索引:查询的列全在索引里,不用回表。
  • 例:索引 (name, age),SELECT name, age WHERE name='张三' 就覆盖了。
  • 命门:能写出一个具体的索引 + SQL 组合。

26. 最左前缀

  • 不能。
  • 因为索引按 (a,b,c) 排序,a > 1 会命中多个 a 值,b 在每个 a 内部有序但跨 a 整体无序,无法用于定位。
  • b 只能用于"索引下推"过滤(减少回表),不能缩小扫描范围。
  • 命门:"跨 a 整体无序"这个解释。

27. ACID 实现

  • A > undo log(记录旧值,反向操作回滚)
  • D > redo log(WAL,先写日志再刷数据页)
  • I > 锁 + MVCC
  • C 不是数据库实现的:一致性是业务目标(余额不能为负、总额不变),数据库只提供 A/I/D 这些手段,C 需要应用逻辑 + 约束共同保证。
  • 命门:"C 是目标,AID 是手段"。

28. redo vs binlog + 两阶段提交

  • 四个区别:层级(引擎层 vs Server 层)、内容(物理 vs 逻辑)、写入(循环写 vs 追加写)、用途(崩溃恢复 vs 主从复制/备份)。
  • 为什么要两阶段提交:一次提交要写两个日志,必须保证原子性。先写 redo(prepare)> 写 binlog > redo(commit)。崩溃时看 binlog 是否完整 决定提交还是回滚。
  • 命门:"用 binlog 的完整性决定 redo 的去留"。

29. MVCC

  • 三件套:隐藏字段(DB_TRX_ID + DB_ROLL_PTR)、undo log 版本链、ReadView。
  • RC 和 RR 的唯一区别:ReadView 的生成时机。RC 每次 SELECT 都生成,RR 只在第一次 SELECT 时生成并复用。
  • 补充:MVCC 只能解决快照读的幻读,当前读要靠间隙锁。
  • 命门:"只差一处:ReadView 生成时机"。

30. 慢 SQL 排查

  • ① 确认真的慢(慢查询日志)
  • ② EXPLAIN 看 type / key / rows / Extra
  • ③ 判断"没索引"还是"索引失效"
  • ④ 看是否返回太多数据(SELECT * / 深分页)
  • ⑤ 看是否锁等待(长事务)
  • ⑥ 看数据量(分表 / 归档)
  • ⑦ 看配置(Buffer Pool、连接数、硬件)
  • 命门:不能一上来就说"加索引"。


自评表 ​

计分 ​

满分:30 题 × 3 分 = 90 分

75-90:骨架扎实,可以开始第 2 个月(语言/框架/中间件)
60-74:主干有了,把错题所在篇目重读一遍,两周后重考
40-59:概念记住了但"为什么"没通,重点补每篇的第 4 节(为什么这么设计)
< 40 :回到导读篇,重新建立模块地图,别急着背细节

错题记录 ​

题号得分属于哪个模块错在哪一层要重读的篇目

"错在哪一层"怎么填:

  • 第 1 层(是什么)错 > 没记住,重读一遍即可
  • 第 2 层(为什么这样设计)错 > 理解不够,重点看"为什么这么设计"和"反事实"
  • 第 3 层(不这样会怎么坏)错 > 没真懂,回去看"类比失效处"和追问链

按模块统计 ​

模块得分满分薄弱点
操作系统24
计算机网络24
数据结构21
数据库21
合计90

下一步 ​

得分最低的那个模块,就是你第 2 个月要补的第一块。
但更重要的判断是:错在第 2 层还是第 3 层?

第 1 层错得多 > 你只是"忘了",重读就行,不用焦虑
第 2/3 层错得多 > 说明当初学的时候就没建立因果,需要重新走一遍"反事实"

真正危险的是:答对了第 1 层,就以为自己会了。

✅ 验收清单 ​

  • 闭卷、限时 60 分钟,独立完成 30 题(不翻资料、不查手机)
  • 按三层标准给自己打分,而不是"大概会"
  • 填完错题记录表,每道错题都能指出"错在哪一层"
  • 统计出最薄弱的模块,并列出要重读的 3 篇
  • 一周后只重做错题,正确率到 80% 以上
  • 能说出"这次自测暴露的最大问题是什么"(一句话)