Redis作为数据库,当然也有事务。但它的事务与Mysql等关系型数据库的事务不同。
Redis中的事务同Redis的执行命令一样,都是最小执行单元,这是与Mysql的第一点区别。为什么?因为Mysql默认开启了自动事务提交,每一条Sql语句都会被当做一个事务提交,而关闭Mysql事务后需要使用commit指令提交后才开启下一个事务。但Redis中的指令就是一个最小执行单元,这点可以认为Redis默认开启自动事务提交,且无法关闭。
Redis开启事务使用指令Multi指令开启事务,同时遇到EXEC指令事务结束。演示如下:
第二点区别,Redis事务中提交的命令不会马上执行,会被加载到队列中,然后遇到exec指令时再执行,并返回所有指令的执行结果,但Mysql的事务会执行sql语句。
看看Redis事务的另一个演示:
可以看到使用lpush指令对String类型的Key进行操作出错了,但是两条set指令却执行了。所以第三点不同,Redis并没有像Mysql那样有事务回滚,但的确Redis的事务中的指令都执行了,只不过有的执行出错了,但出错了也会继续执行后面的指令,回想Mysql的事务,如果有一条sql执行出错,后面的sql都不会执行,而且还会数据回滚,这算是Redis与Mysql事务最大的区别。 所以使用Redis的事务出错需要开发人员来回滚数据。
WATCH监控指令,这个指令监控的内容如果有变化,那么指令之后的事务不会执行,演示如下:
可以看到watch指令监控username,username的值发生了变化,所以后面的事务没有执行。
可以看到两点
1) 使用了watch指令监控某个key,只要值发生改变,后面的事务不会执行,无论事务中是否用到了被监控的Key
2) 只有watch指令后的第一个事务不会被执行,这是因为执行了EXEC等于对所有被监控的键取消了监控。
当然你也可以使用UnWatch指令取消对所有键的监控,这个指令与EXEC不同在于,EXEC不能单独使用,要与Multi配合。
如果使用expire指令给键设置了过期时间,到了时间自动删除这种不会被watch指令认为改变了值,演示如下:
当然使用del指令删除Key会被认为值改变了。
ps:expire指令设置时间后,如果使用SET等SET指令更改了value的值,过期时间将被清除。LPUSH等操作不算。
注意:使用expire必须在watch指令前才不算更改,请看下面的演示:
如果使用watch监控不存在的Key时,watch指令后面的事务是会执行的。
Redis的Java SDK使用事务
测试代码如下:
package org.yamikaze.redis.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisDataException;
import static org.junit.Assert.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Redis的事务处理
* @author yamikaze
*/
public class TransactionTest {
private Jedis jedis;
private String key1 = "key1";
private String key2 = "key2";
private String key3 = "key3";
@Before
public void setUp() {
jedis = MyJedisFactory.defaultJedis();
}
/**
* 当事务中的指令没有错误时
*/
@Test
public void testTransactionWithoutError() {
Transaction transaction = jedis.multi();
transaction.set(key1, key1);
transaction.set(key2, key2);
transaction.set(key3, key3);
List<Object> objs = transaction.exec();
assertTrue(objs.size() == 3);
for(Object obj : objs) {
//这儿由于set指令返回的是Ok,且为String
//如果事务中Lrange这种指令,
if(obj instanceof String) {
assertTrue("OK".equals(obj));
}
}
}
/**
* 当事务中的指令有错误时
*/
@Test
public void testTransactionWithError() {
jedis.set(key1, key2);
Transaction transaction = jedis.multi();
transaction.set(key1, key1);
//使用lpush指令操纵String类型
transaction.lpush(key1, key2, key3);
List<Object> objs = transaction.exec();
assertTrue(objs.size() == 2);
for(Object obj : objs) {
/**
* 有错时返回的对象是JedisDataException
* 这儿的错误是
* WRONGTYPE Operation against a key holding the wrong kind of value
*/
if(obj instanceof JedisDataException) {
System.out.println(((JedisDataException) obj).getMessage());
}
if(obj instanceof String) {
System.out.println(obj);
}
}
/**
* 但事务中的set指令执行了,没有回滚
*/
String result = jedis.get(key1);
assertTrue(key1.equals(result));
}
/**
* 使用WATCH指令监控,事务不会执行
*/
@Test
public void testTransactionWithWatch() {
jedis.set(key1, key2);
//监控key1
jedis.watch(key1);
//改变key1的值
jedis.set(key1, key1);
//开启事务
Transaction transaction = jedis.multi();
transaction.set(key1, key3);
transaction.set(key1, key2);
List<Object> objs = transaction.exec();
//这种watch的情况下没有执行事务所以返回结果为0
assertTrue(objs.size() == 0);
String result = jedis.get(key1);
assertTrue(key1.equals(result));
}
/**
* Watch指令后的第二个事务会执行
*/
@Test
public void testTransactionWithWatch02() {
jedis.set(key1, key2);
//监控key1
jedis.watch(key1);
//改变key1的值
jedis.set(key1, key1);
//开启事务,这是第一个事务
Transaction transaction = jedis.multi();
transaction.set(key1, key3);
transaction.set(key1, key2);
List<Object> objs = transaction.exec();
//这种watch的情况下没有执行事务所以返回结果为0
assertTrue(objs.size() == 0);
String result = jedis.get(key1);
assertTrue(key1.equals(result));
//第二个事务
jedis.multi();
transaction.set(key1, key3);
transaction.set(key1, key2);
objs = transaction.exec();
assertTrue(objs.size() == 2);
result = jedis.get(key1);
assertTrue(key2.equals(result));
}
/**
* expire的情况
*/
@Test
public void testTransactionWithExpire() throws InterruptedException{
jedis.set(key1, key2);
//expire语句不能放到watch语句后面,否则会被视作改变了值
jedis.expire(key1, 10);
//监控key1
jedis.watch(key1);
TimeUnit.SECONDS.sleep(11);
//开启事务
Transaction transaction = jedis.multi();
transaction.set(key1, key3);
transaction.set(key1, key2);
List<Object> objs = transaction.exec();
assertTrue(objs.size() == 2);
String result = jedis.get(key1);
assertTrue(key2.equals(result));
}
/**
* del的情况
*/
@Test
public void testTransactionWithDel() {
jedis.lpush(key1, "1", "2");
jedis.watch(key1);
//删除被监控的键
jedis.del(key1);
jedis.set(key2, key3);
Transaction transaction = jedis.multi();
transaction.set(key2, key2);
transaction.exec();
//事务中的set语句没有执行,key2的值还是key3
String result = jedis.get(key2);
assertTrue(key3.equals(result));
}
@After
public void tearDown() {
if(jedis != null) {
jedis.del(key1, key2, key3);
jedis.close();
}
}
}
《Redis入门指南》