Python操作Redis时scan_iter()为何比keys()搭配mget()慢很多?
性能差异原因
- 方案1(keys + mget)速度最快的核心逻辑
keys命令是单次请求拉取Redis实例当前库的所有匹配键,仅产生1次网络往返,后续mget批量拉取值也是单次请求,全程仅2次Redis交互,本身执行效率确实极高。但要注意keys是阻塞命令,Redis单线程处理模型下,当库中键量超过10万级时,执行keys会导致整个实例阻塞数秒到数十秒不等,所有线上业务请求都会被卡住,这才是官方不推荐生产环境使用的核心理由,不是它本身执行速度慢。 - 方案2(scan_iter逐次get)慢20~30倍的原因
每次遍历到键都单独发起一次get请求,几千个键就产生几千次网络往返,绝大部分耗时都消耗在网络IO上,和scan_iter本身无关,属于写法不合理导致的性能浪费。 - 方案3(scan_iter收集键 + mget)慢4倍的原因
redis-py的scan_iter默认count参数为10,也就是每次scan请求仅拉取10个键,3000多键的场景下需要发起300多次scan请求,几百次网络往返对比keys的1次拉取所有键,自然会慢几倍。
疑问解答
代码是否有优化空间?
有,两个优化点可以让方案3的速度接近keys的水平:
- 直接给
scan_iter传pattern参数,在Redis端直接过滤掉前缀为1_的键,不需要本地遍历判断,减少无效键的传输 - 调大
count参数,比如设置为1000,大幅减少scan的请求次数
优化后写法示例:
def query_scaniter_mget_optimized(): start = datetime.now() # 直接用pattern过滤,count调大到1000 redis_keys = list(r.scan_iter(pattern='[^1_]*', count=1000)) values = r.mget(redis_keys) end = datetime.now() print(str(len(values)) + ' values queried in ' + str(end - start) + ' with optimized scan_iter and mget.')
优化后方案3的耗时会降到0.05s左右,和keys方案几乎一致。
keys是不是最优选择?
分场景判断:
- 如果是离线脚本、测试环境,或者你的库键量永远稳定在1万以内、Redis实例无线上业务请求,那用
keys完全没问题 - 如果是生产环境、键量可能增长到10万以上、需要保障实例可用性,绝对不能用
keys,优化后的scan方案才是最优选择,牺牲极小的单请求耗时,避免整个实例阻塞的风险。
内容的提问来源于stack exchange,提问作者Bagheera
相关产品推荐
相关产品推荐

