Redis研究(十七)—SORT排序,redis研究sort排序
一、有序集合的集合操作
集合类型提供了强大的集合操作命令,但是如果需要排序就要用到有序集合类型。Redis的作者在设计Redis的命令时考虑到了不同数据类型的使用场景,对于不常用到的或者在不损失过多性能的前提下可以使用现有命令来实现的功能,Redis就不会单独提供命令来实现。这一原则使得Redis在拥有强大功能的同时保持着相对精简的命令。
有序集合常见的使用场景是大数据排序,如游戏的玩家排行榜,所以很少会需要获得键中的全部数据。同样Redis认为开发者在做完交集、并集运算后不需要直接获得全部结果,而是会希望将结果存入新的键中以便后续处理。这解释了为什么有序集合只有ZINTERSTORE和ZUNIONSTORE命令而没有ZINTER和ZUNION命令。
当然实际中需要直接获得集合运算结果的情况,除了等待Redis加入相关命令,我们还可以使用MULTI , ZINTERSTORE, ZRANGE, DEL 和EXEC 这5个命令自己实现ZINTER:
MULTI ZINTERSTORE tempKey... ZRANGE tempKey... DEL tempKey... EXEC
二、sort命令
除了使用有序集合外,我们还可以借助Redis提供的SORT。SORT命令可以对列表类型、集合类型和有序集合类型键进行排序,并且可以完成与关系数据库中的连接查询相类似的任务。
比如标有“ruby”标签的文章的ID分别是:“2”,“6”,“12”,“26”。由于在集合类型中所有元素是无序的,所以使用SMEMBERS命令并不能获得有序的结果。为了能够让标签页面下的文章也能按照发布的时间顺序排列(如果不考虑发布后再修改文章发布时间,就是按照文章ID的顺序排列),可以借助SORT命令实现,方法如下所示:
redis>SORT tag:ruby:posts 1) "2" 2) "6" 3) "12" 4) "26"
除了集合类型,SORT命令还可以对列表类型和有序集合类型进行排序:
在对有序集合类型排序时会忽略元素的分数,只对元素自身的值进行排序。例如:
除了可以排列数字外,SORT命令还可以通过ALPHA参数实现按照字典顺序排列非数字元素
SORT命令的 DESC参数可以实现将元素按照从大到小的顺序排列:
redis>SORT tag:ruby:posts DESC 1) "26" 2) "12" 3) "6" 4) "2"
SORT命令还支持LIMIT参数来返回指定范围的结果。用法和SQL 语句一样,LIMIT offset count,表示跳过前offset个元素并获取之后的count个元素。
SORT命令的参数可以组合使用:
redis>SORT tag:ruby:posts DESC LIMIT 1 2 1) "12" 2) "6"
三、BY参数
BY 参数的语法为“BY参考键”。其中参考键可以是字符串类型键或者是散列类型键的某个字段(表示为键名—>字段名)。如果提供了BY参数,SORT命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序。
当参考键名不包含“*”时(即常量键名,与元素值无关),SORT命令将不会执行排序操作,因为Redis认为这种情况是没有意义的(因为所有要比较的值都一样)。例如:
例子中anytext是常量键名(甚至anytext键可以不存在),此时SORT的结果与LRANGE的结果相同,没有执行排序操作。在不需要排序但需要借助SORT命令获得与元素相关联的数据时,常量键名是很有用的。
如果几个元素的参考键值相同,则SORT命令会再比较元素本身的值来决定元素的顺序。
4和1的值都是50,会比较4和1本身的大小决定排序。
当某个元素的参考键不存在时,会默认参考键的值为0:
5的默认值为0,而3的为-10。
参考键虽然支持散列类型,但是“*”只能在“->”符号前面( 即键名部分)才有用,在“->”后( 即字段名部分) 会被当成字段名本身而不会作为占位符被元素的值替換,即常量键名。但是实际运行时会发现一个有趣的结果:
上面提到了当参考键名是常量键名时SORT命令将不会执行排序操作,然而上例中确进行了排序,而且只是对元素本身进行排序。这是因为Redis判断参考键名是不是常量键名的方式是判断参考键名中是否包含“*”,而somekey->somefield :*中包含“*”所以不是常量键名。所以在排序的时候Redis对每个元素都会读取键somekey中的somefield :*字段( “*”不会被替換),本来无值,所以Redis会按照元素本身的大小排列。
四、GET参数
GET参数不影响排序,它的作用是使SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。GET参数的规则和BY参数一样,GET参数也支持字符串类型和散列类型的键,并使用“*”作为占位符。
排序后直接返回对应的文章标题
redis>SORT tag:ruby:posts BY post:*->time DESC GET post:*->title
在一个SORT命令中可以有多个GET参数(而BY参数只能有一个),
redis>SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time有N个GET参数,每个元素返回的结果就有N行。
GET #返回元素本身的值。
五、STORE参数
默认情况下SORT会直接返回排序结果,如果希望保存排序结果,可以使用STORE参数。如希望把结果保存到sort.result键中:
redis>SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET # STORE sort.result
保存后的键的类型为列表类型,如果键已经存在则会覆盖它。加上STORE参数后SORT命令的返回值为结果的个数。
六、性能优化
SORT是Redis中最强大最复杂的命令之一,如果使用不好很容易成为性能瓶颈。SORT命令的时间复杂度是O(n+mlogm ),其中n 表示要排序的列表(集合或有序集合)中的元素个数,m表示要返回的元素个数。当n 较大的时候SORT命令的性能相对较低,并且Redis在排序前会建立一个长度为n(有一个例外是当键类型为有序集合且参考键为常量键名时容器大小为m 而不是n)的容器来存储待排序的元素,虽然是一个临时的过程,但如果同时进行较多的大数据量排序操作则会严重影响性能。
所以开发中使用SORT命令时需要注意以下几点。
(1)尽可能减少待排序键中元素的数量(使n 尽可能小)。
(2)使用LIMIT参数只获取需要的数据(使m 尽可能小)。
(3)如果要排序的数据数量较大,尽可能使用STORE参数将结果缓存。