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