SORT命令
除了使用有序集合外,我们还可以借助Redis提供的SORT命令来实现排序。
SORT命令可以对列表类型、集合类型和有序集合类型键进行排序,并且可以完成与关系数据库中的连接查询相类似的任务。
redis> SORT tag:ruby:posts
1)"2"
2)"6"
3)"12"
4)"26"
redis> LPUSH mylist 4 2 6 1 3 7
(integer)6
redis> SORT mylist
1)"1"
2)"2"
3)"3"
4)"4"
5)"6"
6)"7"
在对有序集合类型进行排序时会忽略元素的分数,只针对元素自身的值进行排序。
redis> ZADD myzset 50 2 40 3 20 1 60 5
(integer)4
redis> SORT myzset
1)"1"
2)"2"
3)"3"
4)"5"
除了可以排列数字外,SORT命令还可以通过ALPHA参数实现按照字典顺序排列非数字元素:
redis> LPUSH mylistalpha a c e d B C A
(integer)7
redis> SORT mylistalpha
(error)ERR One or more scores can't be converted into double
redis> SORT mylistalpha ALPHA
1)"A"
2)"B"
3)"C"
4)"a"
5)"c"
6)"d"
7)"e"
必须加ALPHA参数,不然SORT命令会尝试将所有元素转换为双精度浮点数来比较。
SORT命令默认按从小到大的顺序排列,DESC参数可以实现将元素按照从大到小的顺序排列。
redis> SORT tag:ruby:posts DESC
1)"26"
2)"12"
3)"6"
4)"4"
SORT命令还支持LIMIT参数来返回指定范围的结果。用法和SQL语句一样,LIMIT offset count,表示跳过前offset个元素并获取之后的count个元素。
redis> SORT tag:ruby:posts DESC LIMIT 1 2
1)"12"
2)"6"
BY参数
BY参数的语法为“BY 参考键”。其中参考键可以是字符串类型键或者是散列类型键的某个字段(表示为键名->字段名)。如果提供了BY参数,SORT命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素进行排序。如:
redis> SORT tag:ruby:posts BY post:*->time DESC
- "12"
- "26"
- "6"
- "2"
在上例中SORT命令会读取post:2、post:6、post:12、post:26几个散列键中的time字段的值并以此决定tag:ruby:posts键中各个文章ID的顺序。
除了散列类型键之外,参考键还可以是字符串类型,比如:
redis> LPUSH sortbylist 2 1 3
(integer)3
redis> SET itemscore:1 50
OK
redis> SET itemscore:2 100
OK
redis> SET itemscore:3 -10
OK
redis> SORT sortbylist BY itemscore:* DESC
- "2"
- "1"
- "3"
当参考键名不包含“*”时(即常量键名,与元素值无关),SORT命令将不会执行排序操作,因为Redis认为这种情况是没有意义的(因为所有比较的值都一样)。如:
redis> SORT sortbylist anytext
- "3"
- "1"
- "2"
例子中anytext是常量键名(甚至anytext键可以不存在),此时SORT的结果与LRANGE的结果相同,没有执行排序操作。在不需要排序但是需要借助SORT命令获得与元素相关联的数据时,常量键名是很有用的。
如果几个元素的参考键相同,则SORT命令会再比较元素本身的值来决定元素的顺序。如:
redis> LPUSH sortbylist 4
(integer)4
redis> SET itemscore:4 50
OK
redis> SORT sortbylist BY itemscore:* DESC
- "2"
- "4"
- "1"
- "3"
当某个元素的参考键不存在时,会默认参考键的值为0:
redis> LPUSH sortbylist 5
(integer)5
redis> SORT sortbylist BY itemscore:* DESC
- "2"
- "4"
- "1"
- "5"
- "3"
参考键虽然支持散列类型,但是“*”只能在“->”符号前面(即键名部分)才有用,在“->”后(即字段名部分)会被当成字段名本身而不会作为占位符被元素的值替换,即常量键名。但是实际运行时会发现一个有趣的结果:
redis> SORT sortbylist BY somekey->somefield:*
- "1"
- "2"
- "3"
- "4"
- "5"
上面提到了当参考键名是 常量键名时SORT命令将不会执行排序操作,然而上例中却进行了排序,而且只是对元素本身进行排序。这是因为Redis判断参考键名是不是常量键名的方式是判断参考键名中是否包含"*",而somekey->somefield:*中包含了“*”所以不是常量键名。所以在排序的时候Redis对每个元素都会读取键somekey中的somefield:*字段(*不会被替换),无论能否获得其值,每个元素的参考键值是相同的,所以Redis会按照元素本身的大小排列。
GET参数
GET参数不影响排序,它的作用是使SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。GET参数的规则和BY参数一样,GET参数也支持字符串类型和散列类型的键,并使用“*”作为占位符。如:
redis> SORT tag:ruby:posts BY post:*->time DESCGET post:*->title
- "Windows 8 app designs"
- "love"
- "Uses for Linux"
- "The Ruby' nature"
在一个SORT命令可以使用多个GET参数(而BY参数只能有一个),GET #是返回元素本身的值。如:
redis> SORT tag:ruby:posts BY post:*->time DESCGET post:*->title GET post:*->time GET #
- "Windows 8 app designs"
- "2123132121"
- "12"
- "love"
- "2021212111"
- "26"
- "Uses for Linux"
- "1902012212"
- "6"
- "The Ruby' nature"
- "1021212121"
- "2"
STORE参数
默认情况下SORT会直接返回排序结果,如果希望保存排序结果,可以使用STORE参数。
redis> SORT tag:ruby:posts BY post:*->time DESCGET post:*->title GET post:*->time GET # STORE sort.result
(integer)12
redis> LRANGE sort.result 0 -1
- "Windows 8 app designs"
- "2123132121"
- "12"
- "love"
- "2021212111"
- "26"
- "Uses for Linux"
- "1902012212"
- "6"
- "The Ruby' nature"
- "1021212121"
- "2"
保存后的键的类型为列表类型,如果键已经存在则会覆盖它。STORE参数常用来结合EXPIRE命令缓存排序结果。
性能优化
SORT是Redis中最强大最复杂命令之一。时间复杂度为O(n+mlogm),其中,n为要排序的列表中的元素个数,m表示要返回的元素个数。
开发中需要注意:
1)尽可能减少待排序键中元素的数量(使n尽可能小)。
2)使用LIMIT参数只获取需要的数据(使m尽可能小)。
3)如果要排序的数据数量较大,尽可能使用STORE参数将结果缓存。