SCAN

Syntax
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
Available since:
2.8.0
Time complexity:
O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.
ACL categories:
@keyspace, @read, @slow,

SCAN 命令及其相关命令 SSCANHSCANZSCAN 用于逐步遍历元素集合。

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

由于这些命令允许增量迭代,每次调用只返回少量元素,因此它们可以在生产环境中使用,而不会像KEYSSMEMBERS这样的命令那样,当针对大量键或元素调用时,可能会长时间(甚至几秒钟)阻塞服务器。

然而,虽然像SMEMBERS这样的阻塞命令能够提供在给定时刻属于集合的所有元素,但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,这被称为完整迭代

返回值

SCAN, SSCAN, HSCANZSCAN 返回一个包含两个元素的多重批量回复,其中第一个元素是一个表示无符号64位数字(游标)的字符串,第二个元素是一个包含元素数组的多重批量。

  • SCAN 元素数组是一个键列表。
  • SSCAN 元素数组是集合成员的列表。
  • HSCAN 数组中的每个元素包含两个元素,一个字段和一个值,对应于哈希中返回的每个元素。
  • ZSCAN 数组中的每个元素包含两个元素,一个成员及其关联的分数,对应于排序集合中返回的每个元素。

扫描保证

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

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

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

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

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

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

然而,返回的元素数量是合理的,也就是说,在实际操作中,SCAN在迭代一个大型集合时,可能会返回最多几十个元素,或者当迭代的集合足够小,可以在内部表示为编码数据结构时(这种情况发生在小的集合、哈希和有序集合中),可能会在一次调用中返回集合的所有元素。

然而,用户可以通过使用COUNT选项来调整每次调用返回元素的数量级。

COUNT 选项

虽然 SCAN 不保证每次迭代返回的元素数量,但可以通过经验调整 SCAN 的行为,使用 COUNT 选项。基本上,使用 COUNT 时,用户指定了每次调用时应完成的工作量,以便从集合中检索元素。这只是对实现的一个提示,但一般来说,这是大多数情况下你可以从实现中预期的结果。

  • 默认的 COUNT 值为 10。
  • 当迭代键空间,或者一个足够大以至于可以用哈希表表示的集合、哈希或有序集合时,假设没有使用MATCH选项,服务器通常每次调用会返回count或比count稍多的元素。请查看本文档后面的为什么SCAN可能一次性返回所有元素部分。
  • 当迭代编码为intset(仅由整数组成的小集合)的集合,或编码为ziplist(由小单个值组成的小哈希和集合)的哈希和有序集合时,通常所有元素都会在第一次SCAN调用中返回,无论COUNT值如何。

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

MATCH 选项

可以仅迭代匹配给定通配符样式模式的元素,类似于KEYS命令的行为,该命令将模式作为其唯一参数。

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

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

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

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

当使用Redis Cluster时,搜索针对暗示单个槽的模式进行了优化。如果模式只能匹配一个槽的键,Redis在搜索匹配模式的键时,只会遍历该槽中的键,而不是整个数据库。例如,使用模式{a}h*llo,Redis只会尝试与槽15495中的键匹配,这是哈希标签{a}所暗示的。要使用带有哈希标签的模式,请参阅集群规范中的Hash tags以获取更多信息。

TYPE 选项

你可以使用TYPE选项来让SCAN只返回匹配给定type的对象,这样你就可以遍历数据库寻找特定类型的键。TYPE选项仅在整个数据库的SCAN中可用,而不是在HSCANZSCAN等中。

type 参数与 TYPE 命令返回的字符串名称相同。需要注意的是,一些 Redis 类型,如 GeoHashes、HyperLogLogs、Bitmaps 和 Bitfields,可能在内部使用其他 Redis 类型(如字符串或 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命令系列将恢复到正常行为。请注意,由于这种返回所有元素的特殊行为仅适用于小型聚合,因此对命令的复杂性或延迟没有影响。然而,转换为真正哈希表的确切限制是用户可配置的,因此您在一次调用中可以看到返回的最大元素数量取决于聚合数据类型的大小以及是否仍使用打包表示。

还要注意,这种行为是SSCANHSCANZSCAN特有的。SCAN本身从不显示这种行为,因为键空间总是由哈希表表示。

进一步阅读

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

更多示例

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

HMSET hash name Jack age 33 HSCAN hash 0

RESP2/RESP3 回复

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

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

历史

  • 从 Redis 版本 6.0.0 开始:添加了 TYPE 子命令。
RATE THIS PAGE
Back to top ↑