Redis数据类型-哈希详解

786 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

在redis中,哈希类型指对应的value本身又是一个键值对结构,和字符串类型比较如下

image.png 这是对应关系,我们再搞清楚一个概念,在哈希数据类型中,将value中的key称之为field,如下图所示。

image.png 我们先看一看哈希的操作命令

1 命令

1.1 设置值

设置值的命令是hset key field value

127.0.0.1:6379> hset user:1 name luke
(integer) 1

奇怪的是,当key的值已经存在,这样设置会报错

127.0.0.1:6379> hset user name luke
(error) WRONGTYPE Operation against a key holding the wrong kind of value

这是因为,key为user的是存在的,并且value类型不是哈希类型

127.0.0.1:6379> get user
"demi"

当设置为同类型的时候是可以的,但是变更类型设置的时候就报错了。

这个命令的时间复杂度是O(1),hset就是简单的设置值,值得注意的就是设置的值若是存在,必须value的类型一样才可以去修改。

1.2 获取值

获取值的命令则是hget key field

127.0.0.1:6379> hget user:1 name
"luke"

当key或者field不存在的时候,都会返回nil

127.0.0.1:6379> hget user:1 name
"luke"
127.0.0.1:6379> hget user:1 name1
(nil)
127.0.0.1:6379> hget user:2 name
(nil)

这个命令的时间复杂度是O(1),获取值可以使用它,但是只能一个一个获取,这个命令不能批量获取。

1.3 删除field

我们可以删除一个或者多个field,使用的命令是hdel key field ...

127.0.0.1:6379> hdel user:1 name
(integer) 1
127.0.0.1:6379> hdel user:1 name
(integer) 0

操作删除了几个field就返回几,当没有数据删除的时候,就返回0.

127.0.0.1:6379> hset user1 name luke
(integer) 1
127.0.0.1:6379> hset user1 age 18
(integer) 1
127.0.0.1:6379> hdel user1 name age
(integer) 2

删除命令则是主动的使值失效,这个操作需要注意,不要同时删除过多的值,以免获取数据的时候获取不到,从而使大量的流量打到数据库。其实也很少使用,一般都给值设了过期时间,使其到时自动失效。

1.4 计算field个数

hlen key会返回对应的key有几个field

127.0.0.1:6379> hset user3 name luke
(integer) 1
127.0.0.1:6379> hset user3 age 18
(integer) 1
127.0.0.1:6379> hset user3 sex 1
(integer) 1
127.0.0.1:6379> hlen user3
(integer) 3

这个命令旨在计算个数,当key不存在的时候会返回0,当key下边的field全部删除了也会返回0。从官方给出的时间复杂度O(1)来看是直接取了保存的总数,并没有轮询所有值。

1.5 批量设置或者获取field-value

批量设置是hmset key field value
批量获取是hmget key field

127.0.0.1:6379> hmset user4 name josh age 19 city xian
OK
127.0.0.1:6379> hmget user4 name age city
1) "josh"
2) "19"
3) "xian"

这两个命令则是设置值和获取值的升级版,主要是批量操作节省了网络IO成本,redis本来处理数据就是很快的,再减少网络成本之后,岂不是快的飞起。

1.6 判断field是否存在

判断是否存在的话命令是hexists key field

127.0.0.1:6379> hexists user1 name
(integer) 0
127.0.0.1:6379> hexists user4 name
(integer) 1

存在返回1,不存在返回0

1.7 获取所有field

hkeys key可以获取到key下所有的field,会轮询所有。

127.0.0.1:6379> hkeys user4
1) "name"
2) "age"
3) "city"

1.8 获取所有value

hvals key可以获取到key下的所有value,会轮询所有并按照设置的顺序排列

127.0.0.1:6379> hvals user4
1) "josh"
2) "19"
3) "xian"

1.9 获取所有的field-value

hgetall key会按照field value的顺序依次输出key对应的值。

127.0.0.1:6379> hgetall user4
1) "name"
2) "josh"
3) "age"
4) "19"
5) "city"
6) "xian"

这个命令会轮询所有的,当对应的key内field比较多的时候,会很影响性能。

1.10 hincrby hincrbyfloat

hincrby key field将field对应value的值加上指定的增量

127.0.0.1:6379> hmset user1 name luke age 18
OK
127.0.0.1:6379> hincrby user1 age 2
(integer) 20
127.0.0.1:6379> hget user1 age
"20"

hincrbyfloat key field这个命令则要求field对应value的值可以解析为float类型

127.0.0.1:6379> hset user:2 height 175.5
(integer) 1
127.0.0.1:6379> hincrbyfloat user:2 height 3
"178.5"

这两个命令其实与incrbyincrbyfloat相同,只是作用域不同

1.11 计算value的字符串长度

hstrlen key field可以返回field对应的value的长度。

127.0.0.1:6379> hmset user1 name luke age 18
OK
127.0.0.1:6379> hstrlen user1 name
(integer) 4

这个命令的时间复杂度是O(1),直接读取的值,并不会去轮询计算。

2 内部编码

哈希类型的内部编码有两种

  • ziplist
  • hashtable

2.1 ziplist

ziplist其实设计初衷是为了更节省内存,它会使用更加紧凑的结构实现多个元素的连续存储
使用这个内部编码有两个要求

  • 元素个数小于hash-max-ziplist-entries配置,默认是512个
  • 所有值的大小小于hash-max-ziplist-value配置,默认是64字节

2.2 hashtable

当hash类型不满足上边说的两个条件任意一个的时候,会使用hashtable类型的内部编码作为hashl类型的实现。

就不演示了,造满足这俩条件的数据也蛮累的,而且也比较简单,这里其实会牵扯到内存优化技巧,我们之后会聊到。

3 使用场景

存储类似于java中的对象信息数据,比如:

image.png

其实这样的数据字符串类型序列化也可以存储,只是使用哈希类型更加直观并且可以任意修改其中的属性内容。

那么就有小伙伴想了,那我不在数据库存了,我把用户表在redis中用hash存了不就行了。 这里的哈希类型和一般的关系型数据库还是有稍许不同的。

  • 哈希类型中key的field是可变的,对于不同的的user,luke和crdric我们可以设置不同的field,但是在关系型数据库中,只要列确定了,每条数据都要设置值,尽管可能是null。
  • 关系型数据库我们可以做复杂的关联查询,动辄关联几十张表进行查询,sql几十行。但是这么复杂的查询操作在redis中是很难实现的,就算实现了,数据的变化,也会让维护成本变得很高。

所以,就算redis中的哈希可以存储类似于关系型数据库表的数据,但是它是远远不能完全替代的。