面灵AI→

百度日常实习二面:备份与导出的分块、死锁排查与合并 K 个有序数组

轮次
二面
时间
2026-10
来源
牛客网

《面试题目》

  1. 做一个简单的自我介绍。
  2. 为什么实习结束之后又开始找新的实习?
  3. 这段实习准备实习多久?
  4. 看你之前做的数据库,我们做的文件存储,有一些不一致(方向差异)。
  5. 备份是单机的备份还是集群的?
  6. 做备份的作用都有什么?逻辑备份和物理备份?
  7. 表导出的自适应步长分块怎么做的?为什么不用 LIMIT OFFSET?无主键、复合主键、稀疏主键、字符类型的主键分别怎么做?
  8. 挑一个生产的 bug 是什么现象,怎么定位的?
  9. 死锁问题怎么排查的?
  10. B+ 树、LSM 树各自的读写特点,分别适合什么场景?
  11. Redis 的 AOF、RDB。
  12. 手撕:合并 k 个有序数组,要求 O(k log n)。
  13. 反问:为什么做分布式?业务需求。组里都在做这个文件系统吗?岗位对实习生和校招生的要求怎么样?面试什么时候出结果?

《参考解析》

备份:单机与集群、逻辑与物理

先分清层次。单机备份是「一台实例的数据副本」,集群备份要考虑「全量一致性与拓扑」,做法是先选一个一致性的时间点(比如在从库上做、或先做全局读锁/暂停写入、或按 GTID 位点记录),再逐分片备份,并且要记录每份备份对应的 binlog 位点,才能恢复到一致状态。备份的作用要答全:灾难恢复(误删、硬件故障、机房故障)、回滚到某个时间点(配合 binlog 做时间点恢复)、迁移与扩容、以及审计与合规;还要说清备份不是高可用——高可用靠副本与切换,备份防的是「逻辑错误与人为误操作」,这两类问题主从复制会一起复制过去,所以必须有独立备份。

逻辑备份与物理备份的区别按实现与适用场景讲:逻辑备份导出的是 SQL 或文本(mysqldump、mydumper、SELECT ... INTO OUTFILE),可读、可跨版本与跨平台、能选择性恢复单个表,但慢、恢复更慢、对大库不友好;物理备份直接拷贝数据文件与日志(Percona XtraBackup、文件系统快照),速度快、恢复快、适合大库,但依赖版本与平台、不能轻易选择性恢复。实践中常用于大库的方案是「物理全备 + binlog 增量」并用 XtraBackup 做热备(不锁表、通过 redo 追平一致性);要主动提到验证环节——备份不恢复一次就等于没有备份,定期做恢复演练并记录 RTO/RPO。

自适应步长分块导出,与各类主键的处理

分块导出的目标是「把一个超大的表拆成可并行的小任务,同时避免扫描浪费」。自适应步长的做法通常是:在目标列上按范围切分,先按主键取 min/max,用初始步长切第一块,统计这一块的行数与耗时,若行数过大就缩小步长、过小就放大步长,动态调整到每块行数接近目标值(比如每块 5 万到 20 万行)。为什么不用 LIMIT offset, size:offset 分页在深分页时不是「跳过」而是「扫描并丢弃」前 offset 行,越到后面越慢(总代价是 O(n²/size)),而且导出过程中如果数据有增删,offset 会错位导致漏行或重复。改用「键值游标」(where pk > last_pk order by pk limit size)则每次都是索引区间扫描,代价恒定且不受并发写入影响(新插入的行若在主键更大的位置会在后续块被导出,需要接受这一点或用一致性快照)。

不同主键的处理要分开讲:无主键的表没有可靠的分块依据(LIMIT 的返回顺序不保证稳定),解决方式是给表加上自增主键(可能不可行)、用一个唯一索引列代替、或者用「全表一致性快照 + 单线程流式导出」(SELECT 不加锁地流式读取,牺牲并行度换正确性)。复合主键按「字典序」做游标,条件写成 where (a, b) > (?, ?)(MySQL 支持行值比较,也可以用 a > ? or (a = ? and b > ?)),分块要按复合键的组合切分而不能只看第一列。稀疏主键(值分布不均、有大量空洞)不能用「min + n*step」这类等距切分,否则会切出大量空块或单块过大,应按「按主键采样取分位点」来定边界(比如先采样每隔若干行取一个键值作为切点),或者用自适应的「按行数找边界」(二分逼近目标行数)。字符类型主键(UUID、订单号)可以按字符串范围比较做游标,但要注意字符集与排序规则(utf8mb4_general_ci 与 utf8mb4_bin 的排序不同,会影响区间划分的正确性),以及 UUID 这类随机分布的键上索引扫描效率较好、但插入热点与页分裂需要留意;如果字符主键是无序随机的,按它分块仍然可行,只是不能按「时间局部性」优化。

生产 bug 与死锁的排查

「挑一个生产 bug」要按现象—定位—修复—预防四段讲,并选一个你能说清细节的(比如某接口偶发超时、某张表数据重复、某次发布后 QPS 下降)。定位部分要给出具体动作:先是范围界定(影响哪些用户、什么时间开始、是否与发布或数据变化相关),再是证据收集(日志按 traceId 串联、监控指标对比、必要时用 EXPLAIN 看执行计划或加埋点复现),最后是根因(往往是并发条件、边界数据、或者依赖的隐性约定)。修复要区分「止血」与「治本」:先回滚或降级止血,再改代码与补测试,最后加监控或断言防止复发。

死锁排查要能落到命令与信息:MySQL 用 SHOW ENGINE INNODB STATUS 看 LATEST DETECTED DEADLOCK 段落,里面有事务、SQL、持有的锁与等待的锁、以及被回滚的事务;information_schema.innodb_trx 看当前活动事务与等待,performance_schema.data_locks/data_lock_waits 看锁等待关系,innodb_lock_waits 可以直接看到阻塞链。常见成因与对策:加锁顺序不一致(统一按主键排序后加锁)、一次更新过多行导致锁范围大(拆小批量并加索引避免间隙锁扩大)、缺索引导致全表扫描加锁(补索引)、以及长事务持有锁(缩短事务、把非数据库操作移出事务)。要能顺便解释间隙锁(RR 隔离级别下 Next-Key Lock 会锁区间,两次不同顺序的插入可能互相等待),以及应用层的兜底(捕获死锁错误码 1213 后重试一次是常规做法,因为死锁对业务通常是可重试的)。

B+ 树与 LSM 树、Redis 的 RDB 与 AOF

B+ 树读友好:数据有序、单点与范围查询都是树高次磁盘 IO(通常三到四次),更新是原地修改页,读放大低、写放大中等,但随机写会导致页分裂与碎片,且写要等落盘(配合 WAL 保证)。LSM 树写友好:写入先进内存表(MemTable)并顺序追加日志,满了刷成有序的 SSTable 文件,后台做压实(Compaction)合并;顺序写带来极高的写入吞吐,代价是读要逐层查找(可能查多个文件,读放大)、压缩会消耗 IO 与 CPU、以及空间放大。适用场景:B+ 树适合读多写少、事务型与需要强一致与范围查询的 OLTP(MySQL);LSM 树适合写密集、追加型、可接受读放大的场景(HBase、RocksDB、ClickHouse、Kafka 的日志存储)。要主动补一句工程上的折中:很多系统在 LSM 上加布隆过滤器与多级缓存来缓解读放大,用分层压缩策略(Leveled vs Tiered)在写放大与空间放大之间取舍。

Redis 的 RDB 是某个时间点的全量快照(BGSAVE fork 子进程写盘,二进制、体积小、恢复快),适合备份与快速重启,缺点是两次快照之间的数据会丢;AOF 记录写命令(先写缓冲区再刷盘,appendfsync 有 always/everysec/no 三档),可读、丢数据窗口小,缺点是文件大、恢复慢,通过 AOF 重写压缩体积。生产上一般两者都开:AOF 保数据、RDB 做备份与快速恢复;重启时优先用 AOF(更完整)。要能讲清 fork 的代价(写时复制、内存紧张时可能抖动)、AOF 重写期间的 IO 压力,以及主从全量同步会触发一次 RDB 生成。

手撕:合并 k 个有序数组(O(k log n))

标准解法是最小堆:把 k 个数组的第一个元素入堆(堆里存值、数组下标、元素下标),每次弹出最小元素加入结果,并把该元素所在数组的下一个元素入堆;直到堆空。若有 k 个数组、总元素数为 N,则复杂度是 O(N log k),也就是每次操作 O(log k)。面试官写的「O(k log n)」通常是记法上的偏差(严格说应该按总元素数与堆大小来算),答题时可以直接点出这一点并给出正确复杂度,同时说明另一种等价方案:两两归并(分治)也能达到 O(N log k),只是实现上要多一份数组拷贝。要注意的边界:有数组为空、k 为 0 或 1、元素有重复、以及堆里需要存「来自哪个数组」以免把不同数组的元素混在一起。若面试官要求「原地合并到第一个数组」,就改用从后往前填充的双指针(类似合并两个有序数组),但 k 路的情况通常还是堆最直接。