💽

数据库面试指南

数据库面试指南


一、MySQL 索引

1.1 B+ 树原理

B+ 树特点: - 非叶子节点只存储键值,不存储数据 - 叶子节点存储所有键值和数据,通过链表连接 - 所有叶子节点在同一层
                    [30|60]
                   /   |   \
            [10|20] [40|50] [70|80]
               |       |       |
            叶子节点通过链表连接
为什么用 B+ 树: | 对比 | B 树 | B+ 树 | |——|——|——-| | 数据存储 | 所有节点 | 仅叶子节点 | | 范围查询 | 需中序遍历 | 叶子链表直接遍历 | | 磁盘 IO | 较多 | 较少 |

1.2 聚簇索引与非聚簇索引

类型
叶子节点存储
数量
聚簇索引
完整行数据
1 个
非聚簇索引
主键值
多个
回表:二级索引 → 主键 → 聚簇索引查完整数据

1.3 覆盖索引

查询列都在索引中,无需回表:
-- 索引 idx(name, age)
SELECT name, age FROM user WHERE name = 'Tom'; -- 覆盖索引
SELECT * FROM user WHERE name = 'Tom';          -- 需要回表

1.4 最左前缀原则

联合索引 (a, b, c) 的使用:
查询条件
是否走索引
a = 1
a = 1 AND b = 2
a = 1 AND b = 2 AND c = 3
b = 2
a = 1 AND c = 3
✅ 部分(只用 a)

1.5 索引失效场景

-- 1. 对索引列使用函数
WHERE YEAR(create_time) = 2024  --WHERE create_time >= '2024-01-01' ---- 2. 隐式类型转换
WHERE phone = 13800138000  -- ❌ phone 是 varchar
WHERE phone = '13800138000' ---- 3. LIKE 左模糊
WHERE name LIKE '%Tom'  --WHERE name LIKE 'Tom%'  ---- 4. OR 条件(部分列无索引)
WHERE a = 1 OR b = 2  -- b 无索引则全表扫描

-- 5. !=NOT IN
WHERE status != 1  -- 可能全表扫描

1.6 索引下推(ICP)

Index Condition Pushdown:将 WHERE 条件下推到存储引擎层过滤
-- 索引 idx(name, age)
SELECT * FROM user WHERE name LIKE 'Tom%' AND age = 25;
无 ICP
有 ICP
回表后再过滤 age
在索引层直接过滤 age
回表次数多
回表次数少

1.7 联合索引设计原则

  1. 高选择性列在前:区分度高的列放前面
  1. 等值查询列在前= 条件的列优先于范围条件
  1. 覆盖常用查询:避免回表
  1. 避免冗余索引(a, b) 已包含 (a) 的功能
-- 查询:WHERE status = 1 AND create_time > '2024-01-01' ORDER BY id
-- 推荐索引:(status, create_time)  (status, id)

1.8 前缀索引

对长字符串只索引前 N 个字符:
ALTER TABLE user ADD INDEX idx_email(email(10));
选择性计算
SELECT COUNT(DISTINCT LEFT(email, 10)) / COUNT(*) FROM user;
-- 选择性越接近 1 越好
缺点:无法用于 ORDER BY 和覆盖索引

二、MySQL 事务

2.1 ACID 特性

特性
说明
实现
原子性
全成功或全失败
undo log
一致性
状态一致
其他三者保证
隔离性
并发互不干扰
锁 + MVCC
持久性
提交后永久保存
redo log

2.2 隔离级别

级别
脏读
不可重复读
幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
⚠️
SERIALIZABLE
MySQL 默认 REPEATABLE READ

2.3 MVCC 原理

核心组件: 1. 隐藏列:DB_TRX_IDDB_ROLL_PTR 2. undo log 版本链 3. ReadView
ReadView 可见性判断
trx_id < min_trx_id → 可见(已提交)
trx_id >= max_trx_id → 不可见(未来事务)
trx_id in m_ids → 不可见(活跃事务)
RC vs RR: - RC:每次 SELECT 新建 ReadView - RR:首次 SELECT 创建,后续复用

2.4 当前读与快照读

-- 快照读(MVCCSELECT * FROM user WHERE id = 1;

-- 当前读(加锁)
SELECT * FROM user WHERE id = 1 FOR UPDATE;
INSERT/UPDATE/DELETE

三、MySQL 锁

3.1 锁分类

类型
粒度
并发度
表锁
整表
行锁
单行
模式
说明
S 锁
共享锁,读锁
X 锁
排他锁,写锁

3.2 行锁类型

类型
说明
Record Lock
锁定单行
Gap Lock
锁定间隙,防止插入
Next-Key Lock
Record + Gap
索引值: 10, 20, 30
Next-Key Lock 锁定 (10, 20]

3.3 死锁

产生条件:互斥、占有等待、不可剥夺、循环等待
解决: - 固定加锁顺序 - 设置锁超时 innodb_lock_wait_timeout - 死锁检测 innodb_deadlock_detect

3.4 乐观锁与悲观锁

-- 悲观锁
SELECT * FROM stock WHERE id = 1 FOR UPDATE;
UPDATE stock SET count = count - 1 WHERE id = 1;

-- 乐观锁(版本号)
UPDATE stock SET count = count - 1, version = version + 1
WHERE id = 1 AND version = #{version};

四、MySQL 日志

4.1 三大日志

日志
作用
层级
redo log
崩溃恢复(持久性)
InnoDB
undo log
事务回滚(原子性)+ MVCC
InnoDB
binlog
主从复制、数据恢复
Server

4.2 redo log

WAL(Write-Ahead Logging):先写日志,再写磁盘
事务提交流程:
1. 写 redo log(prepare)
2. 写 binlog
3. 写 redo log(commit)
两阶段提交:保证 redo log 和 binlog 一致性

4.3 Buffer Pool

内存缓冲池:缓存数据页和索引页,减少磁盘 IO
Buffer Pool 结构:
- Free List:空闲页链表
- LRU List:已使用页链表(冷热分离)
- Flush List:脏页链表
LRU 优化: - 新页先放入 old 区(3/8 位置) - 访问超过 1s 后才移到 young 区 - 避免全表扫描污染缓存

4.4 InnoDB vs MyISAM

特性
InnoDB
MyISAM
事务
行锁
❌(表锁)
外键
MVCC
崩溃恢复
redo log
全文索引
5.6+ 支持
存储结构
聚簇索引
堆表
选择建议: - 需要事务、高并发 → InnoDB - 只读、统计分析 → MyISAM(已不推荐)

4.5 一条 SQL 执行流程

客户端 → 连接器 → 查询缓存(8.0废弃) → 分析器 → 优化器 → 执行器 → 存储引擎

五、SQL 优化

5.1 EXPLAIN 关键字段

字段
说明
关注点
type
访问类型
system > const > eq_ref > ref > range > index > ALL
key
使用的索引
NULL 表示未使用索引
rows
扫描行数
越小越好
Extra
额外信息
Using index(覆盖索引)、Using filesort(需优化)

5.2 慢查询优化

  1. 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
  1. 分析执行计划
EXPLAIN SELECT ...;
  1. 常见优化
  • 添加合适索引
  • 避免 SELECT *
  • 分页优化
  • 避免索引失效

5.3 分页优化

-- 深分页问题
SELECT * FROM user LIMIT 1000000, 10; ---- 优化:延迟关联
SELECT * FROM user
WHERE id >= (SELECT id FROM user LIMIT 1000000, 1)
LIMIT 10;

-- 优化:记录上次 ID
SELECT * FROM user WHERE id > #{lastId} LIMIT 10;

5.4 ORDER BY 优化

-- 利用索引排序(索引 idx(status, create_time)SELECT * FROM orders WHERE status = 1 ORDER BY create_time;  -- ✅ 使用索引

-- 无法使用索引排序(需要 filesort)
SELECT * FROM orders WHERE status > 0 ORDER BY create_time;  -- ❌ 范围查询后无法用索引排序
filesort 优化: - 增大 sort_buffer_size - 只 SELECT 需要的列 - 添加合适的联合索引

5.5 JOIN 优化

-- 小表驱动大表
SELECT * FROM small_table s
JOIN large_table l ON s.id = l.foreign_id;

-- 确保被驱动表的关联字段有索引
ALTER TABLE large_table ADD INDEX idx_foreign_id(foreign_id);
JOIN 算法: | 算法 | 说明 | 适用场景 | |——|——|———-| | Index Nested-Loop | 被驱动表用索引 | 有索引 | | Block Nested-Loop | 批量匹配 | 无索引 | | Hash Join | 8.0+ 哈希匹配 | 大表等值连接 |

5.6 子查询 vs JOIN

-- 子查询(可能效率低)
SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE status = 1);

-- 改写为 JOIN
SELECT o.* FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 1;
原则:优先使用 JOIN,避免相关子查询

5.7 COUNT 优化

COUNT(*)COUNT(1) > COUNT(主键) > COUNT(字段)
  • COUNT(*)COUNT(1) 会被优化器优化
  • COUNT(字段) 不统计 NULL

六、分库分表

6.1 拆分方式

方式
说明
场景
垂直拆分
按业务拆表/库
表太多、字段太多
水平拆分
按数据拆分
数据量太大

6.2 分片策略

策略
优点
缺点
范围分片
扩容方便
热点问题
哈希分片
数据均匀
扩容困难
一致性哈希
扩容影响小
实现复杂

6.3 分布式 ID

雪花算法
0 | 41位时间戳 | 10位机器ID | 12位序列号
  • 64 位 long 型
  • 趋势递增
  • 每毫秒可生成 4096 个 ID

6.4 跨库 JOIN 解决方案

  1. 全局表:将小表同步到所有分库
  1. 字段冗余:将常用字段冗余到主表
  1. 应用层组装:分别查询后在应用层合并
  1. 数据同步到数仓:通过 ETL 同步后查询

6.5 数据迁移方案

双写方案
1. 新库创建表结构
2. 开启双写(同时写新旧库)
3. 历史数据迁移
4. 数据校验
5. 切换读流量到新库
6. 停止旧库写入
binlog 同步方案
1. 使用 Canal/DTS 同步 binlog
2. 全量同步 + 增量追平
3. 数据校验
4. 切换流量

6.6 分布式事务

方案
说明
一致性
2PC
两阶段提交
强一致
TCC
Try-Confirm-Cancel
最终一致
Saga
补偿事务
最终一致
本地消息表
消息 + 定时任务
最终一致

七、主从复制

7.1 复制原理

主库 → binlog → IO线程 → 从库relay log → SQL线程 → 从库数据

7.2 主从延迟

原因: - 从库单线程重放 - 大事务 - 网络延迟
解决: - 并行复制 - 读写分离中间件 - 半同步复制

7.3 读写分离

实现方式: 1. 代码层:根据 SQL 类型路由 2. 中间件:MyCat、ShardingSphere、ProxySQL
注意事项: - 主从延迟导致读不到最新数据 - 解决:强制读主库、延迟判断、同步等待
// 强制读主库注解
@Master
public User getUser(Long id) {
    return userMapper.selectById(id);
}

7.4 半同步复制

异步复制:主库提交后立即返回,不等从库确认
半同步复制:至少一个从库确认收到 binlog 后才返回
-- 开启半同步复制
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
优点:减少数据丢失风险 缺点:增加响应延迟

7.5 主从切换

手动切换
1. 停止主库写入
2. 等待从库同步完成
3. 从库提升为主库
4. 修改应用配置
自动切换(MHA/Orchestrator)
1. 检测主库故障
2. 选择最新的从库
3. 补齐差异日志
4. 提升为新主库
5. 其他从库指向新主库
6. 通知应用层

八、Redis 基础

8.1 数据类型与底层结构

类型
底层结构
应用场景
String
SDS
缓存、计数器
List
quicklist
消息队列
Hash
ziplist/hashtable
对象存储
Set
intset/hashtable
去重、交并集
ZSet
ziplist/skiplist
排行榜

8.2 List 底层结构

quicklist(Redis 3.2+):双向链表 + ziplist
quicklist:
  head -> [ziplist] <-> [ziplist] <-> [ziplist] <- tail
优点: - ziplist 节省内存 - 链表支持快速两端操作 - 兼顾内存和性能

8.3 Hash 底层结构

条件
底层结构
元素少且值小
ziplist
其他情况
hashtable
ziplist 阈值: - hash-max-ziplist-entries:512 - hash-max-ziplist-value:64 字节

8.4 SDS(Simple Dynamic String)

struct sdshdr {
    int len;      // 已使用长度
    int free;     // 剩余空间
    char buf[];   // 数据
};
优点: - O(1) 获取长度 - 二进制安全 - 预分配减少内存分配

8.5 跳表(Skip List)

Level 3:  1 -----------------> 9
Level 2:  1 ------> 5 -------> 9
Level 1:  1 -> 3 -> 5 -> 7 -> 9
  • 查找时间复杂度 O(log n)
  • 实现简单,易于范围查询

8.6 Redis 为什么快

  1. 内存操作
  1. 单线程避免锁竞争(6.0 后 IO 多线程)
  1. IO 多路复用(epoll)
  1. 高效数据结构

九、Redis 持久化

9.1 RDB

快照方式:某时刻全量数据
触发方式:
- save:阻塞
- bgsave:fork 子进程
- 配置自动触发
优点:恢复快、文件小 缺点:可能丢失最后一次快照后的数据

9.2 AOF

追加日志:记录写命令
写入策略:
- always:每条命令都写,最安全
- everysec:每秒写一次(默认)
- no:由操作系统决定
AOF 重写:压缩 AOF 文件

9.3 混合持久化

RDB + AOF:RDB 做全量,AOF 做增量

十、Redis 过期与淘汰

10.1 过期删除策略

策略
说明
惰性删除
访问时检查是否过期
定期删除
定时随机抽查删除

10.2 内存淘汰策略

策略
说明
noeviction
不淘汰,内存满报错
allkeys-lru
所有 key 中 LRU
volatile-lru
有过期时间的 key 中 LRU
allkeys-random
随机淘汰
volatile-ttl
淘汰 TTL 最短的
allkeys-lfu
所有 key 中 LFU
volatile-lfu
有过期时间的 key 中 LFU

10.3 LRU vs LFU 实现

LRU(Least Recently Used): - Redis 使用近似 LRU - 随机采样 N 个 key,淘汰最久未访问的 - maxmemory-samples 控制采样数量
LFU(Least Frequently Used): - Redis 4.0+ 支持 - 基于访问频率淘汰 - 使用对数计数器 + 衰减因子
LFU 计数器结构:
高 16 位:上次访问时间(分钟级)
低 8 位:对数计数器(0-255
选择建议: - 访问模式稳定 → LRU - 有热点数据 → LFU

十一、Redis 高可用

11.1 主从复制

全量同步:RDB + 缓冲区命令
增量同步:offset 偏移量

11.2 哨兵模式

功能: - 监控主从状态 - 自动故障转移 - 通知客户端
选举流程: 1. 主观下线 → 客观下线 2. 哨兵选举 Leader 3. Leader 选择新主库 4. 通知从库和客户端

11.3 Cluster 集群

数据分片:16384 个哈希槽
HASH_SLOT = CRC16(key) mod 16384
特点: - 去中心化 - 自动故障转移 - 支持水平扩展

11.4 故障转移流程

Cluster 故障转移
1. 节点间定期 PING/PONG 心跳检测
2. 超时未响应标记为 PFAIL(疑似下线)
3. 超过半数主节点认为 PFAILFAIL(确认下线)
4. 从节点发起选举(Raft 协议)
5. 获得多数主节点投票的从节点晋升为主节点
6. 新主节点广播 PONG,通知集群配置变更
7. 其他从节点指向新主节点
选举优先级: - 复制偏移量最大的从节点优先 - 运行 ID 最小的从节点次之

十二、Redis 缓存问题

12.1 缓存穿透

问题:查询不存在的数据,直接打到数据库
解决: 1. 缓存空值 2. 布隆过滤器
# 布隆过滤器
if not bloom_filter.exists(key):
    return None  # 一定不存在

12.2 缓存击穿

问题:热点 key 过期,大量请求打到数据库
解决: 1. 互斥锁 2. 逻辑过期 3. 热点 key 永不过期
# 互斥锁
def get_data(key):
    data = redis.get(key)
    if data is None:
        if redis.setnx(lock_key, 1, ex=10):
            data = db.query(key)
            redis.set(key, data, ex=3600)
            redis.delete(lock_key)
        else:
            time.sleep(0.1)
            return get_data(key)
    return data

12.3 缓存雪崩

问题:大量 key 同时过期
解决: 1. 过期时间加随机值 2. 多级缓存 3. 熔断降级

12.4 热点 Key 问题

问题:单个 key 访问量过大,导致单节点压力过大
解决方案: 1. 本地缓存:L1 缓存热点数据 2. Key 分片key_1, key_2… 分散到多个节点 3. 读写分离:从节点分担读压力
# Key 分片示例
def get_hot_key(key):
    shard = random.randint(0, 9)
    return redis.get(f"{key}_{shard}")

12.5 大 Key 问题

定义: - String > 10KB - Hash/List/Set/ZSet 元素 > 5000
危害: - 内存不均衡 - 阻塞其他请求 - 网络带宽占用
发现
redis-cli --bigkeys
redis-cli memory usage <key>
解决: 1. 拆分:将大 Hash 拆成多个小 Hash 2. 压缩:对 value 进行压缩 3. 异步删除UNLINK 代替 DEL

12.6 缓存与数据库一致性

常用方案:先更新数据库,再删除缓存
1. 更新数据库
2. 删除缓存
3. 延迟双删(可选)
延迟双删
db.update(data)
redis.delete(key)
time.sleep(0.5)  # 等待主从同步
redis.delete(key)

十三、Redis 应用

13.1 分布式锁

SETNX 实现
-- 加锁
SET lock_key unique_value NX EX 30

-- 解锁(Lua 脚本保证原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
Redlock:多节点加锁,过半成功

13.2 延迟队列

使用 ZSet,score 为执行时间戳:
# 添加任务
redis.zadd("delay_queue", {task_id: execute_time})

# 消费任务
while True:
    tasks = redis.zrangebyscore("delay_queue", 0, now, limit=10)
    for task in tasks:
        if redis.zrem("delay_queue", task):
            process(task)

13.3 限流

滑动窗口
def is_allowed(user_id, limit, window):
    key = f"rate:{user_id}"
    now = time.time()

    # 移除窗口外的记录
    redis.zremrangebyscore(key, 0, now - window)

    # 检查数量
    count = redis.zcard(key)
    if count < limit:
        redis.zadd(key, {now: now})
        redis.expire(key, window)
        return True
    return False

13.4 排行榜

# 增加分数
redis.zincrby("leaderboard", 10, "user:1001")

# 获取排名
redis.zrevrank("leaderboard", "user:1001")

# 获取 Top 10
redis.zrevrange("leaderboard", 0, 9, withscores=True)

13.5 布隆过滤器

原理:使用位数组 + 多个哈希函数判断元素是否存在
# Redis 布隆过滤器(需要 RedisBloom 模块)
# 创建
BF.RESERVE user_filter 0.01 1000000  # 错误率 1%,容量 100 万

# 添加
BF.ADD user_filter user:1001

# 查询
BF.EXISTS user_filter user:1001  # 返回 1 表示可能存在,0 表示一定不存在
特点: - 空间效率高 - 有误判率(假阳性) - 不支持删除

13.6 HyperLogLog

用途:基数统计(去重计数)
# 添加元素
redis.pfadd("uv:page1", "user1", "user2", "user3")

# 获取基数
redis.pfcount("uv:page1")  # 返回 3

# 合并多个 HyperLogLog
redis.pfmerge("uv:total", "uv:page1", "uv:page2")
特点: - 固定 12KB 内存 - 标准误差 0.81% - 适合大数据量去重计数

13.7 GEO 地理位置

# 添加位置
redis.geoadd("locations", 116.405285, 39.904989, "beijing")
redis.geoadd("locations", 121.472644, 31.231706, "shanghai")

# 计算距离
redis.geodist("locations", "beijing", "shanghai", "km")  # 返回 1067.5

# 附近的人(半径 100km)
redis.georadius("locations", 116.405285, 39.904989, 100, "km")

# 获取 geohash
redis.geohash("locations", "beijing")
应用场景: - 附近的人/店铺 - 打车距离计算 - 外卖配送范围

十四、高频面试题与答案

Q1: B+ 树为什么适合做索引?

答案
  1. 磁盘 IO 优化
      • 非叶子节点只存键值,不存数据,单节点可存更多键
      • 树高度低(通常 3-4 层),减少磁盘 IO 次数
  1. 范围查询高效
      • 叶子节点通过双向链表连接
      • 范围查询只需遍历链表,无需回溯
  1. 查询稳定
      • 所有数据都在叶子节点,查询路径长度一致
      • 时间复杂度稳定 O(log n)

Q2: 聚簇索引和非聚簇索引区别?

答案
特性
聚簇索引
非聚簇索引(二级索引)
数据存储
叶子节点存储完整数据行
叶子节点存储主键值
数量
每表只能有一个
可以有多个
查询
直接获取数据
需要回表查询
排序
物理排序
逻辑排序
回表:通过二级索引找到主键,再通过主键到聚簇索引查找完整数据

Q3: 索引失效场景有哪些?

答案
  1. 对索引列使用函数WHERE YEAR(date) = 2024
  1. 隐式类型转换WHERE phone = 13800138000(phone 是 varchar)
  1. LIKE 左模糊WHERE name LIKE '%Tom'
  1. OR 条件部分列无索引
  1. 使用 != 或 NOT IN
  1. 联合索引不满足最左前缀
  1. 范围查询后的列失效

Q4: MVCC 原理是什么?

答案
Multi-Version Concurrency Control(多版本并发控制)
核心组件: 1. 隐藏列:DB_TRX_ID(事务ID)、DB_ROLL_PTR(回滚指针) 2. undo log:存储历史版本,形成版本链 3. ReadView:事务快照,决定可见性
可见性判断: - 创建 ReadView 时记录活跃事务列表 - 版本的 trx_id < min_trx_id → 可见 - 版本的 trx_id > max_trx_id → 不可见 - 版本的 trx_id 在活跃列表中 → 不可见

Q5: 事务隔离级别及解决的问题?

答案
隔离级别
脏读
不可重复读
幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
❌(InnoDB 通过间隙锁解决)
SERIALIZABLE
  • 脏读:读到未提交的数据
  • 不可重复读:同一事务内两次读取结果不同
  • 幻读:同一事务内两次查询行数不同

Q6: MySQL 锁类型有哪些?

答案
按粒度: | 锁类型 | 说明 | |——–|——| | 表锁 | 锁整张表,开销小,并发低 | | 行锁 | 锁单行,开销大,并发高 | | 间隙锁 | 锁索引间隙,防止幻读 | | 临键锁 | 行锁 + 间隙锁,左开右闭 |
按模式: - 共享锁(S):读锁,多个事务可同时持有 - 排他锁(X):写锁,独占 - 意向锁:表级锁,表示事务意图

Q7: redo log 和 binlog 区别?

答案
特性
redo log
binlog
层级
InnoDB 引擎层
MySQL Server 层
内容
物理日志(页修改)
逻辑日志(SQL 语句)
写入方式
循环写,固定大小
追加写,无限增长
用途
崩溃恢复
主从复制、数据恢复
写入时机
事务执行中
事务提交时

Q8: 慢查询如何优化?

答案
  1. 开启慢查询日志slow_query_log = ON
  1. 使用 EXPLAIN 分析:关注 type、key、rows、Extra
  1. 优化索引
      • 添加缺失索引
      • 优化联合索引顺序
      • 使用覆盖索引
  1. SQL 改写
      • 避免 SELECT *
      • 分页优化
      • 子查询改 JOIN
  1. 表结构优化:分库分表、归档历史数据

Q9: Redis 持久化方式?

答案
方式
原理
优点
缺点
RDB
定时快照
恢复快、文件小
可能丢失数据
AOF
记录写命令
数据安全
文件大、恢复慢
混合
RDB + 增量 AOF
兼顾两者优点
4.0+ 支持
AOF 重写:压缩 AOF 文件,只保留最终状态

Q10: 缓存穿透/击穿/雪崩的区别和解决方案?

答案
问题
描述
解决方案
穿透
查询不存在的数据,绕过缓存
布隆过滤器、缓存空值
击穿
热点 key 过期,大量请求打到 DB
互斥锁、永不过期
雪崩
大量 key 同时过期
随机过期时间、多级缓存

Q11: 分布式锁如何实现?

答案
Redis 实现
# 加锁
SET lock_key unique_value NX PX 30000

# 解锁(Lua 脚本保证原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
end
注意事项: - 设置合理过期时间 - 解锁时验证持有者 - 考虑锁续期(看门狗) - 集群环境使用 Redlock

Q12: 缓存一致性如何保证?

答案
推荐方案:先更新数据库,再删除缓存
1. 更新数据库
2. 删除缓存
3. 读请求时重建缓存
延迟双删
1. 删除缓存
2. 更新数据库
3. 延迟一段时间再删除缓存
最终一致性:订阅 binlog,异步更新缓存

Q13: 主从复制原理?

答案
1. 从库执行 CHANGE MASTER TO,配置主库信息
2. 从库启动 IO 线程,连接主库
3. 主库启动 binlog dump 线程,发送 binlog
4. 从库 IO 线程接收,写入 relay log
5. 从库 SQL 线程读取 relay log,重放 SQL
复制方式: - 异步复制:主库不等待从库确认 - 半同步复制:至少一个从库确认 - 组复制:Paxos 协议,强一致性

最后更新:2026-01-09
你觉得这篇文章怎么样?
YYDS
比心
加油
菜狗
views

Loading Comments...