Skip to content

全栈面试:数据库与缓存

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 高可用

缓存和数据库一致性怎么处理?

常见策略:

  • 先更新数据库,再删除缓存
  • 设置缓存过期时间兜底
  • 使用消息队列异步删除或更新缓存
  • 对强一致场景直接读数据库

大多数业务追求最终一致性,不一定要强一致。

面试时要说明:缓存是性能优化手段,不应该成为唯一真实数据源。

慢查询怎么排查?

常见步骤:

  1. 查看慢查询日志。
  2. 使用 EXPLAIN 分析执行计划。
  3. 检查索引是否命中。
  4. 检查返回数据量是否过大。
  5. 检查是否有锁等待。
  6. 检查 SQL 是否可以拆分或改写。
  7. 必要时做分表、归档或缓存。

数据库面试追问

如何设计用户表?

用户表通常包含用户 ID、账号、手机号、邮箱、密码哈希、昵称、头像、状态、创建时间和更新时间。登录凭证、用户资料、角色权限可以按复杂度拆表,避免一张表越来越臃肿。

需要注意唯一索引,例如手机号、邮箱、用户名;密码必须存哈希值,不能明文存储。软删除、冻结、注销等状态要和业务流程区分清楚。

如何设计订单表?

订单表核心字段包括订单号、用户 ID、商品或业务对象 ID、金额、状态、支付状态、创建时间、支付时间和关闭时间。订单号要全局唯一,金额字段要用整数分或高精度类型。

复杂业务通常会拆成订单主表、订单明细表、支付流水表和状态变更记录表。回答时要强调订单状态机,避免状态随意跳转。

如何处理软删除?

软删除通常通过 deleted_atis_deleted 标记数据不可见,适合需要恢复、审计或保留历史的业务。查询时要统一加过滤条件,避免误查已删除数据。

缺点是表会变大,唯一索引也要考虑软删除后的重复问题。长期数据可以归档到历史表,减少主表压力。

如何做数据迁移?

数据迁移要先评估影响范围,准备迁移脚本、备份、灰度和回滚方案。大表迁移不能一次性锁表,通常要分批处理,并控制批次大小和执行时间。

更稳妥的流程是:新增字段或新表,双写或增量同步,校验数据一致性,切读流量,观察稳定后再清理旧结构。

如何处理数据库连接池?

连接池用于复用数据库连接,减少频繁创建连接的成本。关键参数包括最大连接数、最小空闲连接、连接超时、空闲回收时间和健康检查。

连接池过小会导致请求排队,过大会压垮数据库。排查连接池问题时要看活跃连接数、等待队列、慢查询、事务是否长时间不提交。

如何做读写分离?

读写分离通常是主库负责写,从库负责读,通过中间件或应用层路由读写请求。它适合读多写少的业务,可以减轻主库压力。

需要注意主从延迟。对刚写完马上要读的场景,可以读主库、加延迟保护或根据业务做一致性兜底。

如何做分库分表?

分库分表适合单表数据量或写入压力非常大,普通索引优化、归档、缓存都无法解决的场景。常见分片键是用户 ID、租户 ID、订单 ID 等高频查询维度。

难点在跨分片查询、分页、事务、扩容和热点数据。面试时要说明:分库分表是最后手段,先确认瓶颈再做,不能为了架构复杂而复杂。

如何保证支付回调幂等?

支付回调可能重复到达,所以必须幂等处理。常见做法是用支付流水号或回调事件 ID 建唯一索引,先查订单状态,只有待支付状态才允许更新为已支付。

更新订单、写流水、发消息最好放在事务里,后续通知用可靠消息或补偿任务。回答时要强调:不能只依赖前端防重复,支付正确性必须由后端状态机和唯一约束保证。

Built with VitePress and GitHub Pages.