集合类型提供了强大的集合操作命令,但是如果需要排序就要用到有序集合类型。Redis的作者在设计Redis的命令时考虑到了不同数据类型的使用场景,对于不常用到的或者在不损失过多性能的前提下可以使用现有命令来实现的功能,Redis就不会单独提供命令来实现。这一原则使得Redis在拥有强大功能的同时保持着相对精简的命令。
有序集合常见的使用场景是大数据排序,如游戏的玩家排行榜,所以很少会需要获得键中的全部数据。同样Redis认为开发者在做完交集、并集运算后不需要直接获得全部结果,而是会希望将结果存入新的键中以便后续处理。这解释了为什么有序集合只有ZINTERSTORE和ZUNIONSTORE命令而没有ZINTER和ZUNION命令。
当然,我们可以使用MULTI,ZINTERSTORE,ZRANGE,DEL和EXEC这5个命令自己实现ZINTER:
MULTI
ZINTERSTORE tempkey ...
ZRANGE tempkey ...
DEL tempkey
EXEC
除了使用有序集合,还可以使用Redis提供的SORT命令来实现排序。 SORT命令可以对列表类型、集合类型和有序集合类型键进行排序,并且可以完成与关系数据库中的连接查询相类似的任务。
使用SORT命令如下:
127.0.0.1:6379> sadd tag:java:posts 6 2 26 12
(integer) 4
127.0.0.1:6379> sort tag:java:posts
1) "2"
2) "6"
3) "12"
4) "26"
除了集合类型,SORT命令还可以对列表类型和有序集合类型进行排序:
127.0.0.1:6379> lpush mylist 4 2 6 1 3 7
(integer) 6
127.0.0.1:6379> sort mylist
1) "1"
2) "2"
3) "3"
4) "4"
5) "6"
6) "7"
在对有序集合类型排序时会忽略元素的分数,只针对自身的值进行排序。如:
127.0.0.1:6379> zadd myset 50 2 40 3 20 1 60 5
(integer) 4
127.0.0.1:6379> sort myset
1) "1"
2) "2"
3) "3"
4) "5"
除了可以排列数字外,SORT命令还可以通过ALPHA参数实现按照字典顺序排列非数字元素,如:
127.0.0.1:6379> lpush la a c e d B C A
(integer) 7
127.0.0.1:6379> sort la
(error) ERR One or more scores can't be converted into double
127.0.0.1:6379> sort la alpha
1) "A"
2) "B"
3) "C"
4) "a"
5) "c"
6) "d"
7) "e"
从以上代码看出,如果没有加ALPHA参数的话,SORT命令会尝试将所有元素转换成双精度浮点数来比较,如果无法转换则会提示错误。
SORT命令的DESC参数可以实现将元素按照从大到小的顺序排列:
127.0.0.1:6379> sort tag:java:posts desc
1) "26"
2) "12"
3) "6"
4) "2"
SORT命令还支持LIMIT参数来返回指定范围的结果。用法和SQL语句一样,LIMIT offset count
,表示跳过前offset个元素并获取之后的count个元素。如:
127.0.0.1:6379> sort tag:java:posts desc limit 1 2
1) "12"
2) "6"
BY 参考键
BY参数的语法为BY 参考键
。其中参考键可以是字符串类型键或者是散列类型建的某个字段(表示为键名->字段名)。如果提供了BY参数,SORT命令将不再依据元素自身的值进行排序,而是对每个元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序。如:
127.0.0.1:6379> hset post:2 time 1352619200
(integer) 1
127.0.0.1:6379> hset post:6 time 1352619600
(integer) 1
127.0.0.1:6379> hset post:12 time 1352620100
(integer) 1
127.0.0.1:6379> hset post:26 time 1352620000
(integer) 1
127.0.0.1:6379> sort tag:java:posts by post:*->time desc
1) "12"
2) "26"
3) "6"
4) "2"
在以上代码中,SORT命令会读取post:2、post:6、post:12、post:26几个散列键中的time字段的值并以此决定tag:java:posts键中各个文章ID的顺序。
除了散列类型外,参考键还可以是字符串类型,如:
127.0.0.1:6379> lpush sortlist 2 1 3
(integer) 3
127.0.0.1:6379> set is:1 50
OK
127.0.0.1:6379> set is:2 100
OK
127.0.0.1:6379> set is:3 -10
OK
127.0.0.1:6379> sort sortlist by is:* desc
1) "2"
2) "1"
3) "3"
当参考键名不包含“*”时(即常量键名,与元素值无关),SORT命令将不会执行排序操作,因为Redis认为这种情况是没有意义的(因为所有要比较的值都一样)。如:
127.0.0.1:6379> sort sortlist by anytest
1) "3"
2) "1"
3) "2"
anytest是常量键名(甚至是不存在),此时SORT的结果与LRANGE的结果相同,没有执行排序操作。
如果几个元素的参考键值相同,则SORT命令会再比较元素本身的值来决定元素的顺序。如:
127.0.0.1:6379> lpush sortlist 4
(integer) 4
127.0.0.1:6379> set is:4 50
OK
127.0.0.1:6379> sort sortlist by is:* desc
1) "2"
2) "4"
3) "1"
4) "3"
以上代码中,元素4的参考键is:4的值和元素1的参考键is:1的值都是50,所以sort命令会再比较4和1元素本身的大小来决定二者的排序。
当某个元素的参考键不存在时,会默认参考键的值为0:
127.0.0.1:6379> lpush sortlist 5
(integer) 5
127.0.0.1:6379> sort sortlist by is:* desc
1) "2"
2) "4"
3) "1"
4) "5"
5) "3"
上面代码中5排在了3的前面,是因为5的参考键不存在,所以默认为0,而3的参考键为-10。
GET参数不影响排序,它的作用是使SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。GET参数的规则和BY参数一样,GET参数也支持字符串类型和散列类型的键,并使用“*”作为占位符。如:
127.0.0.1:6379> hset post:2 title aaaaaa
(integer) 1
127.0.0.1:6379> hset post:6 title bbbbbb
(integer) 1
127.0.0.1:6379> hset post:12 title cccccc
(integer) 1
127.0.0.1:6379> hset post:26 title dddddd
(integer) 1
127.0.0.1:6379> sort tag:java:posts by post:*->time desc get post:*->title
1) "cccccc"
2) "dddddd"
3) "bbbbbb"
4) "aaaaaa"
在一个SORT命令中可以有多个GET参数(而BY参数只能有一个),如:
127.0.0.1:6379> sort tag:java:posts by post:*->time desc get post:*->title get post:*->time
1) "cccccc"
2) "1352620100"
3) "dddddd"
4) "1352620000"
5) "bbbbbb"
6) "1352619600"
7) "aaaaaa"
8) "1352619200"
可见有N个GET参数,每个元素返回的结果就有N行。如要返回元素本身的值,可以使用 GET #
。如:
127.0.0.1:6379> sort tag:java:posts by post:*->time desc get post:*->title get post:*->time get #
1) "cccccc"
2) "1352620100"
3) "12"
4) "dddddd"
5) "1352620000"
6) "26"
7) "bbbbbb"
8) "1352619600"
9) "6"
10) "aaaaaa"
11) "1352619200"
12) "2"
STORE result
默认情况下 SORT会直接返回排序结果,如果希望保存排序结果,可以使用STORE参数。如把结果保存到sort,result中:
127.0.0.1:6379> sort tag:java:posts by post:*->time desc get post:*->title get post:*->time get # store sort.result
(integer) 12
127.0.0.1:6379> lrange sort.result 0 -1
1) "cccccc"
2) "1352620100"
3) "12"
4) "dddddd"
5) "1352620000"
6) "26"
7) "bbbbbb"
8) "1352619600"
9) "6"
10) "aaaaaa"
11) "1352619200"
12) "2"
保存后的键的类型为列表类型,如果键已经存在则会覆盖它。加上STORE参数后SORT命令的返回值为结果的个数。
SORT是Redis中最强大最复杂的命令之一,如果使用不好容易成为性能瓶颈。SORT命令的时间复杂度是 O(n+mlog(m)),其中n表示要排序的列表(集合或有序集合)中的元素个数,m表示要返回的元素个数。当n较大的时候SORT命令的性能相对较低,并且Redis在排序前会建立一个长度为n的容器来存储待排序的元素,虽然是一个临时的过程,但如果同时进行较多的大数据量排序操作则会严重影响性能。
开发中使用SORT命令时需要注意一下几点:
(1)尽可能减少待排序键中元素的数量(使N尽可能小)
(2)使用LIMIT参数只获取需要的数据(使M尽可能小)
(3)如果要排序的数据数量较大,尽可能使用SORT参数将结果缓存。
author:su1573
鄙人记录生活点滴,学习并分享,请多指教!!!
如需交流,请联系 [email protected],鄙人看到会及时回复