今天分享的内容是:最近和组织内部朋友进行的模拟面试的内容复盘,以后也会把这些通用的大家容易踩坑的地方总结分享出来,大家可以点赞、关注或者转发支持一波。
这位朋友有10
年的工作经验,之前做的是Java开发,自己想转Go就找到了我们,想在9月份的时候跳槽。
面试内容聊的都是开发绕不开的 MySQL、Redis、Kafka
这些硬骨头。整体聊下来,发现他对基础概念有印象,但不少细节要么有点含糊其辞,要么理解有偏差。今天我就从对话中挑出 10 个有代表性的问题,好好拆解拆解。
这篇文章既是复盘,也是给各位朋友提个醒,这些坑可别踩。
1. 索引下推是什么
对话回放
我:那下面就请你先来讲一下,索引下推是什么,说一下。
他:就是在数据查询过程中,涉及到 MySQL 的 server 层和引擎层,一般走索引的话,返回后会在 server 层做过滤和组装;而索引下推的话,查询过程在引擎层就处理完了,返回给 server 层的就是最终结果。大概是这么个意思?
他回答的问题在哪?
解释少了核心:索引下推的本质是 “将本应在 server 层做的过滤逻辑,下放到引擎层利用索引完成”,目的是减少引擎层返回给 server 层的数据量。比如用联合索引查询时,引擎层可以根据索引中的字段直接过滤掉不符合条件的记录,不用把所有符合索引前缀的记录都返回给 server 层再过滤。
正确的打开方式
索引下推是 MySQL 的一种查询优化机制:当查询使用二级索引时,server 层会把本应在自身进行的 “where 条件过滤”,下放到引擎层;引擎层在遍历索引的同时,就会对索引中包含的字段做条件判断,只把符合条件的记录返回给 server 层。这样能减少引擎层和 server 层之间的数据传输,提高查询效率。
比如查询 “where name like ’ 张 %’ and age > 20”,若联合索引是(name, age),引擎层会直接过滤掉 age≤20 的记录,不用把所有 “name 以张开头” 的记录都返回给 server 层。
2. 什么是死锁
对话回放
我:再讲一下死锁的四个条件是什么?
他:死锁的四个条件,一个就是有资源竞争,然后循环依赖,不可剥夺,然后请求保持。
他回答的问题在哪?
名称记混了两个:“循环依赖” 应为 “循环等待”,“请求保持” 应为 “持有与等待”。这四个条件是死锁产生的必要条件,缺一不可,记准名称是基础。
正确的打开方式
死锁产生的四个必要条件:
- 互斥条件:资源只能被一个进程占用,不能共享;
- 持有与等待条件:进程已持有至少一个资源,又提出新的资源请求,而新资源被其他进程占用,此时进程会保持已持有的资源并等待新资源;
- 不可剥夺条件:已分配的资源不能被强制收回,只能由持有进程主动释放;
- 循环等待条件:多个进程形成环形等待链,每个进程都在等待下一个进程持有的资源。
3. 不设主键会怎么样
对话回放
我:在设计数据库表的时候不设置主键会怎么样?
他:不设置主键的话,MySQL 会自动加一个隐藏的主键。
他回答的问题在哪?
InnoDB 引擎下,若表没有显式主键,会优先选择非空唯一索引作为主键;若没有非空唯一索引,才会生成一个 6 字节的隐藏列row_id
作为主键,这个row_id
对外不可见,仅用于 InnoDB 内部组织数据(比如聚簇索引)。
正确的打开方式
MySQL 的 InnoDB 引擎表必须有主键(聚簇索引的基础),若未显式定义,会按以下规则自动生成:
- 优先选择表中第一个非空唯一索引作为主键;
- 若没有非空唯一索引,会自动创建一个隐藏的
row_id
列作为主键,row_id
是自增的,但仅用于内部数据组织; - 建议显式设置主键,因为隐藏的
row_id
可能导致聚簇索引页分裂(自增特性),且非空唯一索引作为主键的查询效率更高。
4. 索引建立的原则
对话回放
我:加索引不能乱加,在哪些情况下我们才会选择添加索引
他:根据实际的查询条件来加索引
我:具体有哪些条件?
他:比如多个字段的联合查询。
他回答的问题在哪?
“根据查询条件” 是大方向,但不够落地。索引是把双刃剑,加得好能加速查询,加得不好会拖慢写入(因为索引需要维护),得兼顾查询频率、字段特性和更新成本。
正确的打开方式
索引建立的核心原则:
- 高频查询字段优先:where、order by、group by 中频繁出现的字段,比如用户表的 “手机号”(登录查询);
- 高基数字段优先:字段值重复少(比如身份证号,基数接近记录数),低基数字段(如性别,只有 2 个值)加索引意义不大;
- 联合索引看顺序:遵循 “最左前缀原则”,把过滤性强的字段放前面(比如 “手机号 + 验证码” 比 “验证码 + 手机号” 更合理);
- 避免更新频繁字段:索引会随数据更新而同步维护,更新频繁的字段(如订单状态)加索引会导致写入变慢;
- 控制数量:单表索引建议不超过 5 个,太多会让 MySQL 优化器在选择索引时 “犯迷糊”,还会占用额外存储空间。
5. redo log 和 binlog 的刷盘机制
对话回放
我:下面再讲一下,redo log 和 binlog 的刷盘机制是什么?
他:刷盘有三种配置,一种是每次事务提交,写到 page cache 里,但不强制操作系统落盘,由系统自己触发;一种是写到 page cache,事务提交时强制操作系统落盘;还有一种是不写 page cache,由后台定时任务刷到 page cache 再落盘。binlog 和 redo log 类似,但 binlog 是基于写了多少个日志后刷盘,redo log 是定时的。
我:其实可以更具体,比如 binlog 的 sync_binlog 参数,设为 0 是系统自动刷盘,设为 1 是每次提交都刷盘…
他回答的问题在哪?
逻辑大致对,但没提具体参数,等于没说透。redo log 和 binlog 的刷盘机制直接影响数据安全性和性能,参数是必须记牢的核心细节。
正确的打开方式
- redo log(由 innodb_flush_log_at_trx_commit 控制):
- 0:事务提交时,日志只写到 redo log buffer,由后台线程每秒刷盘(可能丢失 1 秒内数据);
- 1(默认):事务提交时,直接刷盘(最安全,性能略低);
- 2:事务提交时写到操作系统缓存(page cache),由系统决定刷盘(可能丢失操作系统崩溃前的数据)。
- binlog(由 sync_binlog 控制):
- 0:事务提交时写到 binlog cache,由系统不定时刷盘(风险高,可能丢失大量数据);
- 1(建议):每次事务提交都刷盘(安全,性能有损耗);
- N(N>1):每 N 次事务提交刷盘一次(折中方案,最多丢失 N 个事务数据)。
6. Redis 默认的持久化机制
对话回放
我:持久化除了 AOF 和 RDB,还有混合持久化,讲一下。
他:混合持久化就是两者的结合,文件上半部分是 RDB,下半部分是 AOF。
我:那Redis 默认采用哪种持久化方式?
他:我记得默认是 AOF 开启吧?
我:你可以去了解一下,默认应该是 RDB。
他回答的问题在哪?
这是个典型的易混点。Redis 默认只开启RDB,不开启 AOF。AOF 需要手动配置appendonly yes
才会生效,而混合持久化(RDB+AOF)更是需要额外开启aof-use-rdb-preamble yes
。
正确的打开方式
Redis 的三种持久化方式及默认配置:
- RDB:默认开启,通过快照方式保存某一时刻的内存数据,触发条件由
save
指令定义(如save 900 1
表示 900 秒内有 1 次修改就触发); - AOF:默认关闭,需配置
appendonly yes
,记录所有写操作,刷盘策略由appendfsync
控制(默认everysec
,每秒刷盘一次); - 混合持久化:Redis 4.0 + 支持,默认关闭,需配置
aof-use-rdb-preamble yes
,文件前半部分是 RDB 快照,后半部分是 AOF 增量日志,兼顾恢复速度和数据完整性。
7. Redis 定期删除策略
对话回放
我:那定期删除的详细流程是怎么样的?
他:随机取 20 个 key,若过期的超过 25%,就再取 20 个,直到过期的少于 25%,或者循环时间大于 25 秒就退出。
他回答的问题在哪?
时间限制说错了。Redis 是单线程,定期删除是 “快速检测”,每次循环耗时不能超过500ms(避免阻塞主线程),否则会暂停,等下次再执行。25 秒的话那服务早就卡死了。
正确的打开方式
Redis 定期删除流程(针对过期 key):
- 从过期字典中随机抽取 20 个 key;
- 删除其中已过期的 key;
- 若过期 key 占比≥25%,重复步骤 1-2;
- 若循环次数过多,总耗时超过 500ms 则停止,避免影响正常请求处理。这个机制既能减少过期 key 堆积,又不会因删除操作阻塞主线程。
8. 数据库与缓存的数据一致性如何保证
对话回放
我:数据库和缓存的数据一致性如何保证?
他:常用的是 Cache Aside,写的时候先写库,再删缓存;读的时候先读缓存,没命中就读库,再更新缓存。
我:除了这个还有吗?他:还有读穿或者写穿,但感觉它们的一致性没 Cache Aside 高。具体内容不太清楚
他回答的问题在哪?
“读穿”“写穿” 的一致性并不比 Cache Aside 低,只是适用场景不同。这几种策略没有绝对优劣,得结合业务选。
正确的打开方式
四种主流的缓存更新策略:
- Cache Aside(旁路缓存):应用直接操作数据库和缓存,写流程 “写库→删缓存”,读流程 “读缓存→未命中读库→更新缓存”,适合读写分离场景,需注意 “删缓存” 而非 “更新缓存”(避免脏数据);
- Read Through(读穿):缓存代理负责读操作,未命中时自动查库并更新缓存,应用只与缓存交互,适合不想手动处理缓存更新的场景;
- Write Through(写穿):缓存代理负责写操作,写流程 “更新缓存→更新数据库”,同步更新,一致性高,但写性能略低;
- Write Back(回写):写操作只更新缓存,缓存异步批量更新数据库,写性能好,但可能丢数据(如缓存宕机),适合非核心数据(如浏览量)。
9. Kafka 的 ISR 机制
对话回放
我:什么是 Kafka 的 ISR 机制?
他:ISR 就是同步副本的列表,里面的副本会同步主副本的数据。
我:ISR 指的是与 leader 副本保持同步状态的所有副本的集合,“同步” 是有标准的。
他回答的问题在哪?
只说了 “同步副本列表”,没说清 “同步” 的标准和作用。ISR 是 Kafka 保证数据可靠性的核心,“同步” 不是指实时一致,而是有明确的滞后阈值。
正确的打开方式
ISR(In-Sync Replicas)机制:
- 定义:ISR 是与 leader 副本保持同步的所有副本(包括 leader 自身)的集合;
- 同步标准:副本需满足两个条件 ——① 能定期向 leader 发送心跳(默认每 3 秒);② 滞后 leader 的消息量不超过
replica.lag.time.max.ms
(默认 30 秒),超过则被移出 ISR; - 作用:leader 宕机时,新 leader 只会从 ISR 中选举,确保新 leader 有尽可能新的数据,避免数据丢失;若 ISR 为空,会从 OSR(Out-of-Sync Replicas)中选,但可能丢数据。
10. Kafka 消息丢失怎么解决
对话回放
我:Kafka 发生消息丢失怎么办?有哪些方式可以避免?
他:生产者有重试机制,acks 设为 all 的话,ISR 所有副本确认才算成功;broker 端异步刷盘可能丢数据;消费者自动提交 offset 可能丢数据。
他回答的问题在哪?
方向对,但不够具体。消息丢失可能发生在生产者、broker、消费者任一环节,每个环节的防丢失配置都有细节。
正确的打开方式
全链路防消息丢失配置:
- 生产者:
acks=all
:消息需被 ISR 中所有副本确认才算发送成功;retries>0
:失败自动重试,配合retry.backoff.ms
控制间隔;- 开启回调函数,确认发送结果,失败后人工介入。
- broker:
unclean.leader.election.enable=false
:禁止从非 ISR 副本中选 leader,避免数据丢失;replica.lag.time.max.ms
:合理设置副本同步超时时间(默认 30 秒),确保 ISR 中的副本数据足够新。
- 消费者:
enable.auto.commit=false
:关闭自动提交,改为处理完消息后手动提交 offset;- 若用自动提交,需确保
auto.commit.interval.ms
合理,避免 “处理中提交” 导致的丢失(处理失败后,offset 已提交,消息不会再被消费)。
总结
整场面试下来,能看出他对基础概念有框架性了解,但有些细节问题要么记混了,要么没说透,很多朋友都有这样的问题,后端技术面试,一定是 “细节见真章”。
建议各位朋友 复习时,别只记 “大概意思”,多抠抠具体细节、流程、适用场景,毕竟实际工作中,出问题的往往就是这些地方。
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以私信我,备注:面试群。