数据库与中间件面试知识点
3724字约12分钟
2025-11-24
本文件汇总本大纲中所有偏 数据库、NoSQL、OLAP、消息队列、中间件、事务与索引 的知识点,包括 MongoDB、Redis、Elasticsearch、ClickHouse、Kafka,以及 MySQL / PostgreSQL 的事务与存储引擎说明。
1. NoSQL 与中间件总览
本节对应原文第 4 章的数据库与中间件部分,并补充相关问答。
1.1 MongoDB:Oplog 监听与聚合优化
1.1.1 Oplog 与 Change Streams
- Oplog 是 MongoDB 副本集的固定大小集合,记录所有写操作。
- 传统 CDC 可以直接读
local.oplog.rs,但实现复杂。 - Change Streams(3.6+)提供高级事件流 API:
- 支持按库、集合或聚合管道过滤。
- 提供 resume token,便于断点续传。
- 权限控制更精细,安全性更好。
1.1.2 聚合管道优化与 ESR 索引原则
- 聚合管道优化:
$match/$project尽量前置,减少后续阶段数据量。- 使用
explain()查看是否命中索引。 - 超过内存限制时需
allowDiskUse: true,但性能会下降。
- 索引策略(ESR 原则):
- Equality(等值)字段放最前。
- Sort(排序)字段放中间。
- Range(范围)字段放最后。
1.2 Redis
1.2.1 数据结构与使用场景(简表)
- String:最基础类型,适合简单 KV 与计数器。
- Hash:适合存储对象属性,如用户信息。
- List:队列/栈,消息队列、最近访问列表。
- Set:去重、集合运算,适合独立访客、好友关系。
- Sorted Set:排行榜、时间序列、优先级队列。
- Bitmap:大规模布尔值存储,如签到日志、权限位。
- HyperLogLog:近似去重统计,如 UV。
- Stream:持久化消息队列,支持消费者组。
1.2.2 主从同步、RDB 与 AOF
- 初次同步:主库 fork 子进程生成 RDB → 通过 TCP 传给从库 → 从库加载 RDB 作为基线。
- 命令传播:主库将写命令以 AOF 协议文本形式实时推给从库,从库在内存重放。
- 断点续传:
- 主从维护复制偏移量。
- 主库通过
repl_backlog循环缓冲区保存最近命令。 - 从库重连时带 offset,若仍在 backlog 范围内则增量补齐,否则重新全量。
面试一句话:“第一次给快照,后面给命令,断线补命令,补不齐再给快照。”
1.2.3 分布式锁与缓存问题
- Redlock:多 master 节点 + 大多数成功原则,提升锁可靠性。
- 缓存穿透:布隆过滤器或缓存空对象。
- 缓存雪崩:过期时间加随机值、多级缓存、预热。
- 缓存击穿:互斥锁或永不过期 + 后台刷新。
1.3 Elasticsearch
1.3.1 Mapping 与分片设计
- 字段类型:
- text vs keyword:全文检索 vs 精确过滤/排序/聚合。
- 数值/日期类型选择合适精度。
- 分词器:中文常用 IK,自定义分词器通过 tokenizer + filter 组合。
- Dynamic Mapping:生产一般禁用,改为
"dynamic": "strict"手工定义。 - 分片权衡:
- 过多分片浪费资源,查询变慢。
- 过少分片则单分片过大(建议 30–50GB 内)。
1.3.2 查询 DSL 与分页优化
- Filter vs Query:
- 参与相关性计算的放 Query。
- 纯过滤条件放 Filter,可缓存、性能更高。
- 深度分页:
from+size深分页成本高。- 大批量遍历用 Scroll。
- 实时分页用
search_after。
1.3.3 倒排索引与 BM25
- 倒排索引:term → posting list(文档 ID 列表)。
- TF-IDF/BM25 评分:考虑词频、文档频率、文档长度,BM25 是 ES 默认算法。
1.4 ClickHouse
1.4.1 MergeTree 家族
- MergeTree:基础引擎,按主键排序存储。
- ReplacingMergeTree:同主键时保留最新版本。
- Summing/Aggregating/Collapsing/VersionedCollapsingMergeTree 等分别支持预聚合和折叠删除场景。
1.4.2 物化视图与分区
- 物化视图:在写入时预计算聚合结果,查询直接扫小表。
- 分区:
PARTITION BY常用日期等字段。- 既能加速查询,又能方便删除旧数据(直接 drop 分区)。
1.4.3 跳数索引(Data Skipping Indices)
- 通过 minmax/set/bloom_filter 等索引减少无关数据块扫描。
- 适合范围、等值、高基数等不同场景。
1.5 Kafka(作为消息中间件)
1.5.1 分区、ISR 与幂等生产者
- 分区:实现水平扩展与并行消费;分区数决定并行度上限。
- ISR:与 leader 保持同步的副本集合,保证故障切换时不丢数据。
- 幂等生产者:
- 开启
enable.idempotence=true。 - 通过
PID + 序列号避免重试导致的重复写入。
- 开启
- 事务消息:在幂等基础上实现多分区原子写(两阶段提交)。
1.5.2 Kafka 如何保证有序?
- 仅在 单分区 内保证顺序:
- 生产者按 offset 追加。
- 消费者按 offset 顺序拉取。
- 相关消息进同一分区:
- 使用相同的 key:
ProducerRecord(topic, key, value)。 - 或强制写入指定分区。
- 使用相同的 key:
- 防止重试乱序:
max.in.flight.requests.per.connection=1。
面试总结:“Kafka 的有序性是分区级的,key 决定路由,分区决定顺序;全局有序只能靠单分区或业务端排序。”
1.5.3 消费者组与消费者关系
- 消费者:一个进程/线程实例,从若干分区拉取消息。
- 消费者组:一组消费者共同消费一个 topic,实现负载均衡和容错。
- 组内互斥:一个分区同一时刻只能被组内一个消费者消费。
- 组间共享:不同组可以各自完整消费同一 topic,类似广播。
- Rebalance:组成员变化或分区变化时重新分配分区。
2. MySQL 与 PostgreSQL
2.1 InnoDB 与索引结构
2.1.1 InnoDB 是什么?
- MySQL 默认事务型存储引擎。
- 核心特性:
- 完整事务(ACID)。
- MVCC 提供非阻塞读。
- 行级锁 + 间隙锁。
- 外键约束。
- 聚簇索引:数据按主键顺序存储。
- Redo/Undo 日志提供崩溃恢复。
2.1.2 聚簇索引、二级索引、唯一索引、联合索引的数据结构
- 底层统一为 B+ 树,差别在于叶子节点存啥:
| 类型 | 叶子节点内容 | 回表 | 说明 |
|---|---|---|---|
| 聚簇索引 | 主键 + 整行数据 | 否 | 表本身 |
| 二级索引 | 索引列 + 主键 | 是 | 根据主键回表查整行 |
| 唯一索引 | 索引列 + 主键(唯一约束) | 是 | 插入时多一步查重 |
| 联合索引 | (a,b,c)+ 主键 | 是或否 | 覆盖索引时可不回表 |
- 为什么二级索引存主键而不是物理指针?
- 行移动/页分裂时主键稳定,指针不稳定。
2.1.3 联合索引与最左前缀原则
(a,b,c)的索引可以支持:WHERE a=...WHERE a=... AND b=...WHERE a=... AND b=... AND c=...
WHERE b=1 AND c=2:不能用索引前缀定位起点,基本无法有效利用索引。WHERE a=1 AND c=2:- 能用到
a(范围扫描所有a=1)。 c=2需要在扫描结果中逐行判断(或通过 ICP 在存储引擎层过滤)。
- 能用到
- 高频查询
a=1 AND c=2时,应专门建立(a,c)索引。
2.2 MySQL 索引类型与失效场景
- 索引类型:
- 聚簇索引、二级索引、唯一索引、联合索引、前缀索引、函数索引(8.0+)、全文索引。
- 典型索引失效场景:
- 违反最左前缀(联合索引跳过最左列)。
- 在列上使用函数或表达式。
- 隐式类型转换(字符串列传数字)。
LIKE '%xxx'前缀模糊。OR条件中有未索引列。!= / <> / NOT IN等导致优化器放弃索引。- 小表直接走全表扫描更快。
检查索引是否生效:
EXPLAIN SELECT ...:- 看
key是否为期望索引。 type、rows、Extra(Using index / Using where / Using temporary 等)。
- 看
EXPLAIN FORMAT=JSON查看used_key_parts。optimizer_trace深入查看优化器选择过程。
2.2.1 索引的数据结构类型
索引按底层数据结构可分为以下几类:
B+ 树索引(最常用)
- 特点:
- 多路平衡树,所有数据在叶子节点,非叶子节点只存键值。
- 叶子节点通过指针连接,支持范围查询和排序。
- 树高度低(通常 3-4 层),查询稳定 O(log n)。
- 适用场景:范围查询、排序、等值查询、前缀匹配。
- 使用数据库:MySQL(InnoDB)、Oracle、SQL Server。
B-link 树索引(B+ 树的并发优化变体)
- 特点:
- 基于 B+ 树,但增加了右链接(right-link)指针和 high key。
- 每个内部节点指向右兄弟节点,便于并发插入时减少锁争用。
- 采用 Lehman-Yao 算法,支持高并发读写。
- 查询性能与 B+ 树相同,但并发写入性能更好。
- 适用场景:高并发 OLTP、需要频繁插入的场景。
- 使用数据库:PostgreSQL(B-tree 索引的底层实现)。
哈希索引
- 特点:
- 基于哈希表,O(1) 等值查询。
- 不支持范围查询、排序、前缀匹配。
- 冲突处理(链地址法或开放寻址)。
- 适用场景:纯等值查询、内存表。
- 使用数据库:
- MySQL Memory 引擎支持哈希索引。
- PostgreSQL 支持哈希索引(但 B-link 树更常用)。
- Redis 内部大量使用哈希表。
位图索引(Bitmap Index)
- 特点:
- 为每个唯一值建立位向量,1 表示该行包含该值。
- 适合低基数列(如性别、状态)。
- 多列位图索引可用位运算快速合并。
- 适用场景:OLAP、数据仓库、低基数列过滤。
- 使用数据库:Oracle、PostgreSQL(支持)、ClickHouse(部分场景)。
LSM 树(Log-Structured Merge-Tree)
- 特点:
- 写入先写内存(MemTable),再刷到磁盘(SSTable)。
- 后台合并多个 SSTable,形成层级结构。
- 写性能高,读可能需要查多级。
- 适用场景:写多读少、时序数据、日志存储。
- 使用数据库:RocksDB、LevelDB、Cassandra、MongoDB(WiredTiger 可选)。
倒排索引(Inverted Index)
- 特点:
- term → posting list(文档 ID 列表)。
- 支持全文检索、相关性评分。
- 适用场景:全文搜索、搜索引擎。
- 使用数据库:Elasticsearch、Solr、PostgreSQL(GIN 索引)。
跳数索引(Skipping Index)
- 特点:
- 存储数据块(chunk)的 min/max/set/bloom_filter 等元数据。
- 查询时先过滤数据块,再扫描剩余块。
- 适用场景:列式存储、OLAP 查询。
- 使用数据库:ClickHouse。
2.2.2 各数据库索引实现对比
| 数据库 | 主要索引类型 | 特点与差异 |
|---|---|---|
| MySQL (InnoDB) | B+ 树 | • 聚簇索引:数据按主键顺序存储 • 二级索引:存索引列+主键,需回表 • 支持全文索引(FULLTEXT,5.6+) • 8.0+ 支持函数索引、表达式索引 |
| PostgreSQL | B-link 树(B-tree) | • 堆表结构,索引与数据分离 • B-tree 索引底层采用 B-link 树(Lehman-Yao 算法) • 支持多种索引类型:B-tree、Hash、GIN、GiST、SP-GiST、BRIN • GIN:倒排索引,适合数组、全文检索 • BRIN:块范围索引,适合时序数据 • B-link 树通过右链接提高并发写入性能 |
| MongoDB | B+ 树(WiredTiger) | • 支持单字段、复合、多键、文本、地理空间索引 • 复合索引遵循 ESR 原则 • 稀疏索引:只索引存在该字段的文档 • TTL 索引:自动过期删除 |
| Elasticsearch | 倒排索引 + FST | • 倒排索引:term → posting list • FST(有限状态转换器):压缩 term 字典 • Doc Values:列式存储,用于排序/聚合 • 支持 nested、join 等复杂结构索引 |
| ClickHouse | 主键索引 + 跳数索引 | • 主键:稀疏索引,每 N 行一个索引项 • 跳数索引:minmax/set/bloom_filter 等 • 列式存储,索引用于跳过数据块 • 不支持传统 B+ 树二级索引 |
| Redis | 哈希表 + 跳表 | • 哈希表:String、Hash、Set 底层 • 跳表(Skip List):Sorted Set 底层,O(log n) 有序 • 内存索引,无持久化索引结构 |
关键差异总结
存储结构:
- MySQL InnoDB:聚簇索引,数据与索引耦合。
- PostgreSQL:堆表,索引与数据分离,索引存 TID(行指针)。
- MongoDB:类似堆表,索引存文档位置。
索引类型丰富度:
- PostgreSQL 最丰富(B-link 树、Hash、GIN、GiST、BRIN 等)。
- MySQL 相对单一(主要是 B+ 树,8.0+ 增强)。
- ClickHouse 特殊(跳数索引,适合列式 OLAP)。
B+ 树 vs B-link 树:
- MySQL InnoDB:标准 B+ 树。
- PostgreSQL:B-link 树(B+ 树的并发优化变体),通过右链接减少锁争用,适合高并发写入。
全文检索:
- Elasticsearch:专业倒排索引 + BM25 评分。
- PostgreSQL:GIN 索引 + tsvector。
- MySQL:FULLTEXT 索引(功能相对简单)。
特殊场景:
- ClickHouse:列式 + 跳数索引,适合 OLAP。
- MongoDB:地理空间索引、TTL 索引。
- Redis:内存索引,无持久化索引结构。
面试要点:"MySQL 用聚簇索引+B+树耦合数据,PostgreSQL 用堆表+B-link树(Lehman-Yao算法,高并发优化)+多种索引类型,MongoDB 类似堆表但支持文档索引,ES 用倒排索引做全文检索,ClickHouse 用跳数索引做 OLAP 加速。"
2.3 事务隔离级别与幻读/脏读
2.3.1 脏读、不可重复读、幻读
- 脏读:读到其他事务未提交的数据。
- 不可重复读:同一事务两次读取同一行结果不同(update)。
- 幻读:同一事务两次范围查询,返回行数不同(insert/delete)。
2.3.2 隔离级别对比
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| RU | 可能 | 可能 | 可能 |
| RC | 否 | 可能 | 可能 |
| RR | 否 | 否 | 理论可能,InnoDB 通过 Next-Key Lock 抑制 |
| Serializable | 否 | 否 | 否 |
2.3.3 InnoDB 的实现:MVCC + Next-Key Lock
- MVCC:
- 行隐藏列
DB_TRX_ID / DB_ROLL_PTR。 - Undo Log 保存历史版本。
- Read View 决定可见性。
- 行隐藏列
- 幻读处理:
- RR 级别下,当前读通过间隙锁 + 行锁组成 Next-Key Lock 锁定范围,阻止幻读插入。
2.4 PostgreSQL vs MySQL 隔离级别差异
- 默认隔离级别:
- PostgreSQL:RC。
- MySQL(InnoDB):RR。
- RR 下的幻读:
- PostgreSQL:基于 快照隔离 (Snapshot Isolation),事务内所有读基于同一快照,天然避免幻读。
- MySQL:通过 Next-Key Lock 锁住范围,有副作用(锁冲突/死锁)。
- Serializable:
- PostgreSQL:使用 SSI(Serializable Snapshot Isolation),乐观检测冲突,冲突事务回滚。
- MySQL:更偏向锁表/重度锁,吞吐量较低。
一句话:“PostgreSQL 用更现代的快照与 SSI 解决幻读和串行化,MySQL 则主要依赖锁。”
3. 高频问答汇总
3.1 Redis 数据类型与典型场景
- 已在 1.2.1 中简表总结,可按 String/Hash/List/Set/ZSet/Bitmap/HyperLogLog/Stream 记忆并结合业务场景。
3.2 MySQL 主从同步与一致性
- 复制链路:
- 主库事务提交 → 写 binlog。
- 从库 IO 线程拉 binlog 存 relay log。
- 从库 SQL 线程重放 relay log。
- 一致性手段:
- 异步复制(默认):速度快但可能丢 binlog。
- 半同步复制:至少一个从库 ack 才认为提交成功。
- 组复制/MGR:基于 Paxos/raft 的强一致集群。
- GTID:全局事务 ID,方便 failover 与并行复制。
3.3 Kafka 消费顺序与消费者组(简要回顾)
- 有序性:分区级别,依赖 key 路由与分区内 offset 顺序。
- 消费者组:组内分区互斥、组间共享,实现负载均衡和广播。
- Rebalance:成员变化或分区变化触发,影响消费延迟但不破坏分区内顺序。
