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) ,欢迎继续提问!

相关推荐
程序帝国2 小时前
SpringBoot整合RediSearch(完整,详细,连接池版本)
java·spring boot·redis·后端·redisearch
FourAu2 小时前
更改mysql在电脑中的存储位置
数据库·mysql
哈里谢顿2 小时前
通过lua实现redis 分布式锁
redis
学编程就要猛3 小时前
MySQL:CRUD
数据库·sql·mysql
IT技术分享社区3 小时前
MySQL实战:自动计算字段如何让查询效率翻倍?
数据库·mysql
optimistic_chen3 小时前
【Redis 系列】常用数据结构---Hash类型
linux·数据结构·redis·分布式·哈希算法
Live&&learn3 小时前
Redis语法入门
数据库·redis
我就是你毛毛哥3 小时前
Linux 定时备份 MySQL 并推送 Gitee
linux·mysql
忧郁蓝调263 小时前
Redis不停机数据迁移:基于 redis-shake 的跨实例 / 跨集群同步方案
运维·数据库·redis·阿里云·缓存·云原生·paas