Python 操作 MySQL 与 Redis:AI 应用的数据读写双引擎

Python 操作 MySQL 与 Redis:AI 应用的数据读写双引擎

上一篇把 SQL 基本功练扎实了,这篇让 SQL 从"手敲命令行"升级成"代码自动跑"------pymysql 连 MySQL 做增删改查、防 SQL 注入、事务回滚,再接上 Redis 做缓存加速,双库联动才是 AI 应用的真实数据架构。


前言

上一篇我们用 SQL 在命令行里手动建库建表、查数据、做 JOIN。但真实项目中,你不可能让用户去敲 SQL------所有数据库操作都得封装在代码里。比如 RAG 系统上传文档后自动写入记录、聊天机器人实时存取对话历史、模型推理结果落库统计......这些都需要 Python 程序直连数据库。

这篇就解决两个问题:Python 怎么操作 MySQL? 以及 什么时候需要引入 Redis? 前者是"数据持久化"的标配,后者是"数据加速"的利器,两者搭配才是 AI 后端的完整数据方案。


一、Python 操作 MySQL:pymysql 从入门到防注入

1 为什么要用代码操作数据库?

试想一个场景:你需要把 10 万条文档元数据插入 MySQL。手动一条条敲 SQL?不现实。用 Python 写个循环,几秒搞定。数据库编程的本质就是用代码替代人工,实现批量、自动化、可复用的数据操作。

2 准备工作:建表与安装

先准备一张 AI 场景的表:

sql 复制代码
CREATE DATABASE IF NOT EXISTS ai_platform CHARSET utf8mb4;
USE ai_platform;

-- 模型服务注册表
CREATE TABLE model_services (
    id          INT PRIMARY KEY AUTO_INCREMENT,
    model_name  VARCHAR(80)  NOT NULL COMMENT '模型名称',
    provider    VARCHAR(40)  NOT NULL COMMENT '供应商',
    endpoint    VARCHAR(255) NOT NULL COMMENT 'API 地址',
    max_tokens  INT          NOT NULL DEFAULT 4096,
    is_active   TINYINT      NOT NULL DEFAULT 1 COMMENT '是否启用'
);

-- 用户表(后续登录案例用)
CREATE TABLE platform_users (
    id       INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    pwd_hash VARCHAR(128) NOT NULL
);
INSERT INTO platform_users (username, pwd_hash) VALUES ('admin', '5f4dcc3b5aa765d61d8327deb882cf99');

安装 pymysql:

bash 复制代码
pip install pymysql
# 或用清华镜像加速
pip install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple/

3 pymysql 核心六步法

pymysql 的使用套路非常固定,记住这六步:

markdown 复制代码
1. 导入模块 → 2. 创建连接 → 3. 创建游标 → 4. 执行 SQL → 5. 关闭游标 → 6. 关闭连接
查询操作:fetchone 与 fetchall
python 复制代码
import pymysql

# 2. 创建连接
conn = pymysql.connect(
    host='localhost',
    port=3306,
    user='root',
    password='root',
    database='ai_platform',
    charset='utf8mb4'
)

# 3. 创建游标
cur = conn.cursor()

# 4. 执行 SQL
row_count = cur.execute('SELECT * FROM model_services')
print(f'查询到 {row_count} 条记录')

# fetchone:每次取一条,适合大结果集逐行处理
first_row = cur.fetchone()
print(f'第一条: {first_row}')

# fetchall:一次取完剩余所有行,适合小结果集
remaining = cur.fetchall()
print(f'剩余: {remaining}')

# 5. 关闭游标
cur.close()
# 6. 关闭连接
conn.close()

fetchone vs fetchall 怎么选?

方法 行为 适用场景
fetchone() 每次取一条,游标后移 大结果集、流式处理
fetchall() 一次取全部剩余行 小结果集、需要整体遍历
fetchmany(n) 取指定条数 分批处理
增删改操作:别忘了 commit

InnoDB 引擎支持事务,增删改之后必须手动 commit,否则修改只存在于当前事务,连接关闭后自动回滚。

python 复制代码
import pymysql

conn = pymysql.connect(
    host='127.0.0.1', port=3306,
    user='root', password='root',
    database='ai_platform', charset='utf8mb4'
)
cur = conn.cursor()

# --- 插入 ---
rows = cur.execute(
    "INSERT INTO model_services (model_name, provider, endpoint, max_tokens) "
    "VALUES ('gpt-4o', 'openai', 'https://api.openai.com/v1', 128000)"
)
print(f'插入 {rows} 行')
conn.commit()  # InnoDB 必须提交!

# --- 修改 ---
rows = cur.execute(
    "UPDATE model_services SET max_tokens = 8192 WHERE model_name = 'gpt-4o'"
)
conn.commit()

# --- 删除(带异常回滚) ---
try:
    rows = cur.execute("DELETE FROM model_services WHERE model_name = 'gpt-4o'")
    # 模拟业务异常:如果这里抛错,上面的 DELETE 会被回滚
    # result = 1 / 0
    conn.commit()
except Exception as e:
    print(f'操作异常,执行回滚: {e}')
    conn.rollback()  # 回滚到事务开始前的状态

cur.close()
conn.close()

事务四特性(ACID)速记:

特性 含义 一句话理解
原子性 事务内操作要么全成功,要么全失败 转账:扣钱和加钱必须同时成功
一致性 事务前后数据满足约束规则 余额不能变负数
隔离性 并发事务互不干扰 你转账时别人看不到中间状态
持久性 提交后数据永久保存 断电也不丢

4 SQL 注入:最容易被忽视的安全漏洞

什么是 SQL 注入?

用户输入被直接拼进 SQL 语句,恶意输入改变了 SQL 的逻辑。看这段有漏洞的代码:

python 复制代码
username = input('用户名: ')   # 输入: 任意内容
password = input('密码: ')     # 输入: ' or 1=1 or '

# 直接拼接 → 漏洞代码!
sql = f"SELECT * FROM platform_users WHERE username='{username}' AND pwd_hash='{password}'"
# 实际执行的 SQL:
# SELECT * FROM platform_users WHERE username='xxx' AND pwd_hash='' or 1=1 or ''
# 1=1 恒为真,绕过了密码校验!

row = cur.execute(sql)
if row:
    print('登录成功')  # 被注入了!

输入 ' or 1=1 or ' 后,WHERE 条件恒为真,任何人都能登录------这就是 SQL 注入。

参数化查询:一行修复
python 复制代码
username = input('用户名: ')
password = input('密码: ')

# SQL 用 %s 占位,参数放列表传入
sql = "SELECT * FROM platform_users WHERE username=%s AND pwd_hash=%s"
params = [username, password]

row = cur.execute(sql, params)  # pymysql 自动转义,注入无效
if row:
    print('登录成功')
else:
    print('登录失败')

核心原则:永远不要用 f-string / format 拼接 SQL,始终用参数化查询。 这一条规则能挡住 99% 的注入攻击。


二、非关系型数据库 Redis:为什么 AI 应用离不开它?

1 Redis 是什么?

Redis(Remote Dictionary Server)是一个基于内存的键值对存储,可以用作数据库、缓存和消息中间件。和 MySQL 的"写磁盘、查表"不同,Redis 把数据放内存里,读写速度是 MySQL 的几十到上百倍。

2 Redis 的核心特点

特点 说明 AI 场景对应
基于内存 读写延迟微秒级 对话历史实时存取
持久化 RDB 快照 + AOF 日志,重启不丢数据 缓存数据可恢复
丰富数据类型 String / Hash / List / Set / Sorted Set 不同业务选不同结构
原子操作 单条命令原子性,事务支持 计数器不会并发错乱
键过期 支持 TTL 自动淘汰 会话/验证码自动过期

3 Redis 能干什么?

  • 缓存:大模型推理结果、热门文档向量,先查 Redis 再查 MySQL,响应快 10 倍。
  • 计数器 :API 调用次数、文档阅读量,INCR 一条命令搞定。
  • 消息队列:用 List 实现简单的任务队列(文档解析任务排队)。
  • 排行榜:Sorted Set 做模型评测得分排名。
  • 会话存储:用户登录态、对话上下文,设 TTL 自动过期。

4 Redis 安装与验证

Windows(推荐小皮工具一键安装):

也可以手动下载 Redis Windows 版本,解压后运行:

bash 复制代码
redis-server.exe redis.windows.conf

macOS(Homebrew):

bash 复制代码
brew install redis
brew services start redis
redis-cli ping   # 返回 PONG 表示成功

Linux(Ubuntu):

bash 复制代码
sudo apt update && sudo apt install redis-server
sudo systemctl start redis-server
redis-cli ping   # 返回 PONG

验证安装:

bash 复制代码
redis-cli
127.0.0.1:6379> SET test "hello redis"
OK
127.0.0.1:6379> GET test
"hello redis"

三、Python 操作 Redis:五种数据类型实战

1 安装与连接

bash 复制代码
pip install redis
python 复制代码
import redis

r = redis.Redis(
    host='localhost',
    port=6379,
    db=0,
    decode_responses=True   # 返回字符串而非字节,省去手动 decode
)

# 测试连接
assert r.ping() is True
print('Redis 连接成功')

2 String:最基础的键值对

python 复制代码
# 设置 / 获取
r.set('model:gpt4o:status', 'active')
print(r.get('model:gpt4o:status'))  # 'active'

# 批量设置 / 获取
r.mset({'model:gpt4o:provider': 'openai', 'model:gpt4o:version': '2024-08'})
print(r.mget(['model:gpt4o:status', 'model:gpt4o:provider']))

# 设置带过期时间的键(验证码场景)
r.setex('sms:code:13800138000', 300, '582934')  # 5 分钟后自动删除
print(r.ttl('sms:code:13800138000'))  # 剩余秒数

# 计数器:API 调用次数
r.set('api:call:today', '0')
r.incr('api:call:today')         # +1
r.incrby('api:call:today', 100)  # +100
r.decr('api:call:today')         # -1
print(r.get('api:call:today'))

3 Hash:对象存储的最佳选择

python 复制代码
# 存储模型配置信息
r.hset('config:model:gpt4o', mapping={
    'provider': 'openai',
    'max_tokens': '128000',
    'temperature': '0.7',
    'status': 'active'
})

# 获取单个字段
print(r.hget('config:model:gpt4o', 'temperature'))  # '0.7'

# 获取全部字段
print(r.hgetall('config:model:gpt4o'))

# 获取多个指定字段
print(r.hmget('config:model:gpt4o', ['provider', 'status']))

# 删除某个字段
r.hdel('config:model:gpt4o', 'temperature')

4 List:消息队列与历史记录

python 复制代码
# 模拟文档解析任务队列:左进右出(FIFO)
r.lpush('queue:doc_parse', 'doc_001', 'doc_002', 'doc_003')
r.rpush('queue:doc_parse', 'doc_004')

# 查看队列长度
print(f'待处理: {r.llen("queue:doc_parse")} 条')

# 查看全部任务(不弹出)
print(r.lrange('queue:doc_parse', 0, -1))

# 消费任务:从右侧弹出
task = r.rpop('queue:doc_parse')
print(f'正在处理: {task}')

5 Set:去重与集合运算

python 复制代码
# 存储文档标签
r.sadd('tags:doc_001', 'NLP', 'Transformer', 'Attention')
r.sadd('tags:doc_002', 'NLP', 'RAG', 'Embedding')

# 判断标签是否存在
print(r.sismember('tags:doc_001', 'NLP'))  # True

# 交集:两篇文档的共同标签
print(r.sinter('tags:doc_001', 'tags:doc_002'))  # {'NLP'}

# 并集:所有涉及标签
print(r.sunion('tags:doc_001', 'tags:doc_002'))

# 差集:doc_001 有但 doc_002 没有的标签
print(r.sdiff('tags:doc_001', 'tags:doc_002'))

6 Sorted Set:排行榜利器

python 复制代码
# 模型评测得分排行
r.zadd('rank:model_eval', {
    'gpt-4o': 92.5,
    'claude-3.5': 91.8,
    'gemini-pro': 88.3,
    'gpt-4o-mini': 86.0
})

# 降序排名(分数从高到低)
top3 = r.zrevrange('rank:model_eval', 0, 2, withscores=True)
print(f'Top3: {top3}')

# 查某个模型的分数和排名
print(r.zscore('rank:model_eval', 'gpt-4o'))         # 92.5
print(r.zrevrank('rank:model_eval', 'gpt-4o') + 1)   # 排名第 1(从 0 开始,+1 转人类习惯)

# 按分数范围筛选
high_scores = r.zrangebyscore('rank:model_eval', 90, 100, withscores=True)
print(f'90 分以上: {high_scores}')

# 加分
r.zincrby('rank:model_eval', 2.0, 'gemini-pro')  # gemini-pro +2 分

7 键操作与过期管理

python 复制代码
# 检查键是否存在
print(r.exists('model:gpt4o:status'))  # 1 表示存在

# 设置过期时间
r.expire('model:gpt4o:status', 3600)  # 1 小时后自动删除
print(r.ttl('model:gpt4o:status'))     # 剩余秒数,-1 表示永不过期,-2 表示已不存在

# 移除过期时间,变为永久键
r.persist('model:gpt4o:status')

# 按模式查找键
print(r.keys('model:*'))       # 所有 model: 开头的键
print(r.keys('config:model:*')) # 所有模型配置键

# 删除键
r.delete('model:gpt4o:status')

四、实战:Redis 缓存系统封装

把 Redis 缓存封装成工具类,在 AI 应用中直接复用:

python 复制代码
import redis
import json
import time


class AICache:
    """AI 应用缓存工具类"""

    def __init__(self, host='localhost', port=6379, db=0):
        self.client = redis.Redis(
            host=host, port=port, db=db, decode_responses=True
        )

    def set(self, key: str, value, expire: int = 3600) -> bool:
        """写入缓存,自动序列化,默认 1 小时过期"""
        try:
            self.client.setex(key, expire, json.dumps(value, ensure_ascii=False))
            return True
        except Exception as e:
            print(f'缓存写入失败: {e}')
            return False

    def get(self, key: str):
        """读取缓存,命中返回反序列化结果,未命中返回 None"""
        raw = self.client.get(key)
        if raw:
            return json.loads(raw)
        return None

    def delete(self, key: str) -> bool:
        """删除缓存"""
        return self.client.delete(key) > 0

    def cached_call(self, key: str, fn, expire: int = 300, *args, **kwargs):
        """通用缓存调用:先查缓存,未命中则执行函数并缓存结果"""
        result = self.get(key)
        if result is not None:
            print(f'[缓存命中] {key}')
            return result

        print(f'[缓存未命中] {key},执行计算...')
        result = fn(*args, **kwargs)
        self.set(key, result, expire)
        return result


# 使用示例
if __name__ == '__main__':
    cache = AICache()

    # 模拟耗时操作:大模型推理
    def slow_inference(prompt: str) -> dict:
        time.sleep(2)  # 模拟推理耗时
        return {'prompt': prompt, 'answer': '这是大模型的回答', 'tokens': 150}

    # 第一次调用:缓存未命中,执行推理
    start = time.time()
    result1 = cache.cached_call(
        'inference:hello', slow_inference, 300, '你好'
    )
    print(f'结果: {result1}, 耗时: {time.time() - start:.2f}s')

    # 第二次调用:缓存命中,秒级返回
    start = time.time()
    result2 = cache.cached_call(
        'inference:hello', slow_inference, 300, '你好'
    )
    print(f'结果: {result2}, 耗时: {time.time() - start:.2f}s')

常见问题

Q1:pymysql 的 execute() 返回值是什么?

返回受影响的行数(整数)。SELECT 查询返回的是"结果集行数",INSERT/UPDATE/DELETE 返回的是"实际变更行数"。

Q2:InnoDB 和 MyISAM 哪个支持事务?

只有 InnoDB 支持事务(commit / rollback)。MyISAM 不支持,执行增删改后自动生效,无法回滚。MySQL 8.0 默认引擎就是 InnoDB。

Q3:Redis 数据全在内存,断电不丢吗?

Redis 提供两种持久化机制:RDB(定时快照)和 AOF(追加日志)。合理配置后,断电最多丢失 1 秒数据。生产环境建议同时开启。

Q4:Redis 的 db0~db15 是什么?

Redis 默认有 16 个逻辑数据库(编号 0~15),用 SELECT N 切换。不同 db 之间键名隔离,适合开发/测试环境区分用途,生产环境建议用不同 Redis 实例隔离。

Q5:什么时候用 MySQL,什么时候用 Redis?

MySQL 存需要持久化、有复杂关联的结构化数据(用户、订单、文档记录)。Redis 存需要极速读写、可容忍短暂丢失的热数据(缓存、会话、计数器)。两者互补,不是替代关系。


和 AI 大模型开发的关系

1. RAG 文档处理流水线:MySQL + Redis 协作

python 复制代码
import pymysql
import redis
import json

r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
conn = pymysql.connect(host='localhost', user='root', password='root',
                       database='ai_platform', charset='utf8mb4')
cur = conn.cursor()

def get_document(doc_id: int) -> dict:
    """先查 Redis 缓存,未命中再查 MySQL 并回填缓存"""
    cache_key = f'doc:{doc_id}'
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)

    cur.execute('SELECT id, title, status FROM documents WHERE id = %s', (doc_id,))
    row = cur.fetchone()
    if row:
        doc = {'id': row[0], 'title': row[1], 'status': row[2]}
        r.setex(cache_key, 600, json.dumps(doc, ensure_ascii=False))  # 缓存 10 分钟
        return doc
    return None

2. 对话历史:Redis List 存近期消息,MySQL 存完整归档

python 复制代码
def save_message(session_id: str, role: str, content: str):
    """近期消息存 Redis(快速读取),同时异步落库 MySQL"""
    msg = json.dumps({'role': role, 'content': content}, ensure_ascii=False)
    r.lpush(f'chat:{session_id}', msg)
    r.ltrim(f'chat:{session_id}', 0, 49)  # 只保留最近 50 条
    # MySQL 持久化(可异步执行)
    cur.execute(
        'INSERT INTO chat_messages (session_id, role, content) VALUES (%s, %s, %s)',
        (session_id, role, content)
    )
    conn.commit()

def get_recent_messages(session_id: str, limit: int = 20):
    """从 Redis 读取最近 N 条消息,用于构造 LLM 上下文"""
    raw_list = r.lrange(f'chat:{session_id}', 0, limit - 1)
    return [json.loads(m) for m in reversed(raw_list)]

3. API 限流:Redis 计数器实现滑动窗口

python 复制代码
def check_rate_limit(api_key: str, max_calls: int = 60, window: int = 60) -> bool:
    """检查 API 调用是否超限(每分钟 max_calls 次)"""
    key = f'rate:{api_key}'
    current = r.incr(key)
    if current == 1:
        r.expire(key, window)  # 首次调用设置窗口过期
    return current <= max_calls

4. 模型评测排行榜:Sorted Set 实时更新

python 复制代码
def update_eval_rank(model_name: str, score: float):
    """更新模型评测分数排行"""
    r.zadd('rank:model_eval', {model_name: score})

def get_top_models(n: int = 10):
    """获取 Top N 模型"""
    return r.zrevrange('rank:model_eval', 0, n - 1, withscores=True)

小结

这篇把"数据库操作"从 SQL 命令行升级到了 Python 代码层面:pymysql 六步法完成 MySQL 的增删改查,参数化查询防住 SQL 注入,事务回滚保证数据安全;Redis 五种数据类型各有适用场景,缓存封装类让 AI 应用读写加速。MySQL 负责持久化,Redis 负责加速,双引擎配合才是生产级方案。

#Python #MySQL #Redis #pymysql #SQL注入 #AI开发 #缓存

相关推荐
liangsheng_g4 分钟前
Spring事务传播行为分派与挂起恢复源码实战
数据库·sql·spring
蓝速科技17 分钟前
信创终端 POC 测试实战与选型避坑指南丨蓝速科技
运维·数据库·人工智能·科技·自然语言处理
风哥2号32 分钟前
数据库教程FGMT29‑Linux平台MySQL5.7安装配置与管理入门
数据库
杨云龙UP1 小时前
MySQL Host is blocked because of many connection errors 导致 JDBC 连接失败排查与解决
linux·运维·网络·数据库·sql·mysql·登录失败
这个DBA有点耶3 小时前
数据库数据同步解决方案怎么选?6款主流工具横向对比与信创选型指南
数据库·架构·dba
刃神太酷啦3 小时前
Redis 核心进阶:哨兵、集群、缓存问题与分布式锁详解----《Hello Redis!》(6)
linux·c语言·数据库·c++·redis·分布式·缓存
小雷信息医学3 小时前
不会写复杂代码也能发 SCI?手把手教你用 InSpireR 交互系统一键提取、合并与导出临床科研宽表【第三章】
数据库
旺仔不是程序员3 小时前
复合索引最左前缀原则:PostgreSQL 的 WHERE 为什么必须命中第一列
数据库·后端·sql
旺仔不是程序员3 小时前
字段类型不一致:PostgreSQL 报错与索引失效的第一元凶
数据库·后端·sql
白远山3 小时前
货运跑腿搬家平台开发实战:从需求分析到落地部署指南
数据库·数据挖掘·需求分析