在Redis的五种常用数据结构中,Set(集合)以其天然的去重特性和丰富的集合运算命令受到开发者青睐。当我们需要知道一个集合中究竟存了多少个成员时,SCARD命令就是最直接的答案。它是专为获取集合基数(cardinality)而设计的命令,执行速度极快,无论集合中有十个元素还是一千万个元素,耗时几乎没有差别。本文将从语法、原理、代码实践和常见误区几个方面,把SCARD这个看似简单却颇有讲究的命令讲透。

SCARD命令的基本语法与返回值规则
SCARD的名字来源于Set CARDinality,即集合的基数。它的语法非常简单,只需要一个参数,即集合的键名:
SCARD key
返回值规则值得仔细记住:如果键存在且类型是Set,命令返回集合中成员的数量;如果键不存在,返回0;如果键存在但存储的不是Set类型,比如是String或者List,Redis会直接抛出一个WRONGTYPE错误。这一点在写代码时尤其重要,因为业务代码中键的类型被意外覆盖的情况并不少见,如果不做类型校验,一条SCARD命令就可能让整个请求报错。
下面在redis-cli中演示几个典型场景:
127.0.0.1:6379> SADD user:1001:tags "python" "redis" "mysql" (integer) 3 127.0.0.1:6379> SCARD user:1001:tags (integer) 3 127.0.0.1:6379> SREM user:1001:tags "mysql" (integer) 1 127.0.0.1:6379> SCARD user:1001:tags (integer) 2 127.0.0.1:6379> SCARD not:exist:key (integer) 0
可以看到,随着SADD和SREM的执行,SCARD的返回值实时反映了集合的当前规模。对于不存在的键,Redis并不会报错,而是友好地返回0,这与某些编程语言中访问不存在的容器会抛异常的行为不同,写业务逻辑时可以利用这个特性简化判空处理。
为什么SCARD的时间复杂度是O(1)
很多初学者会疑惑:统计一个有一千万个元素的集合,为什么能瞬间完成?难道不需要遍历整个集合吗?答案藏在Redis的底层实现里。Redis的Set在元素较少且都是整数时会采用intset编码,元素较多或包含字符串时转换为hashtable编码。无论哪种编码,Redis都会在数据结构中维护一个length字段,记录当前成员的数量。
当SADD向集合添加一个成员时,Redis在插入成功后会把计数加一;SREM删除成功时把计数减一。也就是说,计数的维护成本被平摊到了每一次增删操作中,等到执行SCARD时,Redis只需要直接读取这个现成的计数并返回即可,完全不需要遍历集合。这就是典型的空间换时间思想,用少量额外的存储字段,换来了计数查询的常数级开销。
理解了这个原理,就能明白为什么不能用SMEMBERS来做计数。SMEMBERS会一次性返回集合中的所有成员,时间复杂度是O(N)。如果集合里存了五百万个用户ID,一条SMEMBERS不仅会让Redis长时间阻塞,还会把五百万条数据通过网络传输给客户端,内存和网络开销都非常恐怖。曾经有生产事故就是因为前端页面要显示“参与活动的人数”,开发同学随手调了SMEMBERS再在应用层取size,结果大集合一扫描,Redis主线程阻塞,整个服务雪崩。正确的做法永远是用SCARD。
在代码中使用SCARD的实践示例
了解了命令本身,接下来看看在不同编程语言客户端中如何调用。以Python的redis-py库为例:
import redis
# 创建客户端连接
r = redis.Redis(host='127.0.0.1', port=6379, db=0, decode_responses=True)
# 添加标签数据
r.sadd('article:2001:readers', 'user:1', 'user:2', 'user:3')
# 获取阅读该文章的用户数量
reader_count = r.scard('article:2001:readers')
print(f'阅读人数: {reader_count}')
# 对不存在的键调用,返回0
print(r.scard('article:9999:readers')) # 输出: 0Java环境下使用Jedis时,对应的方法名同样是scard:
import redis.clients.jedis.Jedis;
public class ScardDemo {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
// 记录今日活跃用户
jedis.sadd("active:2024-01-01", "u1001", "u1002", "u1003");
jedis.sadd("active:2024-01-01", "u1001"); // 重复添加不会生效
Long count = jedis.scard("active:2024-01-01");
System.out.println("活跃用户数: " + count); // 输出: 活跃用户数: 3
}
}
}需要注意返回值类型。在多数客户端中,SCARD返回的是长整型而非普通整数,如果集合规模可能超过21亿,要留意所用编程语言的整数溢出问题。另外,如果调用时抛出WRONGTYPE相关的异常,说明这个键已经被写入了其他类型的数据,应该先检查业务逻辑中是否有代码用错误的类型操作了同一个键,例如误用SET命令覆盖了原本的Set键。
典型应用场景与组合技巧
SCARD在业务中几乎无处不在。第一个场景是粉丝数与关注数展示:每个用户的粉丝存为一个Set,执行SCARD即可得到粉丝数量,性能远优于关系型数据库的COUNT查询。第二个场景是UV统计:将访问过页面的用户标识写入Set,天然去重,SCARD直接给出独立访客数。第三个场景是标签管理:为每篇文章维护标签Set,通过SCARD可以快速判断标签数量是否符合展示要求。
SCARD还经常与集合运算命令配合使用。例如想统计既收藏又点赞某篇文章的用户数量,可以先执行SINTERSTORE把两个集合的交集存入一个临时键,再对临时键执行SCARD:
SINTERSTORE temp:like:fav article:3001:likes article:3001:favs SCARD temp:like:fav DEL temp:like:fav
这种模式在漏斗分析、用户行为交叉统计中非常实用。不过要提醒的是,SINTERSTORE本身是O(N)复杂度的命令,参与运算的集合很大时仍需谨慎,必要时可以考虑拆分数据或使用SSCAN渐进式处理。对于纯粹只需要交集数量的场景,新版本Redis提供的SINTERCARD命令可以直接返回交集基数而不落盘中间结果,更加高效。
总结一下,SCARD是Redis中性价比极高的命令:语法只有一个参数,返回值规则清晰,O(1)的复杂度让它可以放心地用在任何需要展示集合规模的场合。记住两条原则就够了:统计数量永远优先用SCARD而不是SMEMBERS,遇到WRONGTYPE错误时第一时间排查键的类型是否被污染。掌握这些,你就能在项目中既快又稳地玩转Set集合计数。
Redis SCARDRedis Set集合Redis命令修改时间:2026-09-11 06:34:30