Python 读写 Redis 缓存数据库:写给 Python 初学者的入门案例
很多学习 Python 的小伙伴,在学完文件读写、SQLite、MySQL 之后,都会接触到一个新的数据库:Redis。
Redis 和传统关系型数据库不太一样。MySQL、SQLite 更擅长长期保存结构化数据,而 Redis 更常用于缓存、计数器、排行榜、验证码、分布式锁、消息队列等高性能场景。
这篇文章会先简单介绍 Redis 是什么,然后通过 Python 代码演示如何连接 Redis,并完成常见的读写操作。
一、Redis 是什么
Redis 全称是 Remote Dictionary Server,可以理解为一个高性能的内存型 Key-Value 数据库。
它的核心特点是:
- 数据主要存放在内存中,读写速度非常快
- 使用 Key-Value 形式保存数据
- 支持丰富的数据结构,不只是简单字符串
- 支持设置过期时间,适合做缓存
- 支持持久化,可以把内存数据保存到磁盘
- 常用于缓存、计数、排行榜、会话、限流等场景
我们可以把 Redis 想象成一个超快的字典:
cache={"user:1:name":"Alice","article:100:view_count":358,"login:code:13800138000":"829316"}只不过这个字典不是保存在 Python 程序里,而是保存在 Redis 服务中。多个程序、多个接口、多个服务器都可以一起访问它。
二、Redis 和 MySQL、SQLite 有什么区别
简单对比一下:
| 对比项 | Redis | MySQL / SQLite |
|---|---|---|
| 存储方式 | 主要在内存中 | 主要在磁盘中 |
| 数据模型 | Key-Value 和多种数据结构 | 表、行、列 |
| 读写速度 | 非常快 | 相对较慢 |
| 常见用途 | 缓存、计数、排行榜、验证码 | 业务数据持久化 |
| 查询方式 | 通过 key 访问 | SQL 查询 |
| 是否适合复杂查询 | 不适合 | 适合 |
所以 Redis 通常不是用来完全替代 MySQL 或 SQLite 的,而是和它们配合使用。
例如:
- 用户资料保存在 MySQL 中
- 热门用户资料缓存到 Redis 中
- 下次查询时先查 Redis
- Redis 没有命中,再查 MySQL
- 查到后再写回 Redis
这个模式通常叫缓存旁路,也叫 Cache Aside。
三、准备 Redis 环境
如果你本机已经安装 Redis,可以直接启动 Redis 服务。
如果没有安装,也可以使用 Docker 快速启动:
dockerrun--nameredis-demo-p6379:6379-dredis测试 Redis 是否可用:
redis-cliping如果返回:
PONG说明 Redis 服务已经启动成功。
四、安装 Python Redis 客户端
Python 操作 Redis 常用的库是redis。
安装命令:
pipinstallredis安装完成后,可以在 Python 中导入:
importredis五、连接 Redis
新建一个文件redis_demo.py:
importredis r=redis.Redis(host="localhost",port=6379,db=0,decode_responses=True)print(r.ping())运行:
python redis_demo.py如果输出:
True说明 Python 已经成功连接到 Redis。
这里的几个参数含义如下:
| 参数 | 说明 |
|---|---|
| host | Redis 服务器地址 |
| port | Redis 端口,默认 6379 |
| db | Redis 数据库编号,默认 0 |
| decode_responses | 是否把返回值自动解码成字符串 |
如果不设置decode_responses=True,Redis 返回的字符串可能是字节类型:
b'Alice'对初学者来说,建议先加上decode_responses=True,这样输出更直观。
六、字符串 String:最基础的缓存读写
Redis 最简单的数据类型是 String。
可以用它保存用户名、验证码、Token、简单配置、缓存结果等。
importredis r=redis.Redis(host="localhost",port=6379,db=0,decode_responses=True)# 写入字符串r.set("user:1:name","Alice")# 读取字符串name=r.get("user:1:name")print(name)输出:
Alice设置过期时间
缓存通常不应该永久存在,所以 Redis 支持给 key 设置过期时间。
例如保存短信验证码,60 秒后自动删除:
r.set("login:code:13800138000","829316",ex=60)code=r.get("login:code:13800138000")print(code)其中ex=60表示 60 秒后过期。
也可以先写入,再单独设置过期时间:
r.set("temp:data","hello")r.expire("temp:data",30)查看剩余过期时间:
ttl=r.ttl("temp:data")print(ttl)七、数字自增:计数器案例
Redis 很适合做计数器,例如文章阅读量、点赞数、接口访问次数。
article_key="article:100:view_count"r.set(article_key,0)r.incr(article_key)r.incr(article_key)r.incr(article_key)count=r.get(article_key)print(count)输出:
3也可以一次增加指定数量:
r.incrby(article_key,10)print(r.get(article_key))对应减少:
r.decr(article_key)r.decrby(article_key,5)计数器是 Redis 非常常见的使用场景,因为这类操作简单、高频,而且对速度要求高。
八、Hash:保存对象信息
如果要保存一个用户对象,用多个 String 也可以:
user:1:name user:1:age user:1:city但更推荐使用 Hash:
r.hset("user:1",mapping={"name":"Alice","age":20,"city":"Shanghai"})name=r.hget("user:1","name")print(name)user=r.hgetall("user:1")print(user)输出:
Alice{'name':'Alice','age':'20','city':'Shanghai'}修改 Hash 中的某个字段:
r.hset("user:1","city","Beijing")删除某个字段:
r.hdel("user:1","age")判断字段是否存在:
exists=r.hexists("user:1","name")print(exists)Hash 非常适合保存用户资料、商品摘要、文章基础信息等对象型数据。
九、List:保存列表数据
Redis 的 List 可以理解为一个有序列表。
常见用途:
- 最新消息列表
- 简单队列
- 最近浏览记录
- 日志缓冲
示例:
key="user:1:recent_articles"r.delete(key)r.lpush(key,"article:100")r.lpush(key,"article:101")r.lpush(key,"article:102")articles=r.lrange(key,0,-1)print(articles)输出:
['article:102','article:101','article:100']lpush是从左侧插入,所以最后插入的元素排在最前面。
如果想从右侧插入,可以使用:
r.rpush(key,"article:103")从列表中弹出一个元素:
item=r.lpop(key)print(item)限制列表长度,只保留最近 3 条:
r.ltrim(key,0,2)这个操作很适合做“最近浏览记录”。
十、Set:保存不重复集合
Set 是无序且不重复的集合。
常见用途:
- 用户标签
- 点赞用户集合
- 已签到用户集合
- 去重统计
示例:记录给文章点赞的用户。
key="article:100:liked_users"r.delete(key)r.sadd(key,"user:1")r.sadd(key,"user:2")r.sadd(key,"user:1")users=r.smembers(key)print(users)is_liked=r.sismember(key,"user:1")print(is_liked)输出:
{'user:1','user:2'}True虽然添加了两次user:1,但 Set 会自动去重。
统计集合元素数量:
count=r.scard(key)print(count)取消点赞:
r.srem(key,"user:1")十一、Sorted Set:排行榜案例
Sorted Set 是有序集合,每个元素都有一个分数。
它非常适合做排行榜。
例如游戏积分排行榜:
key="game:rank"r.delete(key)r.zadd(key,{"Alice":1500,"Bob":1800,"Cindy":1700,"David":1200})top3=r.zrevrange(key,0,2,withscores=True)print(top3)输出:
[('Bob',1800.0),('Cindy',1700.0),('Alice',1500.0)]给用户增加积分:
r.zincrby(key,200,"Alice")查询 Alice 的排名:
rank=r.zrevrank(key,"Alice")print(rank)注意:Redis 的排名从 0 开始。如果返回 0,表示第一名。
十二、保存 JSON 数据
有时我们希望把一个 Python 字典直接缓存起来。
可以使用json.dumps()转成字符串保存,读取时再用json.loads()转回来。
importjsonimportredis r=redis.Redis(host="localhost",port=6379,db=0,decode_responses=True)user={"id":1,"name":"Alice","age":20,"roles":["admin","editor"]}r.set("cache:user:1",json.dumps(user,ensure_ascii=False),ex=300)cached_text=r.get("cache:user:1")cached_user=json.loads(cached_text)print(cached_user["name"])print(cached_user["roles"])这种方式很适合缓存接口返回结果。
不过要注意:Redis 本身保存的是字符串。如果你要频繁修改对象中的某个字段,Hash 可能比 JSON 字符串更方便。
十三、完整案例:使用 Redis 缓存用户资料
下面做一个更贴近实际项目的案例。
假设我们有一个“慢查询函数”,它模拟从数据库查询用户资料。为了提高速度,我们先查 Redis;如果 Redis 中没有,再查数据库,并把结果写入 Redis。
importjsonimporttimeimportredis r=redis.Redis(host="localhost",port=6379,db=0,decode_responses=True)defquery_user_from_database(user_id):"""模拟数据库查询。"""print("正在查询数据库...")time.sleep(2)fake_database={1:{"id":1,"name":"Alice","age":20},2:{"id":2,"name":"Bob","age":22},3:{"id":3,"name":"Cindy","age":19},}returnfake_database.get(user_id)defget_user(user_id):"""优先从 Redis 缓存读取用户资料。"""cache_key=f"cache:user:{user_id}"cached_user=r.get(cache_key)ifcached_user:print("命中 Redis 缓存")returnjson.loads(cached_user)user=query_user_from_database(user_id)ifuserisNone:returnNoner.set(cache_key,json.dumps(user,ensure_ascii=False),ex=60)print("已写入 Redis 缓存")returnuserif__name__=="__main__":print(get_user(1))print(get_user(1))第一次运行时,输出类似:
正在查询数据库... 已写入 Redis 缓存{'id':1,'name':'Alice','age':20}命中 Redis 缓存{'id':1,'name':'Alice','age':20}第一次查询 Redis 没有数据,所以会访问“数据库”,并等待 2 秒。
第二次查询时,Redis 中已经有缓存,所以直接返回,速度明显更快。
这就是 Redis 最典型的缓存使用方式。
十四、封装一个简单的 Redis 工具类
在项目中,我们通常不会把 Redis 操作散落在各个地方。可以先封装一个简单工具类:
importjsonimportredisclassRedisCache:def__init__(self,host="localhost",port=6379,db=0):self.client=redis.Redis(host=host,port=port,db=db,decode_responses=True)defset_json(self,key,value,expire_seconds=300):text=json.dumps(value,ensure_ascii=False)self.client.set(key,text,ex=expire_seconds)defget_json(self,key):text=self.client.get(key)iftextisNone:returnNonereturnjson.loads(text)defdelete(self,key):returnself.client.delete(key)defexists(self,key):returnself.client.exists(key)==1cache=RedisCache()cache.set_json("user:1",{"id":1,"name":"Alice"},expire_seconds=60)user=cache.get_json("user:1")print(user)print(cache.exists("user:1"))cache.delete("user:1")这样后续业务代码会更清晰。
十五、连接池:更适合项目使用
在 Web 项目中,不建议每次请求都重新创建 Redis 连接。
可以使用连接池:
importredis pool=redis.ConnectionPool(host="localhost",port=6379,db=0,decode_responses=True,max_connections=20)r=redis.Redis(connection_pool=pool)r.set("site:name","Python Redis Demo")print(r.get("site:name"))连接池可以复用连接,减少频繁创建连接带来的开销。
如果你以后使用 Flask、FastAPI、Django,也可以在应用启动时创建 Redis 客户端,然后在接口中复用它。
十六、删除 key 和清理数据库
删除一个 key:
r.delete("user:1")判断 key 是否存在:
exists=r.exists("user:1")print(exists)查看匹配的 key:
keys=r.keys("user:*")print(keys)清空当前数据库:
r.flushdb()清空所有数据库:
r.flushall()注意:flushdb()和flushall()都是危险操作,真实项目中不要随便执行。
十七、初学者常见问题
1. 连接失败
常见错误:
redis.exceptions.ConnectionError可能原因:
- Redis 服务没有启动
- host 或 port 写错了
- Docker 容器没有映射 6379 端口
- Redis 设置了密码,但代码中没有配置
如果 Redis 设置了密码,需要这样连接:
r=redis.Redis(host="localhost",port=6379,password="your_password",decode_responses=True)2. 读取结果是字节类型
如果看到:
b'Alice'可以在连接时加上:
decode_responses=True3. JSON 读取报错
如果执行:
json.loads(text)报错,通常说明 Redis 中保存的内容不是合法 JSON 字符串。
建议保存和读取都封装成统一方法,避免同一个 key 有时存普通字符串,有时存 JSON。
4. 缓存数据不是最新的
缓存会带来一个新问题:数据库更新了,但 Redis 里还是旧数据。
常见处理方式:
- 更新数据库后删除缓存
- 设置较短的过期时间
- 对一致性要求很高的数据,不要随便缓存
例如:
defupdate_user_name(user_id,new_name):update_database_user_name(user_id,new_name)r.delete(f"cache:user:{user_id}")这样下次查询时会重新从数据库读取最新数据,再写入 Redis。
十八、Redis key 命名建议
Redis 的 key 最好有清晰的命名规则。
推荐格式:
业务名:对象名:对象ID:字段名例如:
user:1:profile user:1:recent_articles article:100:view_count article:100:liked_users login:code:13800138000 game:rank好的 key 命名可以让排查问题更容易。
不推荐随意命名:
a test data1 abc项目变大后,这类 key 很难维护。
十九、什么时候适合使用 Redis
适合使用 Redis 的场景:
- 高频读取的数据
- 可以设置过期时间的数据
- 允许短时间不一致的数据
- 计数器、排行榜、点赞集合
- 验证码、Token、Session
- 接口限流、简单队列
不适合使用 Redis 的场景:
- 复杂 SQL 查询
- 强事务业务
- 长期保存的核心数据
- 需要大量关联查询的数据
一句话总结:核心业务数据放 MySQL、PostgreSQL、SQLite 等数据库中,Redis 更适合放热点数据和临时数据。
二十、完整练习代码
最后给出一个可以直接运行的综合练习:
importjsonimportredisdefmain():r=redis.Redis(host="localhost",port=6379,db=0,decode_responses=True)print("连接状态:",r.ping())print("\n=== String 示例 ===")r.set("app:name","Redis Python Demo",ex=300)print(r.get("app:name"))print("\n=== Counter 示例 ===")r.delete("article:1:view_count")for_inrange(5):r.incr("article:1:view_count")print(r.get("article:1:view_count"))print("\n=== Hash 示例 ===")r.hset("user:1",mapping={"name":"Alice","age":20,"city":"Shanghai"})print(r.hgetall("user:1"))print("\n=== List 示例 ===")r.delete("user:1:recent")r.lpush("user:1:recent","article:100","article:101","article:102")print(r.lrange("user:1:recent",0,-1))print("\n=== Set 示例 ===")r.delete("article:1:likes")r.sadd("article:1:likes","user:1","user:2","user:1")print(r.smembers("article:1:likes"))print("\n=== Sorted Set 示例 ===")r.delete("rank:score")r.zadd("rank:score",{"Alice":98,"Bob":86,"Cindy":95})print(r.zrevrange("rank:score",0,-1,withscores=True))print("\n=== JSON 缓存示例 ===")user={"id":1,"name":"Alice","skills":["Python","Redis"]}r.set("cache:user:1",json.dumps(user,ensure_ascii=False),ex=60)cached_user=json.loads(r.get("cache:user:1"))print(cached_user)if__name__=="__main__":main()运行前确保:
- Redis 服务已经启动
- 已安装 Python 依赖:
pip install redis - 代码中的 host、port、password 配置正确
总结
Python 操作 Redis 的核心流程并不复杂:
安装 redis 库 -> 连接 Redis -> 选择数据结构 -> 写入数据 -> 读取数据 -> 设置过期时间常用方法可以简单记住:
| 方法 | 作用 |
|---|---|
set()/get() | 读写字符串 |
incr()/decr() | 计数器 |
hset()/hgetall() | 操作 Hash |
lpush()/lrange() | 操作 List |
sadd()/smembers() | 操作 Set |
zadd()/zrevrange() | 操作排行榜 |
expire()/ttl() | 设置和查看过期时间 |
delete() | 删除 key |
对 Python 初学者来说,学习 Redis 的重点不是一开始就背所有命令,而是先理解它适合解决什么问题。
如果你能掌握缓存用户资料、验证码、计数器、排行榜这几个案例,就已经可以在很多真实项目中使用 Redis 了。