扫描
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
- 适用日期:
- 2.8.0
- 时间复杂度:
- 每次调用都是 O(1)。完整迭代(包括足够的命令调用以使光标返回到 0)都是 O(N)。N 是集合内的元素数量。
- ACL 类别:
-
@keyspace,,@read@slow,
该SCAN命令和密切相关的命令SSCAN,HSCAN以及ZSCAN用于逐步迭代元素集合。
由于这些命令允许增量迭代,每次调用仅返回少量元素,因此它们可以在生产中使用,而不会出现或之类的命令的缺点,KEYS当SMEMBERS针对大量键或元素进行调用时,可能会长时间(甚至几秒钟)阻塞服务器。
然而,虽然像这样的阻塞命令SMEMBERS能够在给定时刻提供集合中的所有元素,但是 SCAN 系列命令仅对返回的元素提供有限的保证,因为我们逐步迭代的集合可能会在迭代过程中发生变化。
请注意SCAN,、SSCAN和HSCAN的ZSCAN工作原理非常相似,因此本文档涵盖了所有四个命令。但是,一个明显的区别是,在 、和 的情况下,SSCAN第HSCAN一个ZSCAN参数是保存 Set、Hash 或 Sorted Set 值的键的名称。该SCAN命令不需要任何键名称参数,因为它会迭代当前数据库中的键,因此迭代对象是数据库本身。
SCAN 基本用法
SCAN 是基于游标的迭代器。这意味着每次调用该命令时,服务器都会返回一个更新的游标,用户需要在下一次调用中将其用作游标参数。
一次迭代从游标设置为0开始,到服务器返回的游标为0时终止。以下是SCAN迭代的一个示例:
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"
在上面的例子中,第一次调用使用零作为游标,以启动迭代。第二次调用使用前一次调用返回的游标作为回复的第一个元素,即 17。
如您所见,SCAN 返回值是一个由两个值组成的数组:第一个值是下次调用时要使用的新游标,第二个值是一个元素数组。
由于在第二次调用中返回的游标为 0,因此服务器向调用者发出信号,表示迭代已完成,并且已完全探索集合。从游标值 0 开始迭代,并一直调用SCAN直到返回的游标再次为 0,这称为完整迭代。
返回值
SCAN,,SSCAN并返回一个两元素HSCAN的ZSCAN多批量答复,其中第一个元素是一个表示无符号 64 位数的字符串(光标),第二个元素是一个带有元素数组的多批量答复。
SCAN元素数组是键的列表。SSCAN元素数组是 Set 成员的列表。HSCAN对于 Hash 的每个返回元素,元素数组包含两个元素:一个字段和一个值。ZSCAN对于有序集合的每个返回元素,元素数组包含两个元素,一个成员及其关联的分数。
扫描保证
该SCAN命令以及该系列中的其他命令SCAN能够向用户提供一组与完整迭代相关的保证。
- 完整迭代始终会检索从完整迭代开始到结束集合中存在的所有元素。这意味着,如果给定元素在迭代开始时位于集合中,并且在迭代终止时仍位于集合中,则在某个时刻将
SCAN其返回给用户。 - 完整迭代不会返回从完整迭代开始到结束集合中不存在的任何元素。因此,如果在迭代开始之前删除了某个元素,并且在迭代持续期间从未将其添加回集合,则可确保永远不会返回
SCAN此元素。
然而,由于SCAN关联的状态很少(只有光标),它有以下缺点:
- 给定元素可能会被多次返回。应用程序需要处理重复元素的情况,例如,仅使用返回的元素来执行多次重新应用时安全的操作。
- 在完整迭代期间集合中不是持续存在的元素可能会被返回,也可能不会被返回:它是未定义的。
每次 SCAN 调用返回的元素数量
SCAN系列函数不保证每次调用返回的元素数在给定范围内。命令还允许返回零个元素,只要返回的游标不为零,客户端就不应该认为迭代已完成。
但是返回元素的数量是合理的,也就是说,实际上SCAN在迭代大型集合时可能返回最多几十个元素的数量,或者在迭代集合足够小以至于可以在内部表示为编码数据结构时(这适用于小的集合、哈希和有序集合),可能在一次调用中返回集合的所有元素。
但是,用户可以使用COUNT选项来调整每次调用返回元素数量的数量级。
COUNT 选项
虽然SCAN不保证每次迭代返回的元素数量,但可以根据经验调整SCAN使用COUNT选项的行为。基本上,使用 COUNT,用户指定每次调用时应完成的工作量,以便从集合中检索元素。这只是对实现的一个提示,但一般来说,这是您大多数时候可以从实现中期望的。
- 默认
COUNT值为 10。 - 在迭代键空间或足够大到可以用哈希表表示的集合、哈希或有序集合时,假设未使用MATCH选项,则服务器通常每次调用将返回count 个或多于count 个元素。请参阅本文档后面的“为什么 SCAN 可以一次返回所有元素”部分。
- 当迭代编码为 intsets(仅由整数组成的小集合)的集合或编码为 ziplists(由小单个值组成的小哈希和集合)的哈希和有序集合时,通常会在第一次
SCAN调用中返回所有元素,而不管COUNT值是什么。
重要提示:没有必要对每次迭代使用相同的 COUNT 值。调用者可以根据需要随意更改迭代之间的计数,只要下一次调用中传递的游标是上一次调用命令时获得的游标即可。
MATCH 选项
可以只迭代与给定的 glob 样式模式匹配的元素,类似于以KEYS模式作为唯一参数的命令的行为。
为此,只需MATCH <pattern>在命令末尾附加参数SCAN(它适用于所有SCAN系列命令)。
这是使用MATCH进行迭代的示例:
redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood
(integer) 6
redis 127.0.0.1:6379> sscan myset 0 match f*
1) "0"
2) 1) "foo"
2) "feelsgood"
3) "foobar"
redis 127.0.0.1:6379>
需要注意的是,MATCH过滤器是在从集合中检索元素之后、即将数据返回到客户端之前应用的。这意味着,如果模式与集合中的元素非常少匹配,则SCAN在大多数迭代中很可能不会返回任何元素。下面显示了一个示例:
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 个元素,以强制命令对该迭代进行更多扫描。
使用Redis Cluster时,搜索针对暗示单个槽的模式进行了优化。如果模式只能匹配一个槽的键,则在搜索与模式匹配的键时,Redis 只会迭代该槽中的键,而不是整个数据库。例如,对于模式{a}h*llo,Redis 只会尝试将其与槽 15495 中的键(即哈希标签{a}所暗示的)进行匹配。要将模式与哈希标签结合使用,请参阅Cluster 规范中的哈希标签以了解更多信息。
TYPE 选项
您可以使用TYPE选项要求SCAN仅返回与给定匹配的对象type,从而允许您遍历数据库以查找特定类型的键。TYPE选项仅适用于整个数据库SCAN,不适用于HSCAN或ZSCAN等。
参数type是命令返回的相同字符串名称TYPE。请注意,某些 Redis 类型(例如 GeoHashes、HyperLogLogs、Bitmaps 和 Bitfields)可能在内部使用其他 Redis 类型(例如字符串或 zset)实现,因此无法通过 将其与相同类型的其他键区分开来SCAN。例如,ZSET 和 GEOHASH:
redis 127.0.0.1:6379> GEOADD geokey 0 0 value
(integer) 1
redis 127.0.0.1:6379> ZADD zkey 1000 value
(integer) 1
redis 127.0.0.1:6379> TYPE geokey
zset
redis 127.0.0.1:6379> TYPE zkey
zset
redis 127.0.0.1:6379> SCAN 0 TYPE zset
1) "0"
2) 1) "geokey"
2) "zkey"
值得注意的是,TYPE过滤器也会在从数据库检索元素后应用,因此该选项不会减少服务器完成完整迭代所需的工作量,并且对于罕见类型,您可能在许多迭代中都收不到任何元素。
NOVALUES 选项
使用时HSCAN,可以使用NOVALUES选项让Redis仅返回哈希表中的键而不返回其对应的值。
redis 127.0.0.1:6379> HSET myhash a 1 b 2
OK
redis 127.0.0.1:6379> HSCAN myhash 0
1) "0"
2) 1) "a"
2) "1"
3) "b"
4) "2"
redis 127.0.0.1:6379> HSCAN myhash 0 NOVALUES
1) "0"
2) 1) "a"
2) "b"
多次并行迭代
无限数量的客户端可以同时迭代同一个集合,因为迭代器的完整状态位于游标中,每次调用时都会获取并返回给客户端。根本不获取服务器端状态。
中途终止迭代
由于没有状态服务器端,但完整状态由游标捕获,因此调用者可以自由地中途终止迭代,而无需以任何方式向服务器发出信号。可以启动无限数量的迭代,并且永远不会终止,而不会出现任何问题。
使用损坏的游标调用 SCAN
使用损坏、负数、超出范围或其他无效游标进行调用SCAN将导致未定义的行为,但绝不会导致崩溃。未定义的是,实现不再能够确保对返回元素的保证SCAN。
唯一可以使用的有效游标是:
- 开始迭代时游标值为 0。
- 上次调用 SCAN 时返回的游标,以便继续迭代。
终止担保
SCAN仅当迭代集合的大小保持在给定的最大大小范围内时,算法才保证终止,否则迭代始终增长的集合可能会导致永远SCAN不会终止完整的迭代。
这很容易直观地看出:如果集合增长,为了访问所有可能的元素要做的工作就越来越多,而终止迭代的能力取决于调用的次数SCAN及其 COUNT 选项值与集合增长的速度相比。
为什么 SCAN 可能在一次调用中返回聚合数据类型的所有项目?
在COUNT选项文档中,我们指出,有时这一系列命令可能会在一次调用中一次返回 Set、Hash 或 Sorted Set 的所有元素,而不管COUNT选项值是什么。发生这种情况的原因是,只有当我们扫描的聚合数据类型表示为哈希表时,基于游标的迭代器才可以实现,并且很有用。但是,Redis 使用内存优化,其中小聚合数据类型(直到它们达到给定数量的项目或给定的最大单个元素大小)使用紧凑的单分配打包编码表示。在这种情况下,SCAN没有有意义的游标可返回,并且必须一次迭代整个数据结构,因此它唯一合理的行为是在一次调用中返回所有内容。
但是,一旦数据结构变得更大并被提升为使用真实哈希表,SCAN命令系列将诉诸正常行为。请注意,由于这种返回所有元素的特殊行为仅适用于小型聚合,因此它对命令复杂性或延迟没有影响。但是,转换为真实哈希表的确切限制是用户可配置的,因此您可以在单个调用中看到返回的最大元素数取决于聚合数据类型可以有多大,并且仍然使用打包表示。
还要注意,此行为是 所特有的SSCAN,HSCAN并且ZSCAN.SCAN本身从不显示此行为,因为密钥空间始终由哈希表表示。
进一步阅读
有关管理密钥的更多信息,请参阅Redis Keyspace教程。
其他示例
哈希值的迭代。
redis 127.0.0.1:6379> hmset hash name Jack age 33
OK
redis 127.0.0.1:6379> hscan hash 0
1) "0"
2) 1) "name"
2) "Jack"
3) "age"
4) "33"
RESP2/RESP3 回复
数组回复:具体来说,是一个有两个元素的数组。
历史
- 从 Redis 版本 6.0.0 开始:添加了
TYPE子命令。