SCAN

语法
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
可用版本
Redis Open Source 2.8.0
时间复杂度
每次调用为 O(1)。对于一次完整迭代,包括足够多的命令调用使游标回到 0,时间复杂度为 O(N)。N 是集合中的元素数量。
ACL 分类
@keyspace, @read, @slow,

SCAN 命令以及密切相关的命令 SSCANHSCANZSCAN 用于增量遍历元素集合。

  • SCAN 遍历当前选定 Redis 数据库中的键集合。
  • SSCAN 遍历 Set 类型中的元素。
  • HSCAN 遍历 Hash 类型中的字段及其关联的值。
  • ZSCAN 遍历 Sorted Set 类型中的元素及其关联的分值。

由于这些命令允许增量遍历,每次调用仅返回少量元素,因此可以在生产环境中使用,而不会像 KEYSSMEMBERS 命令那样,在对大型键集合或元素集合执行时可能长时间(甚至几秒钟)阻塞服务器。

然而,虽然像 SMEMBERS 这样的阻塞命令能够提供给定时刻 Set 中所有元素,但 SCAN 系列命令仅提供有限的关于返回元素的保证,因为我们在增量遍历时集合可能会发生变化。

请注意,SCANSSCANHSCANZSCAN 的工作方式非常相似,因此本文档涵盖了这四个命令。然而,一个明显的区别是,对于 SSCANHSCANZSCAN,第一个参数是持有 Set、Hash 或 Sorted Set 值的键名。SCAN 命令不需要任何键名参数,因为它遍历当前数据库中的键,所以被遍历的对象是数据库本身。

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,这被称为完整迭代

返回值

SCANSSCANHSCANZSCAN 返回一个包含两个元素的 multi-bulk 回复,其中第一个元素是表示一个无符号 64 位数字(游标)的字符串,第二个元素是一个 multi-bulk 包含一个元素数组。

  • SCAN 返回的元素数组是键的列表。
  • SSCAN 返回的元素数组是 Set 成员的列表。
  • HSCAN 返回的元素数组对于 Hash 的每个返回元素包含两个元素:一个字段和一个值。
  • ZSCAN 返回的元素数组对于 Sorted Set 的每个返回元素包含两个元素:一个成员及其关联的分值。

Scan 保证

SCAN 命令以及 SCAN 系列中的其他命令能够为用户提供与完整迭代相关的一系列保证。

  • 一次完整迭代总是检索从完整迭代开始到结束时集合中存在的所有元素。这意味着如果在迭代开始时某个给定元素在集合中,并且在迭代结束时它仍然存在,那么 SCAN 会在某个时候将其返回给用户。
  • 一次完整迭代绝不会返回在完整迭代开始到结束期间集合中不存在的任何元素。因此,如果在迭代开始前一个元素被删除,并且在整个迭代期间没有被重新添加到集合中,SCAN 会确保永远不会返回该元素。

然而,由于 SCAN 关联的状态非常少(只有游标),它存在以下缺点

  • 给定元素可能会被多次返回。应用程序需要处理重复元素的情况,例如仅使用返回的元素执行可安全重复多次的操作。
  • 在完整迭代期间并非始终存在于集合中的元素,可能会返回也可能不返回:这是未定义的行为。

每次 SCAN 调用返回的元素数量

SCAN 系列函数不保证每次调用返回的元素数量在给定范围内。这些命令也允许返回零元素,并且只要返回的游标不为零,客户端就不应认为迭代已完成。

然而,返回元素的数量是合理的,也就是说,实际上,当遍历大型集合时,SCAN 返回的元素数量最多可达几十个;而当被遍历的集合足够小,可以在内部表示为编码数据结构时(这适用于小型 Set、Hash 和 Sorted Set),SCAN 可能在一次调用中返回集合中的所有元素。

但是,用户可以使用 COUNT 选项来调整每次调用返回元素数量的量级。

COUNT 选项

虽然 SCAN 不保证每次迭代返回的元素数量,但可以使用 COUNT 选项凭经验调整 SCAN 的行为。基本上,通过 COUNT,用户指定了每次调用为了从集合中检索元素应该完成的工作量。这对于实现来说只是一个提示,但总的来说,这通常是您对实现的大部分期望。

  • 默认的 COUNT 值为 10。
  • 在遍历键空间,或足够大而可以用哈希表表示的 Set、Hash 或 Sorted Set 时,假设没有使用 MATCH 选项,服务器通常会在每次调用时返回 count 个或略多于 count 个元素。请参阅本文档后面关于为什么 SCAN 可能一次返回所有元素的部分。
  • 在遍历编码为 intsets 的 Set(仅由整数构成的小集合),或编码为 ziplists 的 Hash 和 Sorted Set(由小个体值构成的小 Hash 和 Set)时,通常会在第一次 SCAN 调用中返回所有元素,无论 COUNT 值是多少。

重要提示:每次迭代不需要使用相同的 COUNT 值。调用者可以根据需要自由更改每次迭代的计数,只要下一次调用中传递的游标是在前一次调用命令中获得的游标即可。

MATCH 选项

可以只遍历匹配给定 glob 样式模式的元素,这类似于 KEYS 命令的行为,该命令将模式作为其唯一参数。

为此,只需在 SCAN 命令的末尾附加 MATCH <pattern> 参数(这适用于所有 SCAN 系列命令)。

这是使用 MATCH 进行迭代的示例

重要的是要注意,MATCH 过滤器是在从集合中检索元素后应用的,就在将数据返回给客户端之前。这意味着如果模式匹配集合中非常少的元素,SCAN 在大多数迭代中很可能不会返回任何元素。下面的示例对此进行了展示

正如您所见,大多数调用都返回了零元素,但最后一次调用使用了 1000 的 COUNT 值,以便强制命令在该次迭代中进行更多扫描。

使用 Redis Cluster 时,针对仅涉及单个槽位的模式优化了搜索。如果一个模式只能匹配一个槽位中的键,Redis 在搜索匹配该模式的键时只会迭代该槽位中的键,而不是整个数据库。例如,对于模式 {a}h*llo,Redis 只会尝试将其与哈希标签 {a} 所属的槽位 15495 中的键进行匹配。要使用带有哈希标签的模式,请参阅 Cluster 规范中的哈希标签以获取更多信息。

TYPE 选项

您可以使用 TYPE 选项来要求 SCAN 只返回匹配给定 type 的对象,从而允许您遍历数据库查找特定类型的键。TYPE 选项仅适用于针对整个数据库的 SCAN,不适用于 HSCANZSCAN 等。

type 参数是与 TYPE 命令返回的字符串名称相同。请注意一个奇怪的地方,一些 Redis 类型,例如 GeoHashes、HyperLogLogs、Bitmaps 和 Bitfields,在内部可能使用其他 Redis 类型(例如 string 或 zset)实现,因此 SCAN 无法将它们与该相同类型的其他键区分开来。例如,一个 ZSET 和一个 GEOHASH

重要的是要注意,TYPE 过滤器也是在从数据库中检索元素后应用的,因此该选项不会减少服务器完成完整迭代所需的工作量,并且对于稀有类型,您在多次迭代中可能不会收到任何元素。

NOVALUES 选项

使用 HSCAN 时,您可以使用 NOVALUES 选项使 Redis 只返回哈希表中的键而不返回其对应的值。

多个并行迭代

无限数量的客户端可以同时迭代同一个集合,因为迭代器的完整状态在游标中,每次调用都会获得并返回给客户端。服务器端不保留任何状态。

中途终止迭代

由于服务器端没有状态,完整状态由游标捕获,调用者可以自由地中途终止迭代,而无需以任何方式向服务器发出信号。可以启动无限数量的迭代并且永不终止,没有任何问题。

使用损坏的游标调用 SCAN

使用损坏、负数、超出范围或以其他方式无效的游标调用 SCAN 将导致未定义行为,但绝不会导致崩溃。未定义的将是 SCAN 实现无法再保证返回元素的保证。

唯一有效的游标是

  • 开始迭代时的游标值 0。
  • 为了继续迭代,使用上一次调用 SCAN 返回的游标。

终止保证

只有当被迭代集合的大小保持在给定最大范围内时,才能保证 SCAN 算法终止,否则迭代一个不断增长的集合可能导致 SCAN 永远无法完成一次完整迭代。

这很容易凭直觉理解:如果集合增长,为了访问所有可能的元素,需要做的工作会越来越多,而迭代终止的能力取决于对 SCAN 的调用次数及其 COUNT 选项值与集合增长速度的比较。

为什么 SCAN 可能在一次调用中返回聚合数据类型的所有项?

COUNT 选项文档中,我们提到有时这一系列命令可能在一次调用中返回 Set、Hash 或 Sorted Set 的所有元素,无论 COUNT 选项值是多少。发生这种情况的原因是,基于游标的迭代器只有在我们要扫描的聚合数据类型表示为哈希表时才能实现并发挥作用。然而,Redis 使用了一种内存优化,其中小型聚合数据类型,在达到给定项数或给定单个元素最大大小之前,使用紧凑的单分配打包编码表示。在这种情况下,SCAN 没有有意义的游标可以返回,并且必须一次遍历整个数据结构,因此它唯一合理的行为是在一次调用中返回所有内容。

然而,一旦数据结构变大并升级为使用真正的哈希表,SCAN 系列命令将恢复正常行为。请注意,由于这种返回所有元素的特殊行为仅适用于小型聚合数据类型,因此它对命令的复杂性或延迟没有影响。但是,转换为真正的哈希表的精确限制是用户可配置的,因此在单次调用中可以看到返回的最大元素数量取决于聚合数据类型可以有多大并仍然使用打包表示。

另请注意,此行为特定于 SSCANHSCANZSCANSCAN 本身从不表现出这种行为,因为键空间始终由哈希表表示。

延伸阅读

有关管理键的更多信息,请参阅Redis 键空间教程。

附加示例

在交互式控制台中尝试以下命令,展示哈希键的迭代过程

HMSET hash name Jack age 33 HSCAN hash 0

RESP2/RESP3 回复

数组回复:具体来说,是一个包含两个元素的数组。

  • 第一个元素是一个批量字符串回复,表示一个无符号 64 位数字,即游标。
  • 第二个元素是一个数组回复,包含扫描到的键的名称。

历史

  • 自 Redis 6.0.0 版本开始:添加了 TYPE 子命令。
评价此页面
回到顶部 ↑