關(guān)系型數(shù)據(jù)庫的事務(wù)具備:原子性廉白、隔離性、持久性乖寒、一致性猴蹂。在Redis中也同樣擁有事務(wù)的概念,本文主要介紹Redis事務(wù)的使用楣嘁、特性磅轻。
如何使用Redis事務(wù)
Redis中與事務(wù)相關(guān)的命令有5個,分別是MULTI
EXEC
DISCARD
WATCH
UNWATCH
逐虚。
MULTI
命令用于開啟事務(wù):
MULTI => "OK"
開啟事務(wù)后聋溜,可以開始執(zhí)行操作:
SET mykey 1 => "QUEUED"
INCR mykey => "QUEUED"
INCR mykey => "QUEUED"
能看到開啟事務(wù)后命令的返回都是"QUEUED",Redis Server收到這些命令后將他們保存在隊(duì)列中叭爱,只有收到EXEC
命令才會真正執(zhí)行:
EXEC
=> "1) OK"
=> "2) (integer) 2"
=> "3) (integer) 3"
EXEC
命令的返回結(jié)果是一個Array結(jié)構(gòu)的數(shù)據(jù)勤婚,其中每個元素是事務(wù)中執(zhí)行的命令的結(jié)果。?
當(dāng)執(zhí)行了MULTI
命令和一些操作后涤伐,想放棄當(dāng)前事務(wù),可以使用DISCARD
命令缨称,DISCARD
命令會清空事務(wù)命令隊(duì)列凝果,并退出事務(wù)。
WATCH
命令使得Redis事務(wù)具有compare-and-set語義睦尽,WATCH
命令用法:
Client1 > WATCH mykey => "OK"
Client1 > MULTI => "OK"
Client1 > INCR mykey => "OK"
Client2 > INCR mykey => "(integer) 4"
Client1 > EXEC => "nil"
Client1對mykey使用WATCH
命令后開啟事務(wù)器净,在執(zhí)行EXEC
命令前,Client2修改了mykey的值当凡,Client1執(zhí)行EXEC
命令會失敗山害。
UNWATCH
用于解除WATCH
命令的限制,UNWATCH
沒有參數(shù)沿量,只能解除所有的watch浪慌。
Redis事務(wù)的特性
Redis事務(wù)提供了以下兩點(diǎn)保證:
- 所有事務(wù)內(nèi)的命令都會按照順序執(zhí)行,并且事務(wù)會被看作是一個單獨(dú)隔離的操作朴则,執(zhí)行中不會被其他客戶端影響权纤。
- 事務(wù)內(nèi)所有的命令要么都執(zhí)行,要么都不被執(zhí)行乌妒。
Redis的持久性與使用的持久化機(jī)制有關(guān)汹想,會放在RDB和AOF處再研究。
這兩點(diǎn)保證包含了隔離性與原子性語義撤蚊。但Redis的原子性與關(guān)系數(shù)據(jù)庫原子性還是有一些差別的古掏,差別源于Redis對事務(wù)中錯誤的處理方式。在Redis事務(wù)中會出現(xiàn)兩類錯誤:
- 命令語法錯誤侦啸、參數(shù)錯誤等槽唾,命令沒有進(jìn)入事務(wù)的命令隊(duì)列丧枪,直接就返回錯誤。
- 命令進(jìn)入事務(wù)的命令隊(duì)列夏漱,但在執(zhí)行
EXEC
后出錯豪诲,例如對錯誤的數(shù)據(jù)類型使用了不支持的操作。
客戶端可以在事務(wù)提交前就感知到第一類錯誤挂绰,一般客戶端在收到第一類錯誤時(shí)會discard當(dāng)前事務(wù)屎篱。
Redis2.6.5之前的版本在執(zhí)行
EXEC
時(shí)會忽略第一類錯誤,執(zhí)行隊(duì)列中的其他命令葵蒂。
Redis2.6.5之后的版本會記錄第一類錯誤交播,當(dāng)執(zhí)行EXEC
命令時(shí)返回:
(error) EXECABORT Transaction discarded because of previous errors.
對于第二類錯誤,Redis會忽略失敗的命令践付,繼續(xù)執(zhí)行隊(duì)列中的其他命令秦士。即一個事務(wù)內(nèi)的操作,可能會出現(xiàn)一部分成功永高,一部分失敗的情況隧土。
對于Redis不支持回滾官方的解釋是:
- Redis命令失敗的情況只有語法錯誤和對某一數(shù)據(jù)類型使用了錯誤的命令。這意味著命令執(zhí)行失敗是因?yàn)槌绦蝈e誤命爬,這些錯誤應(yīng)該在開發(fā)中就被排除曹傀,而不是留到線上靠回滾挽救數(shù)據(jù)。
- Redis之所以簡單饲宛、快速是因?yàn)椴恍枰С只貪L皆愉。
Jedis中使用事務(wù)
public class JedisTransactions {
public static void main(String[] args) {
Jedis jedis = null;
Pipeline pipeline = null;
try{
jedis = new Jedis("localhost", 6379);
//事務(wù)是可以跟pipelining一起使用的
pipeline = jedis.pipelined();
//首先設(shè)置value的值為100
pipeline.set("value", "100");
//設(shè)置watch
pipeline.watch("value");
//開啟事務(wù)
pipeline.multi();
//遞增value 10
pipeline.incrBy("value", 10);
//執(zhí)行錯誤的操作lpush
pipeline.lpush("value", "error");
//再次遞增value 10
pipeline.incrBy("value", 10);
//執(zhí)行exec命令,獲取"未來"的返回結(jié)果
Response<List<Object>> listResponse = pipeline.exec();
//執(zhí)行pipelining
pipeline.sync();
List<Object> result = listResponse.get();
if(result != null && result.size() > 0){
for(Object o : result){
System.out.println(o.toString());
}
}
//雖然事務(wù)中第二個操作失敗了,但不影響value的值
System.out.println("\nvalue is " + jedis.get("value"));
}catch (Exception e){
e.printStackTrace();
}finally {
if(jedis != null){
jedis.close();
}
}
}
}
=>
110
redis.clients.jedis.exceptions.JedisDataException: WRONGTYPE Operation against a key holding the wrong kind of value
120
value is 120
事務(wù)可以與pipelining一起使用,可以看到第二條命令LPUSH
因?yàn)閿?shù)據(jù)類型錯誤失敗了艇抠,但并沒有影響第一條幕庐、第三條遞增命令。