散列与字符串
至此,本章中陆续介绍了 HSET、HSETNX、HGET、HINCRBY 和 HINCRBYFLOAT 等多个散列命令,如果你对第 2 章介绍过的字符串命令还有印象,应该记得字符串也有类似的 SET、SETNX、GET、INCRBY 和 INCRBYFLOAT 命令。这种相似并不是巧合,正如表3-1所示,散列的确拥有很多与字符串命令功能相似的命令。


Redis 选择同时提供字符串键和散列键这两种数据结构,是因为它们虽然在操作上非常相似,但是各自却又拥有不同的优点,这使得它们在某些场合无法被对方替代,下面将分别介绍这两种数据结构各自的优点。
散列键的优点
散列的最大优势,就是它只需要在数据库里面创建一个键,就可以把任意多的字段和值存储到散列里面。相反,因为每个字符串键只能存储一个键值对,所以如果用户要使用字符串键去存储多个数据项,就只能在数据库中创建多个字符串键。
图3-23展示了使用字符串键和散列键存储相同数量的数据项时,数据库中创建的字符串键和散列键。

从图3-23中可以看到,为了存储 4 个数据项,程序需要用到 4 个字符串键或者一个散列键。按此计算,如果我们需要存储 100 万篇文章,那么在使用散列键的情况下,程序只需要在数据库里面创建 100 万个散列键就可以了;但是如果使用字符串键,那么程序就需要在数据库里面创建 400 万个字符串键。
数据库键数量增多带来的问题主要和资源有关:
-
为了对数据库以及数据库键的使用情况进行统计,Redis 会为每个数据库键存储一些额外的信息,并因此带来一些额外的内存消耗。对于单个数据库键来说,这些额外的内存消耗几乎可以忽略不计,但是当数据库键的数量达到上百万、上千万甚至更多的时候,这些额外的内存消耗就会变得比较可观。
-
当散列包含的字段数量比较少的时候,Redis 就会使用特殊的内存优化结构去存储散列中的字段和值。与字符串键相比,这种内存优化结构存储相同数据所需要的内存要少得多。使用内存优化结构的散列越多,内存优化结构的效果也就越明显。在一定条件下,对于相同的数据,使用散列键进行存储比使用字符串键存储要节约一半以上的内 存,有时候甚至会更多。
-
除了需要耗费更多内存之外,更多的数据库键也需要占用更多的 CPU。每当 Redis 需要对数据库中的键进行处理时,数据库包含的键越多,进行处理所需的 CPU 资源就会越多,处理所耗费的时间也会越长,典型的情况包括:
-
统计数据库和数据库键的使用情况。
-
对数据库执行持久化操作,或者根据持久化文件还原数据库。
-
通过模式匹配在数据库中查找某个键,或者执行类似的查找操作。
-
这些操作的执行时间都会受到数据库键数量的影响。
最后,除了资源方面的优势之外,散列键还可以有效地组织起相关的多项数据,让程序产生更容易理解的数据,使得针对数据的批量操作变得更方便。比如在上面展示的图 3-23 中,使用散列键存储文章数据就比使用字符串键存储文章数据更为清晰、易懂。
字符串键的优点
虽然使用散列键可以有效地节约资源并更好地组织数据,但是字符串键也有自己的优点:
-
虽然散列键命令和字符串键命令在部分功能上有重合的地方,但是字符串键命令提供的操作比散列键命令更为丰富。比如,字符串能够使用 SETRANGE 命令和 GETRANGE 命令设置或者读取字符串值的其中一部分,或者使用 APPEND 命令将新内容追加到字符串值的末尾,而散列键并不支持这些操作。
-
第 12 章中将对 Redis 的键过期功能进行介绍,这一功能可以在指定时间到达时,自动删除指定的键。因为键过期功能针对的是整个键,用户无法为散列中的不同字段设置不同的过期时间,所以当一个散列键过期的时候,它包含的所有字段和值都将被删除。与此相反,如果用户使用字符串键存储信息项,就不会遇到这样的问题——用户可以为每个字符串键分别设置不同的过期时间,让它们根据实际的需要自动被删除。
字符串键和散列键的选择
表 3-2 从资源占用、支持的操作以及过期时间 3 个方面对比了字符串键和散列键的优缺点。

既然字符串键和散列键各有优点,那么我们在构建应用程序的时候,什么时候应该使用字符串键,什么时候又该使用散列键呢?对于这个问题,以下总结了一些选择的条件和方法:
-
如果程序需要为每个数据项单独设置过期时间,那么使用字符串键。
-
如果程序需要对数据项执行诸如 SETRANGE、GETRANGE 或者 APPEND 等操作,那么优先考虑使用字符串键。当然,用户也可以选择把数据存储在散列中,然后将类似 SETRANGE、GETRANGE 这样的操作交给客户端执行。
-
如果程序需要存储的数据项比较多,并且你希望尽可能地减少存储数据所需的内存,就应该优先考虑使用散列键。
-
如果多个数据项在逻辑上属于同一组或者同一类,那么应该优先考虑使用散列键。