欢迎光临
我们一直在努力

MySQL缓存策略与读写分离深度剖析

一、前言

在互联网系统中,数据库往往是性能瓶颈所在。随着用户量增长,数据库的读写压力急剧增加,如何提升MySQL的访问性能成为后台开发必须掌握的核心技能。

系统性能瓶颈分布(典型互联网架构):

用户请求 –> 网关 –> 业务逻辑层 –> 数据库层
|
v
这里是性能瓶颈
80%的系统延迟
集中在这里

本文从缓存策略、读写分离、连接池三个方向,深入剖析MySQL性能优化的核心方案。全文约12000字,建议收藏慢慢消化。


二、为什么需要缓存策略

2.1 性能对比——十万倍的差距

存储介质访问速度说明
内存(Redis) ~100纳秒 顺序访问内存
磁盘(MySQL) ~10毫秒 机械磁盘随机IO
SSD(MySQL) ~0.1毫秒 固态硬盘

性能差距计算:
磁盘 10ms / 内存 100ns = 100,000 倍

实际业务场景比喻:
– 内存访问 ≈ 从RAM取数据(毫秒级响应)
– 磁盘访问 ≈ 派人坐飞机去仓库取货(秒级响应)

结论:一次磁盘IO的时间,内存可以进行10万次IO操作

2.2 为什么MySQL需要缓存

MySQL自身缓冲层局限性:

1. LRU缓存机制
– MySQL会缓存最近执行的SQL和结果
– 基于LRU(最近最少使用)算法淘汰
– 与业务无关,无法按业务需求定制

2. 缓存内容受限
– 只能缓存查询结果
– 无法缓存用户定义的热点数据
– 缓存空间有限

3. 无法解决的根本问题
– 磁盘IO的根本瓶颈没有消除
– 高并发下依然会成为瓶颈

2.3 业务场景分析

什么样的业务需要缓存?

场景特征: 典型业务:
1. 读多写少(80%读 + 20%写) 新闻资讯、社交Feed、商品列表
2. 热点数据重复访问 用户信息、配置信息、排行榜
3. 数据一致性要求不高 日志查询、统计分析、历史记录
4. 需要毫秒级响应 秒杀系统、实时推荐、搜索提示

什么样的业务不需要缓存?

场景特征:
1. 写多读少(80%写 + 20%读) 监控系统、日志写入
2. 数据实时性要求极高 金融交易、库存扣减
3. 数据量小但一致性要求高 关系型核心业务


三、Redis与MySQL的数据状态分析

3.1 五种数据状态详解

状态MySQLRedis产生原因处理方式
1 有数据 无数据 正常,缓存未命中 读取后写入Redis
2 无数据 有数据 Redis写入后MySQL删除 异常,需删除Redis数据
3 有数据 有数据(不一致) 并发修改导致 异常,以MySQL为准
4 有数据 有数据(一致) 正常命中 最佳状态
5 无数据 无数据 正常,都无此数据 最佳状态

3.2 状态转换图

数据状态转换:

正常流程: 异常流程:
MySQL(有) + Redis(无) MySQL(无) + Redis(有)
| |
v v
读取后写入Redis 删除Redis缓存
| |
v v
MySQL(有) + Redis(有) MySQL(无) + Redis(无)
| ^
v |
状态4(最佳) 状态5(最佳)


四、缓存读写策略

4.1 读策略——Cache-Aside模式

核心原则:先读Redis,缓存不存在再读MySQL

Cache-Aside 读策略完整流程:

用户请求
|
v
+————————-+
| 步骤1:检查Redis缓存 |
+————————-+
|
+—- 有数据(命中)—-> 直接返回(约1ms)
|
+—- 无数据(未命中)—->
v
+————————-+
| 步骤2:查询MySQL数据库 |
+————————-+
|
+—- MySQL有数据 —->
| |
| v
| +————————-+
| | 步骤3:写入Redis缓存 |
| +————————-+
| |
| v
| 返回数据(约10ms)
|
+—- MySQL无数据 —-> 返回空
|
v
可选:缓存空值(防穿透)

代码实现:

import json
import redis
import pymysql

class CacheAside:
def __init__(self):
self.redis_client = redis.Redis(host='localhost', port=6379)
self.mysql_conn = pymysql.connect(host='localhost', user='root', password='***', database='app')

def get_user(self, user_id):
cache_key = ***"user:{user_id}"

# 第一步:查Redis
cached = self.redis_client.get(cache_key)
if cached:
return json.loads(cached)

# 第二步:Redis没有,查MySQL
with self.mysql_conn.cursor() as cursor:
sql = "SELECT id, name, email, created_at FROM users WHERE id = %s"
cursor.execute(sql, (user_id,))
result = cursor.fetchone()

# 第三步:写入Redis
if result:
user_data = {
'id': result[0],
'name': result[1],
'email': result[2],
'created_at': str(result[3])
}
# 缓存1小时
self.redis_client.setex(cache_key, 3600, json.dumps(user_data))
return user_data

return None

4.2 写策略——安全优先模式

核心原则:数据安全为主,先删Redis再写MySQL

安全优先写策略流程:

DML操作(INSERT/UPDATE/DELETE)
|
v
+————————-+
| 步骤1:删除Redis缓存 |
+————————-+
| 原因:防止脏数据被读取
v
+————————-+
| 步骤2:执行MySQL DML |
+————————-+
|
v
+————————-+
| 步骤3:MySQL同步到Redis |
+————————-+
| 可选:通过伪从数据库方案
v
完成

为什么先删缓存?

场景:用户修改了个人信息,但还没写入MySQL

如果先更新Redis:
– 事务回滚时,Redis是新的,MySQL是旧的,数据不一致
– 多线程并发时,可能读到MySQL的旧数据

如果先删缓存:
– 事务回滚时,Redis没有脏数据
– 其他线程读取时,会从MySQL获取正确数据

4.3 写策略——效率优先模式

适用场景:极少量写操作,大量读操作

效率优先写策略流程:

1. 先写Redis(设置过期时间)
2. 再写MySQL
3. MySQL同步到Redis(中间件完成)

过期时间设计:

# 过期时间设置逻辑
import time

def write_data(key, value):
# 过期时间 = MySQL写入耗时 + 同步到Redis耗时 + 安全余量
expire_time = 0.2 # 200ms

redis_client.setex(key, expire_time, value)

# 写入MySQL
mysql_client.execute("UPDATE …", value)

# MySQL会自动同步到Redis(通过伪从数据库方案)

安全分析:

阶段Redis状态MySQL状态数据正确性
正常情况 最新 最新 正确
MySQL写入失败 可能错误(过期时间内) 失败 Redis过期后正确
同步失败 可能错误(过期时间内) 成功 Redis过期后正确

结论: 200ms内可能读到错误数据,但概率极低,属于可接受范围。


五、缓存数据同步方案

5.1 方案对比

方案原理优点缺点推荐度
伪从数据库 伪装成从数据库,拉取binlog 不侵入业务,低延迟 需要额外组件 强烈推荐
UDF+触发器 触发器调用C++函数写入Redis 实时性高 每次变更都建连,效率低 不推荐
定时任务 定期全量同步 实现简单 数据延迟大 不推荐

5.2 伪从数据库方案(go-mysql-transfer)

工作原理:

MySQL主数据库 go-mysql-transfer Redis
| | |
| 1. 执行DML操作 | |
|————————————–> |
| | |
| 2. 写入binlog | |
|————————————–> |
| | |
| 3. IO线程拉取binlog | |
|<————————————– |
| | |
| | 4. 解析binlog |
| | |
| | 5. 写入Redis |
| |—————————->

MySQL主从配置:

# /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
# 主库ID(必须唯一)
server-id = 1

# 绑定地址(0.0.0.0允许外部访问)
bind-address = 0.0.0.0

# 开启binlog
log-bin = mysql-bin
expire-logs-days = 7

# binlog格式(ROW格式便于解析)
binlog-format = ROW

# 不记录binlog的数据库(可选)
binlog-ignore-db = mysql
binlog-ignore-db = information_schema

从库配置(如果需要):

# /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
# 从库ID(必须唯一)
server-id = 1001

# 开启relay log
relay-log = relay-bin

# 只读模式
read-only = ON

go-mysql-transfer配置:

# app.yml

app:
# 监听地址
address: ":8100"

# 数据库配置
database:
# MySQL地址
host: "192.168.1.100"
port: 3306
user: "root"
password: "***"
database: "app_db"

# 从库ID(主从架构中唯一标识)
slave-id: 1001

# 读取binlog的起始位置(不指定则从最新位置开始)
# position: "mysql-bin.000001"
# offset: 4

# Redis配置
redis:
host: "192.168.1.101"
port: 6379
# 如果有密码
# password: "***"
# 数据库编号
# db: 0

# 数据同步规则
rules:
# 规则1:用户表
db: "app_db"
table: "user"
# Redis key格式
# 支持变量:${db}、${table}、${pk}
# 完整key: ***
redis-key: "***"
# 需要同步的列
columns:
id
name
email
phone
created_at
# 主键列(用于生成Redis key)
pk: "id"

# 规则2:商品表
db: "app_db"
table: "product"
redis-key: "***"
columns:
id
name
price
stock
updated_at
pk: "id"

启动服务:

# 启动(不指定position,从最新位置开始)
./go-mysql-transfer -config app.yml

# 启动(指定position,从指定位置开始)
./go-mysql-transfer -config app.yml -position mysql-bin.000001 -offset 4

# 后台运行
nohup ./go-mysql-transfer -config app.yml > transfer.log 2>&1 &

5.3 UDF + 触发器方案(不推荐)

为什么不推荐?

— UDF方案流程

1. 创建UDF函数(用C++编写)
extern "C" {
long redis_set(const char* key, const char* value) {
// 连接Redis,写入数据
redisClient.set(key, value);
return 1;
}
}

2. 编译成动态库
g++ shared fPIC I/usr/include/mysql redis_udf.cpp o redis_udf.so
cp redis_udf.so /usr/lib/mysql/plugin/

3. 在MySQL中注册函数
CREATE FUNCTION redis_set RETURNS INTEGER SONAME 'redis_udf.so';

4. 创建触发器
CREATE TRIGGER after_user_insert
AFTER INSERT ON user
FOR EACH ROW
BEGIN
SELECT redis_set(CONCAT('user:', NEW.id), NEW.name) INTO @r;
END;

问题分析:

问题严重程度说明
每次变更都建立连接 Redis连接建立开销大,频繁建立连接效率低
UDF无法回滚 如果Redis写入失败,UDF无法回滚
不具备事务特性 触发器执行失败会影响主事务
侵入业务代码 业务逻辑与缓存逻辑耦合
维护困难 C++代码修改需要重新编译

六、读写分离

6.1 读写分离架构

写操作
|
v
+————+
| 主数据库 | (Master)
| 接收DML |
+————+
|
| binlog同步(异步)
v
读操作 +————+
+———+ | 从数据库1 | (Slave 1)
| 客户端 |————> 接收SELECT
+———+ +————+
| 读操作 | ^
+———+ | binlog同步
| v
+——> +————+
| 从数据库2 | (Slave 2)
+————+
^
binlog同步 |
v
+————+
| 从数据库3 | (Slave N)
+————+

6.2 主从复制原理

核心:异步复制,最终一致性

主从复制完整流程:

主数据库(Master) 从数据库(Slave)
| |
| 用户执行 UPDATE |
v |
+————-+ |
| 1. 事务开始 | |
+————-+ |
| |
v |
+————-+ |
| 2. 写入 | |
| Buffer Pool | |
+————-+ |
| |
v |
+————-+ |
| 3. 写入 | |
| Change Buffer| |
+————-+ |
| |
v |
+————-+ |
| 4. 写入 | 同时进行 |
| Redo Log |—————————> | (崩溃恢复用)
+————-+ |
| |
v |
+————-+ |
| 5. 写入 | |
| Binlog |—————————->|
+————-+ IO线程拉取 |
| |
| +—————+
| | 6. 写入 |
| | Relay Log |
| +—————+
| |
| +—————+
| | 7. SQL线程 |
| | 重放SQL |
| +—————+
| |
v |
+————-+ |
| 6. 事务提交 | |
| 返回成功 | |
+————-+ |

关键组件详解:

组件位置职责重要性
Binlog 主数据库 记录所有数据变更(statement/row/mixed) 核心
IO线程 从数据库 连接主库,拉取binlog 核心
Relay Log 从数据库 临时存储拉取的binlog 核心
SQL线程 从数据库 读取Relay Log,重放SQL 核心
Relay Log Info 从数据库 记录SQL线程执行位置 中等
Master Info 从数据库 记录IO线程同步位置 中等

6.3 三种binlog格式

格式说明优点缺点适用场景
STATEMENT 记录SQL语句 日志量小 函数、存储过程结果不一致 简单SQL
ROW 记录行变更 数据一致性强 日志量大 敏感数据
MIXED 混合模式 平衡两者 可能退化为STATEMENT 一般场景

— 查看当前binlog格式
SHOW VARIABLES LIKE 'binlog_format';

— 设置(临时)
SET SESSION binlog_format = 'ROW';

— 设置(永久,需重启)
— 在my.cnf中设置
binlogformat = ROW

6.4 读写分离解决的问题

问题传统方案读写分离方案
读压力大 主数据库承压 分散到多个从数据库
并发能力 受限于单机 横向扩展从库数量
可用性 主挂了服务中断 从库可以顶替读取
响应时间 所有请求排队 读操作并行处理

6.5 读写分离的局限

读写分离后的数据一致性问题:

时间线:
T1: 客户端A 执行写 UPDATE SET balance=900 WHERE id=1(主数据库)
T2: 客户端B 执行读 SELECT balance(从数据库,可能还没同步)
|
v
结果:客户端B读到旧数据(balance=1000)

这是因为主从复制是异步的
从数据库不一定能拉到最新数据

一致性等级:

等级说明延迟适用场景
强一致性 写完就能读到 阻塞等待 金融、库存
顺序一致性 按提交顺序读取 较短 一般业务
最终一致性 延迟后最终一致 不确定 读多写少

6.6 读写分离的坑

坑1:主从延迟导致读取旧数据

原因:
– 异步复制,从库可能落后主库
– 大事务写入,从库同步时间长
– 从库IO压力大,处理慢

解决方案:
– 对一致性有要求的读操作,强制走主库
– 监控主从延迟,设置阈值报警
– 大事务拆分,减少延迟

坑2:从库宕机

解决方案:
– 读写分离中间件自动切换
– 熔断机制,摘除故障从库
– 多从库冗余

坑3:主库切换

解决方案:
– 使用VIP或域名,切换时改DNS
– 读写分离中间件支持主从切换
– 提前演练故障切换流程


七、连接池

7.1 为什么需要连接池

连接建立的开销:

建立连接耗时分析:
1. TCP三次握手(约1ms)
2. MySQL认证(约1-2ms)
3. 连接参数设置(约0.5ms)
4. 连接验证(约0.5ms)
———————————
总计:约3-5ms

如果有1000 QPS的查询请求:
– 不使用连接池:每次都建立新连接
1000 * 3ms = 3000ms = 3秒纯连接建立时间!

– 使用连接池:复用已有连接
首次建立3-5ms,后续复用为0ms

7.2 连接池核心参数

# 连接池配置示例(Python)

from DBUtils import PooledDB

pool = PooledDB(
creator=pymysql, # 数据库模块
maxconnections=20, # 最大连接数
mincached=5, # 初始化时创建的连接数
maxcached=10, # 最多缓存的连接数
maxshared=0, # 0表示全部是独占连接
blocking=True, # 连接用完是否阻塞等待
maxusage=None, # 单个连接最多复用次数
setsession=[], # SQL执行前执行的命令
ping=1, # 检查连接是否有效
host='localhost',
port=3306,
user='root',
password='password',
database='app_db',
charset='utf8mb4'
)

# 使用连接
conn = pool.connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
result = cursor.fetchall()
cursor.close()
conn.close() # 归还连接到池中,不是真正关闭

参数说明:

参数说明推荐值
maxconnections 最大连接数 CPU核心数 * 2 + 磁盘数
mincached 初始连接数 5-10
maxcached 最大缓存连接数 20-50
blocking 连接耗尽时是否阻塞 True
maxusage 单连接最大复用次数 1000-5000
ping 检测连接有效性的时机 1(执行前检测)

7.3 连接池的工作原理

连接池内部结构:

+——————+
| 连接池管理器 |
+——————+
|
v
+——————+ +——————+
| 连接队列 | | 连接队列 |
| (空闲连接) | | (使用中连接) |
+——————+ +——————+
| |
v v
[conn1] [conn3]
[conn2] [conn4]
[conn4]

请求到达时:
1. 从空闲队列取一个连接
2. 标记为"使用中"
3. 交给请求使用
4. 使用完毕后,标记为"空闲",放回空闲队列

7.4 MySQL网络模型

MySQL默认网络模型(阻塞IO):

线程池:
+——–+ +——–+ +——–+
|线程1 | |线程2 | |线程3 |
+——–+ +——–+ +——–+
| | |
v v v
+——–+ +——–+ +——–+
|连接1 | |连接2 | |连接3 |
|阻塞等待| |阻塞等待| |阻塞等待|
+——–+ +——–+ +——–+

问题:
– 每个连接需要一个线程
– 线程创建/销毁有开销
– 线程切换有开销
– 大量连接时,线程数爆炸

解决方案:
– 线程池(如MySQL Enterprise的thread pool)
– 异步IO(如MariaDB的async IO)
– IO多路复用(Linux epoll/kqueue)

7.5 异步连接

异步IO模型:

传统阻塞IO:
请求1 –> 等待 –> 返回
请求2 –> 等待 –> 返回
请求3 –> 等待 –> 返回

异步IO:
请求1 ——> 发送(不等待)
请求2 ——> 发送(不等待)
请求3 ——> 发送(不等待)
|
| 事件通知
v
返回1 返回2 返回3

技术实现:
1. libeio(Linux异步IO库)
2. asyncio(Python异步框架)
3. 回调函数/协程


八、缓存方案的问题

8.1 缓存穿透

定义: 查询一个不存在的数据,Redis没有,MySQL也没有,导致请求直接打到数据库。

缓存穿透流程:

请求 –> Redis(没有)–> MySQL(没有)–> 返回空
|
+———–> 压力没有减少
数据库受伤

危害:

  • 大量不存在的数据请求打到数据库
  • 数据库压力剧增
  • 可能导致数据库宕机

解决方案:

方案原理优点缺点
布隆过滤器 判断数据是否存在 内存占用小,判断快 有误判率
缓存空值 将空值也缓存 实现简单 内存浪费
限制查询 限制不合法的查询 防止恶意攻击 可能误杀正常请求

# 布隆过滤器方案
from bloom_filter import BloomFilter

bloom = BloomFilter(capacity=1000000, error_rate=0.01)

def get_data(key):
# 先检查布隆过滤器
if key not in bloom:
return None # 一定不存在

# 布隆过滤器说存在,再查Redis/MySQL
return redis.get(key) or mysql.query(key)

# 缓存空值方案
def get_data(key):
data = redis.get(key)
if data == 'NULL': # 缓存的空值标记
return None

if data:
return data

# 查MySQL
result = mysql.query(key)

if result:
redis.setex(key, 300, result)
else:
# 缓存空值,设置较短过期时间
redis.setex(key, 60, 'NULL')

return result

8.2 缓存击穿

定义: 热点数据过期的瞬间,大量请求同时访问,导致直接打到数据库。

缓存击穿流程:

热点数据A(缓存中)
|
| 时间点:缓存过期
v
大量请求同时涌入
|
v
都发现缓存没有
|
v
同时去查MySQL
|
v
MySQL压力剧增(缓存击穿)

危害:

  • 热点数据过期瞬间,大量并发打到数据库
  • 数据库可能承受不住而宕机

解决方案:

方案原理优点缺点
分布式锁 只有一个请求查MySQL 实现简单 性能略有下降
热点数据永不过期 后台更新,不设过期时间 无击穿问题 数据可能过时
逻辑过期 缓存有过期时间,但业务判断是否更新 灵活 实现复杂

# 分布式锁方案
import redis

def get_data_with_lock(key):
# 尝试获取锁
lock_key = ***"lock:{key}"
lock = redis.set(lock_key, "1", nx=True, ex=5)

if lock:
# 获取到锁,去查MySQL
data = mysql.query(key)
redis.setex(key, 3600, data)
redis.delete(lock_key)
return data
else:
# 没获取到锁,短暂等待后重试
import time
time.sleep(0.1)
return redis.get(key) # 重试从缓存获取

# 热点数据永不过期方案
def get_data(key):
data = redis.get(key)

if not data:
# 缓存没有,查MySQL
data = mysql.query(key)
redis.set(key, data) # 不设置过期时间

return data

# 后台定期更新热点数据
def update_hot_data():
keys = ['user:1', 'user:2', 'product:1'] # 热点key列表
for key in keys:
data = mysql.query(key.replace('user:', 'users WHERE id=').replace('product:', 'products WHERE id='))
redis.set(key, data)

8.3 缓存雪崩

定义: 大量缓存数据同时过期,导致大量请求同时打到数据库。

缓存雪崩流程:

缓存数据A –> 过期时间T –> 同时过期
缓存数据B –> 过期时间T –> 同时过期
缓存数据C –> 过期时间T –> 同时过期
|
v
大量请求同时涌入
|
v
MySQL压力剧增(雪崩)

危害:

  • 大规模缓存失效
  • 数据库压力骤增
  • 可能引发数据库宕机

解决方案:

方案原理优点缺点
过期时间加随机值 不同key过期时间不同 实现简单 不够均匀
多级缓存 本地缓存 + Redis + MySQL 抗压能力强 实现复杂
热点数据永不过期 只对非热点数据过期 根本解决 需要识别热点

# 过期时间加随机值
def set_data(key, value, base_expire=3600):
import random
# 过期时间 = 基础时间 + 随机偏移(0-300秒)
expire = base_expire + random.randint(0, 300)
redis.setex(key, expire, value)

# 多级缓存
LOCAL_CACHE = {} # 本地缓存(进程内)

def get_data(key):
# L1: 本地缓存
if key in LOCAL_CACHE:
return LOCAL_CACHE[key]

# L2: Redis
data = redis.get(key)
if data:
LOCAL_CACHE[key] = data
return data

# L3: MySQL
data = mysql.query(key)
if data:
redis.setex(key, 3600, data)
LOCAL_CACHE[key] = data

return data

8.4 缓存与事务的矛盾

核心问题:Redis不支持回滚,无法保证多语句事务的原子性

问题场景:转账操作

MySQL事务(正确):
BEGIN;
UPDATE account SET balance = balance – 100 WHERE id = 1; — 成功
UPDATE account SET balance = balance + 100 WHERE id = 2; — 余额不足,失败
ROLLBACK; — 整个事务回滚,余额不变

Redis操作(无法回滚):
DECRBY balance:1 100 — 成功
INCRBY balance:2 100 — 如果失败,无法回滚上面的操作
— 导致钱凭空出现!

为什么Redis不支持回滚?

特性MySQLRedis
事务模型 ACID 单命令原子
回滚支持 支持 不支持
多命令事务 MULTI/EXEC WATCH/MULTI/EXEC(有限)
回滚能力 全部回滚 只能取消WATCH

解决方案:

# 方案1:先删缓存,业务逻辑在MySQL事务中完成
def transfer_money(from_id, to_id, amount):
# 删缓存
redis.delete(f"account:{from_id}")
redis.delete(f"account:{to_id}")

# MySQL事务
with mysql.transaction():
mysql.execute("UPDATE account SET balance = balance – %s WHERE id = %s", (amount, from_id))
mysql.execute("UPDATE account SET balance = balance + %s WHERE id = %s", (amount, to_id))

# 缓存由伪从数据库同步

# 方案2:使用分布式事务(不推荐,效率低)
# Seata、分布式事务框架等


九、综合方案总结

9.1 架构全景图

高并发系统性能优化架构:

+————————-+
| 用户请求 |
+————————-+
|
+—————–+—————–+
| |
v v
+—————-+ +—————-+
| 接入层/网关 | | 负载均衡 |
+—————-+ +—————-+
|
+—————–+—————–+
| |
v v
+—————-+ +—————-+
| 业务逻辑层 | | 缓存层 |
| (连接池) | | (Redis) |
+—————-+ +—————-+
|
+—————–+—————–+
| | |
v v v
+—————-+ +—————-+ +—————-+
| 主数据库 | | 从数据库1 | | 从数据库2 |
| (写操作) | | (读操作) | | (读操作) |
+—————-+ +—————-+ +—————-+

9.2 方案对比

方案解决的问题性能提升代价
缓存策略 降低数据库读压力 10-100倍 数据一致性挑战
读写分离 降低主数据库读压力 2-5倍 数据最终一致性
连接池 提高并发性能 3-5倍 连接资源管理

9.3 最佳实践

场景推荐方案说明
读多写少 缓存 + 读写分离 最大性能提升
写多读少 连接池优化 减少连接开销
一致性要求高 不使用缓存 + 强一致 保证数据正确
极端高并发 多级缓存 + 读写分离 层层抗压
数据分析 从数据库查询 不影响主库

十、面试追问FAQ

问题答案
Redis为什么比MySQL快? Redis是纯内存数据库,MySQL是磁盘数据库。内存访问速度是磁盘的10万倍
缓存穿透、击穿、雪崩的区别? 穿透是查不存在的数据;击穿是热点数据过期瞬间大量涌入;雪崩是大量缓存同时过期
如何解决缓存穿透? 布隆过滤器判断数据是否存在,或缓存空值(短过期时间)
如何解决缓存击穿? 分布式锁保证只有一个请求查数据库,或热点数据永不过期
如何解决缓存雪崩? 过期时间加随机值,多级缓存策略,热点数据永不过期
缓存和数据库的一致性怎么保证? 写操作先删Redis再写MySQL,通过伪从数据库同步;或先写MySQL再删Redis
读写分离为什么无法保证强一致性? 主从复制是异步的,从数据库可能有延迟,只能保证最终一致性
读写分离的适用场景? 读多写少,对数据一致性要求不高的业务
连接池的核心参数有哪些? maxconnections、mincached、maxcached、blocking、maxusage
为什么事务必须在同一个连接中执行? MySQL的事务是对连接而言的,不同连接无法保证原子性
伪从数据库方案优于UDF+触发器的原因? 伪从数据库不侵入业务代码,不影响主库性能;UDF每次变更都要建立连接,效率低且无法回滚
binlog有哪几种格式? STATEMENT(记录SQL)、ROW(记录行变更)、MIXED(混合模式)
主从复制延迟的原因? 大事务、网络抖动、从库IO压力大、从库执行SQL慢
如何监控主从延迟? show slave status\\G 查看 Seconds_Behind_Master

根据零声教育教学写作https://github.com/0voice

赞(0)
未经允许不得转载:171主机测评 » MySQL缓存策略与读写分离深度剖析
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址