Set 命令是一个怪兽

阅读数:32 2019 年 10 月 24 日 16:07

Set命令是一个怪兽

Helicoprion(旋齿鲨)是一种现已灭绝但奇怪的动物,它生活在二叠纪早期的海洋中,它在尺寸和形状上与现代大白鲨看起来或多或少相似,它曾经是海洋中强大的掠夺者,因其下颌上长着排列成螺旋状的牙齿而得名,这有点类似于在下颚内部放置一个带有鲨鱼齿的圆锯,但这不符合生物的进化论,因此我们现在看不到这样的物种了。

在某些方面,Redis SET 命令就像旋齿鲨,但它仍然在全球范围内的大量 Redis 服务器上被使用。SET 是一个非常早期的命令,具有一些非常有用、不寻常的功能,但是在深度使用的同时可能存在风险。

SET 命令看起来很简单直接,我们在学习 Redis 的时候通常将它用作第一个命令,我们使用它进行简单的测试以确保 Redis 正常工作。比如:

复制代码
> SET foo bar

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

SET 命令:一种数据的破坏者

回到我们简单的 SET 示例。让我们模拟一个更复杂的场景:

复制代码
> UNLINK foo
(integer) 1
> HSET foo bar 123
(integer) 1
> SET foo bar
OK

有没有用 SET 发现这里的怪异?现有的键 foo 是 hash 类型(由于 HSET),但是当我之后立即运行 SET 时它仍然可以正常工作。与其他 Redis 命令相比,这实际上非常奇怪。让我们采取相同的命令,但对最后两个命令使用相反的顺序:

复制代码
> UNLINK foo
(integer) 1
> SET foo bar
OK
> HSET foo bar 123
(error) WRONGTYPE Operation against a key holding thewrong kind of value

您可以看到 SET 忽略键的存在或类型,并始终写入,另一方面,哈希在面对不同类型的非空键时会抛出错误。除了字符串之外的所有数据类型都是如此,特别是 SET 命令和一些衍生的命令(PSETEX,SETEX,MSET),例如:

复制代码
> HSET foo bar 123
(integer) 1
> APPEND foo bar
(error) WRONGTYPE Operation against a key holding thewrong kind of value
> INCR foo
(error) WRONGTYPE Operation against a key holding thewrong kind of value
> SETBIT foo 1 1
(error) WRONGTYPE Operation against a key holding thewrong kind of value
> BITFIELD foo SET u8 0 1
(error) WRONGTYPE Operation against a key holding thewrong kind of value
> INCRBY foo 1
(error) WRONGTYPE Operation against a key holding thewrong kind of value
> INCRBYFLOAT foo 1
(error) WRONGTYPE Operation against a key holding thewrong kind of value
> SETRANGE foo 1 barbar
(error) WRONGTYPE Operation against a key holding thewrong 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 不能对非数字执行 INCR。但请继续看, 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

您可以看到初始值是作为 float 放入的,因此 INCR(对于整数)将不起作用,但是,INCRBYFLOAT 确实有效,这 1.2 + 0.8 = 2 会将值更改为前一个 INCR 命令允许使用的整数。

一个命令,多种参数

该命令的另一个独特之处在于能够提供两类可选参数:一类用于到期,另一类用于存在检查。我们来看看第一个类别:到期参数。

对于大多数命令,如果要立即使键过期,则需要立即发出 EXPIRE 或 PEXPIRE,最常见的是在 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"

您可以看到第 4 个命令实际上没有做任何事情,因为键 foo 已经存在。这有很多用途:设置默认值而不覆盖现有数据,防止在用户在误操作输入时对键的以外覆盖。

与此相反的是 XX 命令,这仅在键已存在时设置值:

复制代码
> UNLINK foo
(integer) 1
> SET foo 1234 XX
(nil)
> set foo 1234
OK
> SET foo 5678 XX
OK

这可用于将写入限制在已经定义过的键上。一种使用这个命令的场景是输入检查,只有在已经输入的情况下,才会把 Key 覆盖。

那么 SET 是一种危险的、不好的、不建议使用的命令?

绝对不是这样的,SET 是 Redis 中许多优秀实践的基础,但是它同时也具有许多与 Redis 其他命令根本不同的特性,重要的是要了解这些功能和命令如何工作,在此基础之上才能更好地组织 Redis 的键空间,以及在应用程序中正确的使用 Redis。

本文转载自公众号中间件小哥(ID:huawei_kevin)。

原文链接:

https://mp.weixin.qq.com/s/2JCBL6bkRSz_B0qnWia2gg

评论

发布