Redis 的 SCARD 命令用于返回集合键的基数,也就是集合中元素的数量。它命令格式非常简单:SCARD key。如果键不存在,返回 0;如果键存在但不是集合类型,会返回类型错误。不少开发者容易把它当成一个绝对无脑的 O(1) 命令,实际上它的时间复杂度取决于集合的底层编码,与数据规模、写入模式和 Redis 版本都有关系。理解这些细节,才能在计数场景中真正用对 SCARD。

一、SCARD 命令基础与底层实现
SCARD 命令从 Redis 早期版本就存在,语法为 SCARD key,返回集合的基数。若键不存在,Redis 将其视为空集合并返回 0。若键类型不是 set,则返回 WRONGTYPE Operation against a key holding the wrong kind of value 错误。这个命令不接受任何可选参数,也不支持一次查询多个键。想要同时获取多个集合的基数,只能使用 pipeline 或 Lua 脚本。
Redis 集合有两种底层编码:intset 和 hashtable。intset 用于元素全部为整数且元素数量不超过 set-max-intset-entries 配置值的情况,默认 512。intset 结构体内部维护 length 字段,因此 SCARD 直接返回该字段,时间复杂度 O(1)。当集合元素数量超过阈值或插入非整数元素时,会转换为 hashtable。hashtable 编码的集合内部就是一个哈希表,维护元素计数为字典大小,SCARD 读取该字典的使用数量,同样是 O(1)。因此大多数情况下 SCARD 都是常数时间操作。
代码示例:
127.0.0.1:6379> SADD users:online 1001 1002 1003 (integer) 3 127.0.0.1:6379> SCARD users:online (integer) 3 127.0.0.1:6379> SCARD not_exist_key (integer) 0
从输出可以看到,SCARD 只返回一个整数,不会返回元素明细。如果客户端需要同时了解集合内容和数量,正确的做法是分别使用 SSCAN 或 SMEMBERS 获取元素,再用 SCARD 获取数量,而不是把整个集合取到本地后计算长度。
二、SCARD 与相似计数命令的对比选择
Redis 提供了多种集合或聚合类型的计数命令:LLEN 返回列表长度,ZCARD 返回有序集合基数,HLEN 返回哈希字段数,BITCOUNT 统计位图中值为 1 的比特数。它们都能完成计数,但数据结构不同,写入、删除和查询复杂度也不同。SCARD 适合需要自动去重且不关心排序的计数场景,例如在线用户数、标签出现次数、文章被点赞的用户数等。
如果数据需要保留重复记录,应该选择列表并用 LLEN;如果需要按分数排序或按范围统计,应该选择有序集合并用 ZCARD 或 ZCOUNT;如果统计维度是二值状态,例如用户是否签到,使用位图配合 BITCOUNT 更省内存。选择时还要考虑内存占用:intset 编码的集合在少量整数场景下比 hashtable 更省内存,但非整数或大集合无法利用 intset。
例如,统计文章标签数量可以用集合存储标签,使用 SCARD article:tags。由于标签天然去重,不会出现重复标签;同时标签通常为字符串,超过 512 个或包含非整数时底层为 hashtable,计数依然是常数时间。对比使用关系型数据库 SELECT COUNT(DISTINCT tag),Redis 的方式减少了数据库压力,但要注意持久化和内存成本。
三、SCARD 的使用注意事项与避坑建议
虽然 SCARD 本身很快,但它并不是完全没有风险。第一个坑是错误地在生产环境对超大集合频繁执行 SCARD 并配合其他命令。SCARD 本身 O(1),但如果集合非常大,例如千万级元素,读写集合时可能触发 Redis 的删除或过期阻塞。对于集合键设置过期时间后,Redis 删除大键时可能造成主线程阻塞,间接影响 SCARD 所在连接。为此应避免让单个集合无限膨胀,可以按时间分片,如 online:2025-01,并用多个 SCARD 汇总。
第二个坑是主从切换或持久化窗口中的数据不一致。SCARD 读取的是当前实例的键空间状态。如果发生主从切换,旧主库上尚未同步到从库的写命令可能导致新主库 SCARD 结果小于业务预期。对强一致计数需求不能只依赖单次 SCARD,可以结合 WAIT 命令等待从库确认后再读取,或者使用 Redis Cluster 下的一致性机制。
第三个坑是把它和 SMEMBERS 混用。有些开发者为了获取数量,先执行 SMEMBERS key 取回全部元素再在客户端求长度。这会带来很大的网络开销和内存压力。正确做法是直接使用 SCARD,只返回一个整数。如果确实需要元素列表再单独调用 SSCAN 迭代获取,而不是一次性取回全量。
还要注意类型检查:复用键名时,如果某个键已经被设置为字符串或哈希,再执行 SCARD 会返回错误。可以在调用前使用 TYPE key 判断,或者在应用层统一键名规范,避免类型冲突。
四、SCARD 性能验证与实战示例
可以通过一个简单的压测体会 SCARD 的性能特征。先构造不同规模的整数集合,再对比 hashtable 编码字符串集合,观察 SCARD 延迟差异。示例:
# 写入少量整数,编码为 intset 127.0.0.1:6379> SADD intset:demo 1 2 3 (integer) 3 127.0.0.1:6379> OBJECT ENCODING intset:demo "intset" 127.0.0.1:6379> SCARD intset:demo (integer) 3 # 写入超过 512 个整数触发转换为 hashtable 127.0.0.1:6379> SADD intset:big 1 2 3 4 5 6 7 8 9 10 (integer) 10 127.0.0.1:6379> OBJECT ENCODING intset:big "hashtable"
实际业务中,计数需求常常伴随去重和过期。比如统计 24 小时内活跃用户数,可以将集合键设计为 active:users:20250117,每个用户 ID 用 SADD 写入,过期时间设为 48 小时。读取时用 SCARD 获取当前活跃用户数。若要看到趋势,可以按小时分片并汇总,但汇总时需要小心键数量过大,可以用 SCARD 获取每个分片后累加,或者改用 HyperLogLog 做去重估算。
另一个实战是权限系统中统计某个角色包含的用户数。如果角色成员常常变化,直接存集合并用 SCARD 返回成员数量非常高效。但要注意如果角色下用户量极大,删除角色或清空集合时会阻塞,可以先用 SSCAN 分批删除,再用 DEL 删除键,避免直接对超大集合执行 DEL。这些实践能帮助你在享受 SCARD 低复杂度优势的同时,避开隐藏在数据规模和运维层面的大坑。
Redis SCARDSCARD命令集合基数修改时间:2026-09-20 13:35:47