SCAN
迭代数据库中的键名称。
语法
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
引入版本:2.8.0
复杂度摘要:O(1)
[!NOTE] 此命令的行为在集群 Redis 环境中有所不同。请参阅 多键操作 页面了解更多信息。
SCAN 命令和密切相关的命令 SSCAN、HSCAN 和 ZSCAN 用于增量迭代元素集合。
由于这些命令允许增量迭代,每次调用仅返回少量元素,因此它们可以在生产中使用,而不会出现像 KEYS 或 SMEMBERS 这样的命令的缺点,这些命令在针对大量键或元素调用时可能会长时间(甚至几秒钟)阻塞服务器。
然而,虽然像 SMEMBERS 这样的阻塞命令能够在给定时刻提供属于 Set 的所有元素,但 SCAN 命令系列仅对返回的元素提供有限的保证,因为我们增量迭代的集合在迭代过程中可能会发生变化。
请注意,SCAN、SSCAN、HSCAN 和 ZSCAN 的工作方式都非常相似,因此本文档涵盖了所有四个命令。然而,一个明显的区别是,在 SSCAN、HSCAN 和 ZSCAN 的情况下,第一个参数是保存 Set、Hash 或 Sorted Set 值的键的名称。 SCAN 命令不需要任何键名参数,因为它迭代当前数据库中的键,因此迭代的对象是数据库本身。
必需参数
cursor
光标值。使用 0 开始迭代,然后使用每次调用返回的游标作为下一次调用的参数。
可选参数
MATCH pattern
仅返回与给定的 glob-style 模式匹配的键。
COUNT count
每次迭代返回的键数量的提示。默认值为 10。
TYPE type
仅返回给定类型的键,例如 string、list 或 set。
详细说明
SCAN基本使用
SCAN 是一个基于游标的迭代器。这意味着在每次调用该命令时,服务器都会返回一个更新的游标,用户需要在下一次调用中将其用作游标参数。
当游标设置为0时开始迭代,当服务器返回的游标为0时终止迭代。以下是SCAN迭代的示例:
> 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"
> 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元素数组包含两个元素,即每个返回的哈希元素的一个字段和一个值。ZSCAN元素数组包含两个元素:一个成员及其关联的分数,适用于有序集合的每个返回元素。
扫描保证
SCAN 命令以及 SCAN 系列中的其他命令能够向用户提供一组与完整迭代相关的保证。
- 完整迭代始终检索从完整迭代开始到结束期间出现在集合中的所有元素。这意味着,如果给定元素在迭代开始时位于集合内部,并且在迭代终止时仍然存在,则在某个时刻
SCAN将其返回给用户。 - 完整迭代永远不会返回从完整迭代开始到结束期间集合中不存在的任何元素。因此,如果一个元素在迭代开始之前被删除,并且在迭代持续期间从未添加回集合中,则
SCAN确保永远不会返回该元素。
然而,由于 SCAN 的关联状态非常少(只有光标),因此它具有以下缺点:
- 给定元素可能会多次返回。由应用程序来处理重复元素的情况,例如,仅使用返回的元素来执行多次重新应用时安全的操作。
- 在完整迭代期间集合中不常出现的元素可能会被返回,也可能不会:它是未定义的。
每次SCAN调用返回的元素数量
SCAN 系列函数不保证每次调用返回的元素数量在给定范围内。这些命令还允许返回零元素,并且只要返回的游标不为零,客户端就不应该认为迭代完成。
但是返回的元素数量是合理的,也就是说,实际上,SCAN 在迭代大型集合时可能会以几十个元素的顺序返回最大数量的元素,或者当迭代的集合小到足以在内部表示为编码数据结构时(这种情况发生在小型集合、哈希和有序集合合中),可能会在一次调用中返回集合的所有元素。
不过,用户可以使用 COUNT 选项来调整每次调用返回元素数量的数量级。
计数选项
虽然 SCAN 不保证每次迭代返回的元素数量,但可以使用 COUNT 选项凭经验调整 SCAN 的行为。基本上,用户通过 COUNT 指定 为了从集合中检索元素,每次调用时应完成的工作量。这是用于实现的 只是一个提示,但是一般来说,这是您在大多数情况下可以从实现中期望得到的。
COUNT默认值为 10。- 当迭代键空间、或者足够大到可以用哈希表表示的 Set、Hash 或 Sorted Set 时,假设没有使用 MATCH 选项,服务器通常会在每次调用时返回 计数 或比 计数 多一些的元素。请查看本文档后面的 为什么SCAN可能会一次返回所有元素 部分。
- 当迭代编码为 intsets 的 Sets(仅由整数组成的小集合)或编码为 ziplists 的哈希值和有序集合合(由小的单个值组成的小哈希值和集合)时,通常在第一个
SCAN调用中返回所有元素,无论COUNT值如何。
重要提示:每次迭代都有 不需要使用相同的COUNT值。调用者可以根据需要自由地将计数从一次迭代更改为另一次迭代,只要在下一次调用中传递的光标是在上一次调用该命令时获得的光标即可。
匹配选项
可以仅迭代与给定 glob 样式模式匹配的元素,类似于将模式作为其唯一参数的 KEYS 命令的行为。
为此,只需在 SCAN 命令末尾附加 MATCH <pattern> 参数(它适用于所有 SCAN 系列命令)。
这是使用 MATCH 迭代的示例:
> sadd myset 1 2 3 foo foobar feelsgood
(integer) 6
> sscan myset 0 match f*
1) "0"
2) 1) "foo"
2) "feelsgood"
3) "foobar"
需要注意的是,MATCH 过滤器是在从集合中检索元素之后、将数据返回给客户端之前应用的。这意味着,如果模式与集合内的元素很少匹配,则 SCAN 在大多数迭代中可能不会返回任何元素。示例如下所示:
> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
> 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"
正如您所看到的,大多数调用返回零个元素,但最后一个调用使用了 1000 的 COUNT 来强制命令对该迭代进行更多扫描。
使用 Redis集群 时,搜索针对暗示单个槽的模式进行了优化。如果一种模式只能匹配一个槽的键,则在搜索与该模式匹配的键时,Redis 只会迭代该槽中的键,而不是整个数据库。例如,对于模式 {a}h*llo,Redis 只会尝试将其与槽 15495 中的键进行匹配,这也是哈希标签 {a} 所暗示的。要使用带有哈希标签的模式,请参阅集群规范中的 哈希标签 以获取更多信息。
类型选项
您可以使用 TYPE 选项要求 SCAN 仅返回与给定 type 匹配的对象,从而允许您迭代数据库查找特定类型的键。 TYPE 选项仅适用于整个数据库 SCAN,不适用于 HSCAN 或 ZSCAN 等。
type 参数与 TYPE 命令返回的字符串名称相同。请注意,某些 Redis 类型(例如 GeoHashes、HyperLogLogs、Bitmap 和 Bitfields)可能在内部使用其他 Redis 类型(例如字符串或 zset)实现,因此无法通过 SCAN 与相同类型的其他键区分开来。例如,ZSET 和 GEOHASH:
> GEOADD geokey 0 0 value
(integer) 1
> ZADD zkey 1000 value
(integer) 1
> TYPE geokey
zset
> TYPE zkey
zset
> SCAN 0 TYPE zset
1) "0"
2) 1) "geokey"
2) "zkey"
需要注意的是,从数据库检索元素后也会应用 TYPE 过滤器,因此该选项不会减少服务器完成完整迭代所需的工作量,并且对于罕见类型,您可能在多次迭代中接收不到任何元素。
NOVALUES 选项
使用HSCAN时,可以使用NOVALUES选项使Redis仅返回哈希表中的键而不返回其对应的值。
> HSET myhash a 1 b 2
OK
> HSCAN myhash 0
1) "0"
2) 1) "a"
2) "1"
3) "b"
4) "2"
> 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 键空间教程。
附加示例
给出以下命令,显示哈希键的迭代,在交互式控制台中尝试:
redis> HMSET hash name Jack age 33
OK
redis> HSCAN hash 0
1) "0"
2) 1) "name"
2) "Jack"
3) "age"
4) "33"
Redis Software 与 Redis Cloud 兼容性
| Redis 软件 |
Redis Cloud |
说明 |
|---|---|---|
| ✅ 标准 |
✅ 标准 |
返回值
RESP2
数组回复:具体来说,是一个包含两个元素的数组。 * 第一个元素是 批量字符串回复,表示无符号 64 位数字,即游标。 * 第二个元素是带有扫描键名称的 数组回复。
RESP3
数组回复:具体来说,是一个包含两个元素的数组。 * 第一个元素是 批量字符串回复,表示无符号 64 位数字,即游标。 * 第二个元素是带有扫描键名称的 数组回复。
资料来源:Redis 官方 SCAN 文档;中文内容参考 Redis 文档许可。