MySQL 和 Redis搭配使用指南

MySQL 和 Redis 是现代应用架构中经典的 "持久存储 + 高速缓存" 组合。它们各自优势互补:

  • MySQL:强一致性、事务支持、持久化,适合存储核心业务数据(如用户信息、订单)。
  • Redis:内存数据库、微秒级响应、丰富数据结构,适合缓存热点数据、会话、排行榜等。

✅ 一、典型搭配模式

场景 策略
读多写少的数据(如商品详情) 先查 Redis,命中则返回;未命中查 MySQL,再写入 Redis
写操作(如更新用户资料) 先更新 MySQL,再删除/更新 Redis 中的缓存(Cache-Aside Pattern
防止缓存穿透 对空结果也缓存(短 TTL),或使用布隆过滤器
防止缓存雪崩 设置随机过期时间
防止缓存击穿 对热点 key 使用互斥锁(如 Redis 分布式锁)

📌 核心原则以 MySQL 为唯一真实数据源,Redis 仅为缓存


✅ 二、Python 实战示例(使用 pymysql + redis-py

1. 安装依赖

复制代码
pip install pymysql redis

2. 初始化连接

python 复制代码
import redis
import pymysql
import json
import time
from contextlib import contextmanager

# Redis 连接(带连接池)
redis_client = redis.Redis(
    host='localhost',
    port=6379,
    decode_responses=True,  # 自动解码 bytes 为 str
    socket_connect_timeout=2,
    socket_timeout=2
)

# MySQL 连接配置
DB_CONFIG = {
    'host': 'localhost',
    'user': 'root',
    'password': 'your_password',
    'database': 'blog_db',
    'charset': 'utf8mb4',
    'cursorclass': pymysql.cursors.DictCursor
}

@contextmanager
def get_db_connection():
    conn = pymysql.connect(**DB_CONFIG)
    try:
        yield conn
    finally:
        conn.close()

3. 缓存策略实现:Cache-Aside(旁路缓存)

📌 场景:根据用户 ID 获取用户信息

python 复制代码
CACHE_PREFIX = "user:"
CACHE_TTL = 300  # 5分钟

def get_user_by_id(user_id: int):
    """从缓存或数据库获取用户"""
    cache_key = f"{CACHE_PREFIX}{user_id}"
    
    # 1. 先查 Redis
    cached = redis_client.get(cache_key)
    if cached is not None:
        print("✅ Hit cache")
        return json.loads(cached)
    
    # 2. 缓存未命中,查 MySQL
    print("❌ Cache miss, querying MySQL...")
    with get_db_connection() as conn:
        with conn.cursor() as cursor:
            cursor.execute("SELECT id, name, email FROM users WHERE id = %s", (user_id,))
            user = cursor.fetchone()
    
    if user:
        # 3. 写入缓存(注意:序列化为 JSON)
        redis_client.setex(cache_key, CACHE_TTL, json.dumps(user))
        return user
    else:
        # 4. 防止缓存穿透:对空结果也缓存(短时间)
        redis_client.setex(cache_key, 60, "")  # 空值缓存 60 秒
        return None

4. 更新数据时同步缓存

python 复制代码
def update_user(user_id: int, name: str, email: str):
    """更新用户信息,并失效缓存"""
    # 1. 先更新 MySQL
    with get_db_connection() as conn:
        with conn.cursor() as cursor:
            cursor.execute(
                "UPDATE users SET name=%s, email=%s WHERE id=%s",
                (name, email, user_id)
            )
            conn.commit()
    
    # 2. 删除 Redis 缓存(推荐"删除"而非"更新",避免并发不一致)
    cache_key = f"{CACHE_PREFIX}{user_id}"
    redis_client.delete(cache_key)
    print(f"🧹 Cache invalidated for user {user_id}")

💡 为什么删除而不是更新?

  • 更新需查 DB 再写缓存,多一次查询
  • 并发场景下可能写入旧数据(先更新 DB 的请求后写缓存)
  • 删除更简单安全:下次读自动加载最新数据

5. 防止缓存击穿:热点 Key 加锁

当某个 key 过期瞬间,大量请求同时击穿到 DB。

python 复制代码
import uuid

def get_user_safe(user_id: int):
    """带分布式锁防止缓存击穿"""
    cache_key = f"{CACHE_PREFIX}{user_id}"
    
    # 1. 查缓存
    cached = redis_client.get(cache_key)
    if cached is not None:
        return json.loads(cached)
    
    # 2. 尝试获取分布式锁(锁名 = cache_key + ":lock")
    lock_key = cache_key + ":lock"
    lock_value = str(uuid.uuid4())
    lock_ttl = 5000  # 5秒
    
    # 使用 Lua 脚本原子加锁(见前文)
    lock_script = redis_client.register_script("""
    if redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then
        return 1
    else
        return 0
    end
    """)
    
    if lock_script(keys=[lock_key], args=[lock_value, lock_ttl]):
        try:
            # 双重检查:可能其他线程已加载
            cached = redis_client.get(cache_key)
            if cached is not None:
                return json.loads(cached)
            
            # 查询 DB 并写缓存
            with get_db_connection() as conn:
                with conn.cursor() as cursor:
                    cursor.execute("SELECT id, name, email FROM users WHERE id = %s", (user_id,))
                    user = cursor.fetchone()
            
            if user:
                redis_client.setex(cache_key, CACHE_TTL, json.dumps(user))
                return user
            else:
                redis_client.setex(cache_key, 60, "")
                return None
        finally:
            # 释放锁(Lua 脚本确保安全)
            unlock_script = redis_client.register_script("""
            if redis.call("GET", KEYS[1]) == ARGV[1] then
                return redis.call("DEL", KEYS[1])
            else
                return 0
            end
            """)
            unlock_script(keys=[lock_key], args=[lock_value])
    else:
        # 未获得锁,短暂等待后重试(或直接返回错误)
        time.sleep(0.01)
        return get_user_safe(user_id)  # 递归重试(生产环境建议限制重试次数)

✅ 三、完整使用流程示例

ini 复制代码
if __name__ == "__main__":
    user_id = 123
    
    # 首次查询:缓存未命中 → 查 DB → 写缓存
    user = get_user_by_id(user_id)
    print("User:", user)
    
    # 第二次查询:命中缓存
    user = get_user_by_id(user_id)
    print("User (cached):", user)
    
    # 更新用户
    update_user(user_id, "New Name", "new@example.com")
    
    # 再次查询:缓存已失效,重新加载
    user = get_user_by_id(user_id)
    print("Updated user:", user)

✅ 四、高级技巧与注意事项

1. 缓存空值防穿透

bash 复制代码
# 如上文所示,对 DB 返回 None 的情况也缓存空字符串(TTL 较短)

2. 设置随机过期时间防雪崩

ini 复制代码
import random
base_ttl = 300
random_ttl = base_ttl + random.randint(0, 60)  # 300~360 秒
redis_client.setex(key, random_ttl, value)

3. 使用 Pipeline 批量操作

scss 复制代码
with redis_client.pipeline() as pipe:
    pipe.setex("user:1", 300, json.dumps(user1))
    pipe.setex("user:2", 300, json.dumps(user2))
    pipe.execute()

4. 监控缓存命中率

python 复制代码
# 在 get_user 中增加指标
total_requests += 1
if cached:
    cache_hits += 1
print(f"Hit rate: {cache_hits/total_requests:.2%}")

5. 数据一致性保障

  • 最终一致性:接受短暂不一致(适用于大多数场景)
  • 强一致性:写操作时加全局锁(性能差,慎用)
  • 延迟双删(不推荐):先删缓存 → 更新 DB → 延迟再删缓存(仍有窗口期)

✅ 五、总结:最佳实践清单

项目 建议
读流程 先 Redis,后 MySQL
写流程 先 MySQL,后删 Redis
缓存键设计 语义清晰,如 user:{id}product:{sku}
空值处理 缓存空结果(短 TTL)
过期时间 设置随机值防雪崩
热点 Key 用分布式锁防击穿
序列化 用 JSON(兼容性好),避免 pickle(安全风险)
连接管理 Redis 用连接池,MySQL 用上下文管理器

💡 记住
Redis 是加速器,不是数据库。所有关键数据必须以 MySQL 为准。


通过以上范例,你可以安全、高效地在 Python 项目中结合 MySQL 和 Redis。如果需要 Django / Flask 集成示例使用连接池/异步版本(aioredis) ,欢迎继续提问!

相关推荐
MiNG MENS12 分钟前
nginx 代理 redis
运维·redis·nginx
DevilSeagull1 小时前
MySQL(2) 客户端工具和建库
开发语言·数据库·后端·mysql·服务
远洪2 小时前
claude code 国内安装使用
数据库·mysql
千月落3 小时前
Redis数据迁移
数据库·redis·缓存
wangbing11254 小时前
MySQL 官方 GPG 密钥过期问题
数据库·mysql
重生之我是Java开发战士4 小时前
【MySQL】事务 & 用户与权限管理
android·数据库·mysql
2401_895521345 小时前
MySQL中的count函数
数据库·mysql
.小小陈.6 小时前
MySQL 入门到实战:从基础概念到核心存储引擎
数据库·mysql
yoyo_zzm6 小时前
Laravel10.x新特性全解析
数据库·mysql·架构
洛水水6 小时前
【Redis入门】一篇详解Redis五大数据结构
数据结构·数据库·redis