当前位置: 代码网 > it编程>数据库>Mysql > MySQL缓存策略与读写分离的深入剖析

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

2026年09月29日 • Mysql •我要评论
一、前言在互联网系统中,数据库往往是性能瓶颈所在。随着用户量增长,数据库的读写压力急剧增加,如何提升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中设置
binlog-format = 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/execwatch/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

到此这篇关于mysql缓存策略与读写分离的文章就介绍到这了,更多相关mysql缓存策略与读写分离内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

赞 (0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com