redis並不直接支援索引,需要通過自己來維護。
對於非範圍唯一索引,我們可以簡單的把索引也存成KV對,v儲存主key即可,
而範圍檢索,或者非唯一索引,則要使用redis 的 zset來實現。
舉例一個傳統的使用者系統例子
uid 使用者id
name 使用者名稱
credit 使用者積分
type 類型
可以直接放到一個hashset中
hmset usr:1 uid 1 name aaa credit 10 type 0
hmset usr:2 uid 2 name bbb credit 20 type 1
通過uid檢索很快,但是如果要查詢type=1的使用者,則只能全掃描。
在關聯式資料庫中,我們可以簡單在type上建立索引
select * from usr where type=1
這樣的SQL就可以高效執行了。redis中需要我們自己再維護一個zset
zadd usr.index.type 0 0:1
zadd usr.index.type 0 1:2
注意,所有權重都設定成0,這樣可以直接按值檢索,然後可以通過
zrangebylex usr.index.type [1: (1;
(這裡有一個小技巧,字元:後面就是;,所以>=1:且<1;的就是以1:開頭的
比 [1: [1:\xff 這種形式看起來簡潔一些。)
顯然得到的結果是1:2,還要自己處理一下獲得使用者id=2,然後就可以通過usr:2再擷取其它欄位了。
這裡如果更新很少,那麼也可以考慮直接將其它欄位接到1:2之後,這樣一次可獲得,等於是SQL中的帶冗餘欄位的索引。
為了保證索引一致,需要做很多額外工作,
新增比較簡單:
hmset usr:3 uid 3 name ccc credit 30 type 3
zadd usr.index.type 0 3:3
為了保持一致性,最好也用指令碼來實現。而修改的話,有些複雜,
必須先讀出原值,還是使用lua吧
local usr=KEYS[1] --usr:1 所有用到的key都要用參數提供
local index=KEYS[2] --usr.index.type
local uid=ARGV[1] --1
local newtype=ARGV[2] --9
--儲存原始值
local oldtype=redis.call('hget',usr,'type')
--type修改為新值
redis.call('hset',usr,'type',newtype)
--刪除老的索引
redis.call('zrem',index,oldtype ..":" .. uid)
--增加新索引
redis.call('zadd',index,0, newtype .. ":" .. uid)
為了保持一致性,所有修改type的地方都要使用這個lua指令碼,
刪除一條usr記錄時也要讀出原始的type,同時刪除索引,指令碼類似。
顯然,SQL資料庫之所以索引很好用,無非是他幫你內部實現了,其實現原理也是類似的。
同樣redis也可以建立多個索引,甚至複合索引,比如:
zadd usr.index.type_credit 0 3:20:3
type:credit:uid的格式,這裡需要注意的是,參與索引的數字欄位必須左補0對齊(補幾個則根據欄位長度),比如
003:00020:3
除非你不需要範圍檢索,只需要精確檢索,比如:
zrangebylex usr.index.type_credit [3:20: (3:20;
只需要檢索type=。 and credit=。的使用者,否則你就必須用
zrangebylex usr.index.type_credit [3:00020: (3:00030;
檢索type=。 and credit between 。 and 。
顯然redis使用自建索引還是比較費事的,是否真的需要。
是否可直接使用SQL資料庫而讓redis緩衝查詢結果。 需要架構師仔細權衡。
但是的確掌握建立索引技巧後,redis又變強大許多。