今天的目标很实在:搞懂 Python 怎么连接 MySQL 和 Redis,弄清楚查询和修改的区别,最重要的是——彻底理解 Redis 的字符串和哈希到底有什么不同


一、MySQL 和 Redis 分别用来干什么?

很多新手会问:有了 MySQL,为什么还要学 Redis?

  • MySQL 像你家的 大书柜:容量大,能存很多书(核心数据),但找书、放书稍微慢一点(数据在硬盘上)。

  • Redis 像你 手边的小推车:容量小,但随手拿随手放,速度飞快(数据在内存里)。

实际项目中,两者配合使用:

  • MySQL 存放不能丢的数据:用户、订单、财务流水。

  • Redis 存放高频访问的“热点副本”:商品详情缓存、验证码、登录会话、点赞数、排行榜。

一句话:MySQL 存“根本”,Redis 当“加速器”。


二、PyMySQL:Python 连 MySQL 的“翻译官”

1. 三者关系

  • MySQL:真正的数据库服务(存数据)。

  • Python:写业务逻辑的程序。

  • PyMySQL:Python 的一个第三方库,负责把 Python 的指令翻译成 MySQL 能听懂的话,再把结果翻译回来。

类比:Python 是只会中文的老板,MySQL 是只会英文的仓库管理员,PyMySQL 就是同声传译。

2. 核心流程(4 步)

import pymysql

# 1. 建立连接
conn = pymysql.connect(host='127.0.0.1', user='root', password='你的密码', database='test')

# 2. 创建游标
cursor = conn.cursor()

# 3. 执行 SQL
cursor.execute("SELECT * FROM users")

# 4. 获取结果并关闭
rows = cursor.fetchall()
cursor.close()
conn.close()

3. 查询 vs 增删改 —— 关键区别

操作类型 是否需要 commit 如何取结果
查询 (SELECT) 不需要 fetchone() / fetchall()
增删改 (INSERT/UPDATE/DELETE) 必须 commit() 用 cursor.rowcount 看影响行数

示例:修改数据 + 事务回滚

try:
    cursor.execute("UPDATE users SET balance = balance - 100 WHERE name = '小明'")
    cursor.execute("UPDATE users SET balance = balance + 100 WHERE name = '小红'")
    conn.commit()          # 两条都成功才提交
except:
    conn.rollback()        # 任何一条出错,撤销所有修改

记住口诀:查询只 fetch,修改要 commit,出错 rollback


三、Redis 安装与连接

Redis 包含两个东西:

  • Redis 服务:真正的数据库程序(需要单独安装启动)。

  • redis-py:Python 的客户端库(pip install redis)。

测试连接是否成功:

import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
print(r.ping())   # 输出 True 表示连通

加上 decode_responses=True 可以让 Redis 自动返回字符串,而不是字节。


四、Redis 的五大金刚(数据类型)

类型 一句话理解 典型场景
String 一个 key 对应一个值 缓存、计数器、验证码
Hash 一个 key 对应多个字段(像一个小字典) 用户信息、购物车
List 有序、可重复的列表 消息队列、最新列表
Set 无序、不重复的集合 抽奖去重、共同好友
Sorted Set 带分数的有序集合 排行榜

五、重中之重:String(存 JSON) vs Hash(存对象)

这是今天最核心、最易混淆的知识点,必须彻底搞懂。

场景:存储一个用户(姓名:张三,年龄:20,城市:北京)

1. 用 String 存 JSON

SET user:1 '{"name":"张三","age":20,"city":"北京"}'

想改年龄为 21 怎么办?
你只能:取出整个字符串 → 解析 JSON → 改年龄 → 转成字符串 → 写回。
即使用代码,也要三步:

data = json.loads(r.get("user:1"))
data["age"] = 21
r.set("user:1", json.dumps(data))

问题

  • 改一个字段,却要读写整个对象,性能差、网络浪费。

  • 如果两个人同时改(比如一个改年龄、一个改城市),后执行的会覆盖先执行的,数据丢失。

2. 用 Hash 存储

HSET user:1 name "张三" age 20 city "北京"

改年龄只需一行

HSET user:1 age 21

甚至可以直接加一岁:

HINCRBY user:1 age 1

优势

  • 只操作目标字段,其他字段不动。

  • 原子操作,无并发覆盖问题。

  • 代码简洁,性能高。

3. 为什么 String 不能只改一部分?

因为 Redis 把 String 的值看作一个 整体黑盒,不知道里面是 JSON 还是别的格式,也没有“修改内部某个字段”的命令。
而 Hash 在设计时就把每个 field 当作独立单元,天然支持部分修改。

4. 什么时候用哪个?

使用场景 推荐类型
经常单独修改某个属性(积分、点赞数、状态) Hash
总是整体读写(缓存 HTML 片段、图片 base64) String
需要每个对象独立过期(Hash 只能整个 key 过期) String
小对象且字段少(内存效率高) Hash

一句话:Hash 就是为“频繁改对象的某几个字段”而生的,别再拿 String 存 JSON 去改字段了,那是弯路。


六、其他 Redis 类型简单示例

1. String – 计数器(文章阅读数)

r.set("article:1001:views", 0)
r.incr("article:1001:views")      # 阅读 +1
r.incrby("article:1001:views", 5) # 一次加 5

2. Hash – 购物车

r.hset("cart:1001", "apple", 3)
r.hset("cart:1001", "banana", 2)
r.hincrby("cart:1001", "apple", 1)  # 苹果加一个

3. List – 消息队列(先进先出)

r.lpush("tasks", "task1", "task2")   # 左边加
task = r.rpop("tasks")               # 右边取

4. Set – 抽奖去重

r.sadd("lottery", "张三", "李四", "张三")  # 张三只加一次
r.smembers("lottery")  # 查看所有参与者

5. Sorted Set – 排行榜

r.zadd("rank", {"小明": 100, "小红": 95})
r.zincrby("rank", 10, "小红")                # 小红加 10 分
top = r.zrevrange("rank", 0, 2, withscores=True)  # 前三名

七、重要提醒:过期时间(TTL)

缓存、验证码、会话都必须设置过期时间,否则 Redis 会被垃圾数据撑爆。

r.setex("code:13800138000", 60, "123456")   # 60 秒后自动删除
r.ttl("code:13800138000")   # 查看剩余生存时间

注意:只有整个 key 可以设置过期时间。Hash 内部不能单独让某个 field 过期。


八、今日小结

  • PyMySQL:查询用 fetch,修改用 commit,出错 rollback

  • MySQL 存核心数据,Redis 加速热点数据。

  • String 存 JSON:改一个字段 = 重写整个对象(麻烦且不安全)。

  • Hash 存对象:改一个字段 = 只动那个字段(干净利落)。

  • 缓存一定要设过期时间,否则内存会爆炸。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐