dot Redis 8 发布了——它是开源的

了解更多

SET 命令是一个奇怪的家伙

旋齿鲨是一种现已灭绝但奇怪的动物,漫游于早二叠纪的海洋。它在大小和形状上与当代的虎鲨大致相似。最有可能的是,它是海洋中的一种可怕掠食者。它与众不同之处在于它有一个“齿轮”,这有点像下颚内藏着一把鲨鱼牙齿组成的圆锯。这似乎是个好主意,但进化有不同的想法,我们现在没有那样的现存动物。这是一个进化的死胡同。

在某些方面,Redis 的 SET 命令就像旋齿鲨,但它仍然在全球的 Redis 服务器中游荡。它是一个非常早期的命令,具有一些不寻常的特性,这些特性看起来是个好主意,但如果使用不当可能会很危险,但如果正确使用则非常有用。

另一方面,SET 命令似乎和任何其他命令一样普通。它是我们学习时最早接触的命令之一,我们也用它来做简单的测试以确保 Redis 正常工作。我记不清输入过多少次这个命令了

> SET foo bar

所以,表面上这没什么特别的。但它隐藏了什么吗?

SET:数据的破坏者

回到我们简单的 SET 示例。让我们添加更多上下文

> UNLINK foo
(integer) 1
> HSET foo bar 123
(integer) 1
> SET foo bar
OK

你注意到 SET 在这里有什么奇怪之处了吗?现有的键 foo 是哈希类型(由于 HSET),但当我紧接着运行 SET 时,它仍然接受了这个命令。与其他 Redis 命令相比,这实际上非常奇怪。让我们使用相同的命令,但颠倒最后两个命令的顺序

> UNLINK foo
(integer) 1
> SET foo bar
OK
> HSET foo bar 123
(error) WRONGTYPE Operation against a key holding the wrong kind of value

你可以看到 SET 忽略了键的存在或类型,总是写入。另一方面,哈希在遇到非空且类型不同的键时会抛出错误。对于除了 String 类型之外的所有数据类型都是如此,特别是 SET 命令及其少数变体(PSETEX, SETEX, MSET)也是如此。例如:

> HSET foo bar 123
(integer) 1
> APPEND foo bar
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> INCR foo
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> SETBIT foo 1 1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> BITFIELD foo SET u8 0 1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> INCRBY foo 1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> INCRBYFLOAT foo 1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> SETRANGE foo 1 barbar
(error) WRONGTYPE Operation against a key holding the wrong kind of value

SETNX 和 SET…NX(稍后会详细介绍)是一个有趣的旁注,如果键不存在,它们会进行 SET,如果设置成功返回 1,否则返回 0。所以,它不进行类型检查,而是存在检查。

总而言之,SET 根本不关心类型。它总是写入,几乎没有什么能阻挡它。

一个 TYPE,三种类型。

如果你使用 Redis 有一段时间了,你会知道可以使用 TYPE 命令检索存储在键中的数据类型。所以,例如,让我们回到 foo

> SET foo bar
OK
> TYPE foo
string

很简单。你在键 foo 设置了值“bar”。现在,让我们看看其他内容

> SET foo 1234
OK
> TYPE foo
String
> GETRANGE foo 2 3
"34"

所以,你可能认为数字 1234 是以字符形式存储的,你的想法或多或少是正确的。然而,这个故事还有更多内容

> INCR foo
(integer) 1235
> GETRANGE foo 2 3
"35"

这说明 Redis 将字符理解为文本和数字——你可以认为这是一种松散类型。但这变得很奇怪

> SET foo "hello world"
OK
> INCR foo
(error) ERR value is not an integer or out of range

显然,Redis 不能增加非数字。但还有更多细节要讲。Redis 也理解浮点数。看这个例子

> SET foo 1.2
OK
> INCR foo
(error) ERR value is not an integer or out of range
> INCRBYFLOAT foo 0.8
"2"
> INCR foo
(integer) 3

你可以看到初始值是浮点数,因此 INCR(用于整数)将不起作用。然而,INCRBYFLOAT 可以工作。这将值更改为一个整数,该整数可以被之前不允许的 INCR 命令使用。

一个命令,多个参数

该命令的另一个独特之处是能够提供两类可选参数:一类用于设置过期时间,另一类用于检查键是否存在。让我们看看第一类:过期参数。

对于大多数命令,如果你想立即让键过期,你需要在之后立即执行 EXPIREPEXPIRE,通常是在 MULTI / EXEC 事务中。例如

> MULTI
OK
> SADD baz alpha beta gamma
QUEUED
> EXPIRE baz 10
QUEUED
> EXEC
1) (integer) 3
2) (integer) 1

这确保了你的 SADD 命令和 EXPIRE 命令之间不会被中断。你知道在 EXEC 之后,你将立即拥有一个在 10 秒后过期的集合。然而,使用 SET,你可以在没有事务的情况下做到这一点。

> SET foo bar EX 10
OK

或者,你可以使用 PX 而不是 EX 来以毫秒为单位而不是秒为单位设置过期时间。这是一个方便的简写形式,也可以用 SETEX 和 PSETEX 来表达。我认为这些命令只是快捷方式——它们在你的应用中节省了几次击键,以及服务器之间的几个字节传输,但牺牲了一点可读性和灵活性。

另一类参数,NX / XX,控制 SET 如何处理存在或不存在的数据。NX 参数仅在键不存在时设置值。所以,看这个例子

> UNLINK foo
(integer) 0
> SET foo 1234 NX
OK
> GET foo
"1234"
> SET foo 5678 NX
(nil)
> GET foo
"1234"

你可以看到第四个命令实际上什么都没做,因为键 foo 已经存在。这有很多用途:设置默认值且不覆盖现有数据,防止用户输入成为键一部分时意外进行 SET 等。

与此相反的是 XX 命令。它仅在键已经存在时设置值。

> UNLINK foo
(integer) 1
> SET foo 1234 XX
(nil)
> set foo 1234
OK
> SET foo 5678 XX
OK

这可用于将写入限制在明确定义的键上。但它不做类型检查。所以 XX 仍然会覆盖其他类型的键,只要该键存在。

那么,SET 是危险的 / 不好的 / 不建议使用吗?

当然不是。SET 是 Redis 中许多优秀模式运行的基础。然而,它有许多特性与 Redis 的其余部分根本不同。了解这些特性如何工作对于正确假设如何构建键空间并在应用中操作 Redis 至关重要。