全栈面试:数据库与缓存
SQL 和 NoSQL 怎么选?
SQL 数据库适合结构化数据、强一致性和复杂查询。
常见场景:
- 用户
- 订单
- 支付
- 库存
- 权限
NoSQL 适合灵活结构、高吞吐或特定数据模型。
常见场景:
- 日志
- 文档
- 缓存
- 搜索
- 时序数据
面试时不要说谁更好,而是看一致性、查询方式、数据结构、扩展性和团队能力。
索引是什么?
索引可以加速查询,但会增加写入成本和存储成本。
常见索引:
- 主键索引
- 唯一索引
- 普通索引
- 联合索引
- 全文索引
索引适合加在高频查询、过滤、排序、关联字段上。
为什么索引会失效?
常见原因:
- 对索引字段使用函数
- 前置模糊查询,例如
%keyword - 隐式类型转换
- 联合索引不满足最左前缀
- 使用
or导致优化器放弃索引 - 查询返回数据量太大
排查时可以使用 EXPLAIN 看执行计划。
事务 ACID 是什么?
ACID 包括:
- Atomicity 原子性
- Consistency 一致性
- Isolation 隔离性
- Durability 持久性
事务用于保证一组操作要么全部成功,要么全部失败。
典型场景:转账、下单、库存扣减。
事务隔离级别有哪些?
常见隔离级别:
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
隔离级别越高,一致性越强,但并发性能可能越低。
常见问题:
- 脏读
- 不可重复读
- 幻读
Redis 常见用途有哪些?
Redis 常见用途:
- 缓存
- 分布式锁
- 限流
- session 存储
- 排行榜
- 消息队列,简单场景
- 计数器
面试时要说明 Redis 快,是因为它主要在内存中操作,并且数据结构丰富。
缓存穿透、击穿、雪崩是什么?
缓存穿透:查询不存在的数据,缓存和数据库都查不到,请求直接打到数据库。
解决:
- 缓存空值
- 布隆过滤器
- 参数校验
缓存击穿:热点 key 过期,大量请求同时打到数据库。
解决:
- 热点 key 不过期
- 互斥锁
- 提前刷新
缓存雪崩:大量 key 同时过期或缓存服务不可用。
解决:
- 过期时间加随机值
- 多级缓存
- 降级限流
- Redis 高可用
缓存和数据库一致性怎么处理?
常见策略:
- 先更新数据库,再删除缓存
- 设置缓存过期时间兜底
- 使用消息队列异步删除或更新缓存
- 对强一致场景直接读数据库
大多数业务追求最终一致性,不一定要强一致。
面试时要说明:缓存是性能优化手段,不应该成为唯一真实数据源。
慢查询怎么排查?
常见步骤:
- 查看慢查询日志。
- 使用
EXPLAIN分析执行计划。 - 检查索引是否命中。
- 检查返回数据量是否过大。
- 检查是否有锁等待。
- 检查 SQL 是否可以拆分或改写。
- 必要时做分表、归档或缓存。
数据库面试追问
如何设计用户表?
用户表通常包含用户 ID、账号、手机号、邮箱、密码哈希、昵称、头像、状态、创建时间和更新时间。登录凭证、用户资料、角色权限可以按复杂度拆表,避免一张表越来越臃肿。
需要注意唯一索引,例如手机号、邮箱、用户名;密码必须存哈希值,不能明文存储。软删除、冻结、注销等状态要和业务流程区分清楚。
如何设计订单表?
订单表核心字段包括订单号、用户 ID、商品或业务对象 ID、金额、状态、支付状态、创建时间、支付时间和关闭时间。订单号要全局唯一,金额字段要用整数分或高精度类型。
复杂业务通常会拆成订单主表、订单明细表、支付流水表和状态变更记录表。回答时要强调订单状态机,避免状态随意跳转。
如何处理软删除?
软删除通常通过 deleted_at 或 is_deleted 标记数据不可见,适合需要恢复、审计或保留历史的业务。查询时要统一加过滤条件,避免误查已删除数据。
缺点是表会变大,唯一索引也要考虑软删除后的重复问题。长期数据可以归档到历史表,减少主表压力。
如何做数据迁移?
数据迁移要先评估影响范围,准备迁移脚本、备份、灰度和回滚方案。大表迁移不能一次性锁表,通常要分批处理,并控制批次大小和执行时间。
更稳妥的流程是:新增字段或新表,双写或增量同步,校验数据一致性,切读流量,观察稳定后再清理旧结构。
如何处理数据库连接池?
连接池用于复用数据库连接,减少频繁创建连接的成本。关键参数包括最大连接数、最小空闲连接、连接超时、空闲回收时间和健康检查。
连接池过小会导致请求排队,过大会压垮数据库。排查连接池问题时要看活跃连接数、等待队列、慢查询、事务是否长时间不提交。
如何做读写分离?
读写分离通常是主库负责写,从库负责读,通过中间件或应用层路由读写请求。它适合读多写少的业务,可以减轻主库压力。
需要注意主从延迟。对刚写完马上要读的场景,可以读主库、加延迟保护或根据业务做一致性兜底。
如何做分库分表?
分库分表适合单表数据量或写入压力非常大,普通索引优化、归档、缓存都无法解决的场景。常见分片键是用户 ID、租户 ID、订单 ID 等高频查询维度。
难点在跨分片查询、分页、事务、扩容和热点数据。面试时要说明:分库分表是最后手段,先确认瓶颈再做,不能为了架构复杂而复杂。
如何保证支付回调幂等?
支付回调可能重复到达,所以必须幂等处理。常见做法是用支付流水号或回调事件 ID 建唯一索引,先查订单状态,只有待支付状态才允许更新为已支付。
更新订单、写流水、发消息最好放在事务里,后续通知用可靠消息或补偿任务。回答时要强调:不能只依赖前端防重复,支付正确性必须由后端状态机和唯一约束保证。