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开发 #缓存

相关推荐
CNSSIRD数据库2 小时前
中国创新型中小企业研究数据库2022-2026
大数据·数据库·数据分析·论文笔记
2601_963282772 小时前
极寒区域无线通信系统工程实战:黑龙江冰雪环境对讲机组网、射频损耗与设备耐寒可靠性优化全指南
大数据·数据库·人工智能
小王同学66662 小时前
Django 5 中实现文件上传功能
数据库·django·sqlite
zuozewei3 小时前
《GB/T 47241-2026 - 虚拟电厂技术导则》国标解析.md
数据库
小张同学a.4 小时前
LAMP架构4——MySQL高可用
linux·运维·服务器·数据库·mysql·架构·负载均衡
Xxtaoaooo14 小时前
工业 IoT 存储引擎选型:多模引擎在数据模型与存储设计上的取舍
数据库·物联网·dolphindb
凌虚16 小时前
面向 MySQL 用户的 PostgreSQL 快速上手指南
数据库·后端·架构
五阿哥永琪16 小时前
MySQL中操作json的函数!
数据库·mysql·json
来者皆善16 小时前
了解Mysql优化吗?如何优化索引?
数据库·mysql