Redis遍历所有key的两个命令 -- KEYS 和 SCAN

本文详细介绍了Redis中SCAN命令的使用方法及其优势。包括命令的基本格式、如何使用游标进行增量迭代、COUNT和MATCH选项的作用等。并通过PHP代码示例展示了SCAN命令的实际应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当我们需要遍历Redis所有key或者指定模式的key时,首先想到的是KEYS命令:

KEYS pattern


官网对于KEYS命令有一个提示:  KEYS 的速度非常快,例如,Redis在一个有1百万个key的数据库里面执行一次查询需要的时间是40毫秒 。但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的  KEYS , 你最好还是用 Redis 的集合结构  SETS  来代替。
KEYS命令使用很简单。

redis> MSET one 1 two 2 three 3 four 4
OK
redis> KEYS *o*
1) "four"
2) "one"
3) "two"
redis> KEYS t??
1) "two"
redis> KEYS *
1) "four"
2) "three"
3) "one"
4) "two"
redis>
但由于KEYS命令一次性返回所有匹配的key,所以,当redis中的key非常多时,对于内存的消耗和redis服务器都是一个隐患,
对于Redis 2.8以上版本给我们提供了一个更好的遍历key的命令 SCAN 该命令的基本格式:

SCAN cursor [MATCH pattern] [COUNT count]

SCAN  每次执行都只会返回少量元素,所以可以用于生产环境,而不会出现像 KEYS 或者 SMEMBERS 命令带来的可能会阻塞服务器的问题。

SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程

SCAN命令的游标参数(即cursor)被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。

简单的迭代演示:

redis 127.0.0.1:6379> scan 0
1) "17"
2)  1) "key:12"
    2) "key:8"
    3) "key:4"
    4) "key:14"
    5) "key:16"
    6) "key:17"
    7) "key:15"
    8) "key:10"
    9) "key:3"
   10) "key:7"
   11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
   2) "key:18"
   3) "key:0"
   4) "key:2"
   5) "key:19"
   6) "key:13"
   7) "key:6"
   8) "key:9"
   9) "key:11"

在上面这个例子中, 第一次迭代使用 0 作为游标, 表示开始一次新的迭代。第二次迭代使用的是第一次迭代时返回的游标 17 ,作为新的迭代参数 。

显而易见,SCAN命令的返回值 是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则又是一个数组, 这个数组中包含了所有被迭代的元素。

注意:返回的游标不一定是递增的,可能后一次返回的游标比前一次的小。

在第二次调用 SCAN 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集已经被完整遍历过了。

full iteration :以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历。

SCAN增量式迭代命令并不保证每次执行都返回某个给定数量的元素,甚至可能会返回零个元素, 但只要命令返回的游标不是 0 , 应用程序就不应该将迭代视作结束。

不过命令返回的元素数量总是符合一定规则的, 对于一个大数据集来说, 增量式迭代命令每次最多可能会返回数十个元素;而对于一个足够小的数据集来说,可能会一次迭代返回所有的key

COUNT选项

对于增量式迭代命令不保证每次迭代所返回的元素数量,我们可以使用COUNT选项, 对命令的行为进行一定程度上的调整。COUNT 选项的作用就是让用户告知迭代命令, 在每次迭代中应该从数据集里返回多少元素。使用COUNT 选项对于对增量式迭代命令相当于一种提示, 大多数情况下这种提示都比较有效的控制了返回值的数量。

注意:COUNT选项并不能严格控制返回的key数量,只能说是一个大致的约束。并非每次迭代都要使用相同的 COUNT 值,用户可以在每次迭代中按自己的需要随意改变 COUNT 值, 只要记得将上次迭代返回的游标用到下次迭代里面就可以了。

MATCH 选项

类似于KEYS 命令,增量式迭代命令通过给定 MATCH 参数的方式实现了通过提供一个 glob 风格的模式参数, 让命令只返回和给定模式相匹配的元素。

MATCH 选项对元素的模式匹配工作是在命令从数据集中取出元素后和向客户端返回元素前的这段时间内进行的, 所以如果被迭代的数据集中只有少量元素和模式相匹配, 那么迭代命令或许会在多次执行中都不返回任何元素。

以下是这种情况的一个例子:

redis 127.0.0.1:6379> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
redis 127.0.0.1:6379> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2)  1) "key:611"
    2) "key:711"
    3) "key:118"
    4) "key:117"
    5) "key:311"
    6) "key:112"
    7) "key:111"
    8) "key:110"
    9) "key:113"
   10) "key:211"
   11) "key:411"
   12) "key:115"
   13) "key:116"
   14) "key:114"
   15) "key:119"
   16) "key:811"
   17) "key:511"
   18) "key:11"
redis 127.0.0.1:6379>
可以看出,以上的大部分迭代都不返回任何元素。在最后一次迭代, 我们通过将 COUNT 选项的参数设置为 1000 , 强制命令为本次迭代扫描更多元素, 从而使得命令返回的元素也变多了。

基于SCAN的这种安全性,建议大家在生产环境都使用SCAN命令来代替KEYS,不过注意,该命令是在2.8.0版本之后加入的,如果你的Redis低于这个版本,则需要升级Redis。


下面用PHP代码演示SCAN命令的使用:

<?php

$redis = new Redis();

$redis->connect('127.0.0.1', 6379);


/* 设置遍历的特性为不重复查找,该情况下扩展只会scan一次,所以可能会返回空集合 */
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY);

$it = NULL;
$pattern = '*';
$count = 50;  // 每次遍历50条,注意是遍历50条,遍历出来的50条key还要去匹配你的模式,所以并不等于就能够取出50条key

do
{
    $keysArr = $redis->scan($it, $pattern, $count);

    if ($keysArr)
    {
        foreach ($keysArr as $key)
        {
            echo $key . "\n";
        }
    }

} while ($it > 0);   //每次调用 Scan会自动改变 $it 值,当$it = 0时 这次遍历结束 退出循环


echo '---------------------------------------------------------------------------------' . "\n";


/* 设置扩展在一次scan没有查找出记录时 进行重复的scan 直到查询出结果或者遍历结束为止 */
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);

$it = NULL;
$pattern = '*';
$count = 50;  // 每次遍历50条,注意是遍历50条,遍历出来的50条key还要去匹配你的模式,所以并不等于就能够取出50条key

//这种用法下我们只需要简单判断返回结果是否为空即可, 如果为空说明遍历结束
while ($keysArr = $redis->scan($it, $pattern, $count))
{
    foreach ($keysArr as $key)
    {
        echo $key . "\n";
    }
}

执行结果:

[root@localhost php]# /usr/local/php/bin/php scan.php 
bm
bm2
h1
name
bit
bm1
places
cities
hhl
---------------------------------------------------------------------------------
bm
bm2
h1
name
bit
bm1
places
cities
hhl

注意:如果php执行报错 请升级到较新版本的Redis扩展

更多请参考:






Thai's all!

<think>嗯,用户想查找Redis中所有没有设置过期时间的key,或者TTL等于-1的。那我得先回忆一下Redis的相关命令。首先,RedisKEYS命令可以匹配所有符合模式的键,比如KEYS *,但这样会阻塞服务器,尤其是在数据量大的时候,生产环境不太推荐使用。用户提到的示例代码里用了SCAN命令,可能更合适,因为SCAN是增量式的,不会阻塞。 接下来,每个键的过期时间可以通过TTL命令查看。TTL返回-1表示没有设置过期时间,-2表示键不存在。所以需要遍历所有键,检查它们的TTL是否为-1。但如何高效地遍历呢?使用SCAN命令迭代,然后对每个键执行TTL,判断是否为-1。 不过,用户提供的引用里提到了一些相关的命令,比如SINTERSTOREBRPOP,但这些可能当前问题无关。不过引用中的示例代码使用了scan方法来获取匹配模式的键,比如"future_*"。这可能对用户的问题有帮助,因为用户需要扫描所有键,然后过滤出没有过期时间的。 另外,用户提到的问题可能涉及到性能问题。如果直接使用KEYS *,可能会导致Redis阻塞,尤其是在数据量大的情况下。所以应该推荐使用SCAN命令来分批次获取键,减少对服务器的影响。 然后,具体步骤应该是:用SCAN遍历所有键,对每个键执行TTL命令,收集那些TTL为-1的键。这个过程可以通过脚本实现,比如用Lua脚本或者用客户端代码循环SCANTTL。 不过,Redis本身并没有直接提供一次性查询所有无过期时间键的命令,所以需要自己组合命令。例如,在shell中可以用redis-cli的--scan选项,结合管道xargs来处理。比如: redis-cli --scan | while read key; do if [ $(redis-cli ttl "$key") -eq -1 ]; then echo "$key" fi done 但这种方法在键很多的时候会很慢,因为每个TTL命令都是一个请求。有没有更高效的方法?可能用Lua脚本,用SCAN在服务端遍历,然后直接返回符合条件的键。不过Lua脚本需要考虑执行时间,避免阻塞。 另外,用户可能需要考虑权限问题,是否有权限执行SCANTTL命令,以及数据量大的情况下内存网络的影响。如果键的数量极大,可能需要分多次处理,或者使用并行处理的方法。 还有一个问题,如何验证这个方法的正确性。比如,测试环境中先试一下,确认返回的键确实是没有设置过期时间的。用户提供的引用中有一个测试方法用了scan,可能可以参考。 总结下来,解决方案的大致步骤是: 1. 使用SCAN命令遍历所有键。 2. 对每个键执行TTL命令,检查是否为-1。 3. 收集符合条件的键并返回。 需要注意避免使用KEYS命令,使用SCAN代替。另外,处理大量数据时需要分批次,避免长时间阻塞或占用过多资源。</think>要查找Redis中所有没有设置过期时间的键(即TTL等于-1),可以通过以下分步实现: ### 实现步骤 1. **使用SCAN命令安全遍历所有键** ```bash redis-cli --scan --pattern '*' | while read key; do echo "检查键: $key" done ``` 避免使用`KEYS *`命令,防止阻塞Redis服务[^1]。 2. **结合TTL命令过滤无过期时间的键** ```bash redis-cli --scan --pattern '*' | while read key; do ttl=$(redis-cli ttl "$key") if [ $ttl -eq -1 ]; then echo "无过期时间: $key" fi done ``` TTL返回值说明: - `-1`: 无过期时间 - `-2`: 键不存在 - `>=0`: 剩余生存时间(秒)[^2] 3. **优化方案(Lua脚本实现)** ```lua local cursor = 0 local keys = {} repeat local res = redis.call('SCAN', cursor, 'MATCH', '*', 'COUNT', 100) cursor = tonumber(res[1]) for _,key in ipairs(res[2]) do if redis.call('TTL', key) == -1 then table.insert(keys, key) end end until cursor == 0 return keys ``` 执行脚本: ```bash redis-cli --eval find_no_expire.lua ``` ### 注意事项 - **性能影响**:扫描百万级键可能耗时数分钟,建议在从节点执行 - **内存消耗**:返回大量键可能占用客户端内存,建议分批处理 - **版本要求**:SCAN命令需要Redis 2.8+版本支持[^3] ### 验证方法 ```bash # 设置测试键 redis-cli set test_key1 value1 redis-cli set test_key2 value2 EX 60 # 执行过滤脚本应只返回test_key1 ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值