一、Redis基本的事务操作
首先声明:
- redis的单条命令是保证原子性的(回想一下setnx k1 v1 k5 v5命令如果k1已经存在,那么k5也会设置失败)
- 但是redis的事务不保证原子性!见下面“1.2 某条命令有错怎么办?”
- redis的事务也没有隔离性!mysql的事务必须具有隔离性是因为可能有多个线程操作数据,但是redis是单线程的,所以根本不需要隔离。相应的,redis也就没有脏读、幻读等一系列由隔离性引发的问题。
1.1 开始事务、执行事务、放弃事务
注意:每次exec执行完事务后,这个事务就消失了。下次要再输入multi命令去创建新事务
discard命令放弃事务
1.2 事务中某条命令有错怎么办?
- 编译型异常”,也就是事务中某条命令语法有错,比如把“hget”写成了"ghet",那么整个事务中的所有命令都不会被执行,这里是具有原子性特点的。
- “运行时异常”,语法没错,但是命令执行出现问题。比如事务中某条命令是“geodist china:city beijing tianjin”,但是china:city这个集合中压根没有天津这个城市。那么这一条命令执行失败,但是其他命令会执行成功。所以redis的事务不具有原子性。
注意看下面的演示
二、通过redisTemplate操作再次理解事务
@Test
public void test01(){
// 1.从数据库中取出数据,转为java对象
Blog blog1 = blogMapper.selectById(2);
Blog blog2 = blogMapper.selectById(3);
// 2.java对象-->json
String blogString1 = JSON.toJSONString(blog1);
String blogString2 = JSON.toJSONString(blog2);
redisTemplate.multi();
try{
redisTemplate.opsForValue().set("blog1",blogString1);
redisTemplate.opsForValue().set("blog2",blogString2);
int i=1/0; //运行时异常,但是上面两条命令会执行成功
redisTemplate.exec();
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println(redisTemplate.opsForValue().get("blog1"));
System.out.println(redisTemplate.opsForValue().get("blog2"));
}
}
三、Redis用watch实现乐观锁
mysql用version版本号实现乐观锁,我们的redis用watch监控实现乐观锁。
先看一下加watch监控后,事务成功的情况下:
cj:11>set money 1000
"OK"
cj:11>watch money # 给money开启监控,记录money当前的值
"OK"
cj:11>multi
"OK"
cj:11>incrby money 100
"QUEUED"
cj:11>incrby money 200
"QUEUED"
cj:11>exec # 事务执行前money没有被别的线程修改过(money值没变),那么事务执行成功!
1) "OK"
2) "1100"
3) "OK"
4) "1300"
5) "OK"
再看一下,事务执行前,命令里的值被其他线程修改的情况(入门篇里说过,redis的单线程指的是给一个用户网络连接请求开启一个单线程,这个单线程不会开子线程。但是当多个网络请求的时候,自然是多个线程在同时进行,当然这不属于redis级别的多线程,而是java程序级别的多线程)
这个事务就会失败
失败后再怎么弄勒?毕竟我们还是要花500块钱的。当然是再来一遍了!但是要先解除监控,再加上监控
cj:11>unwatch # 解除监控
"OK"
cj:11>watch money # 再次加上监控,获取money最新的值
"OK"
cj:11>multi
"OK"
cj:11>decrby money 500
"QUEUED"
cj:11>exec
1) "OK"
2) "2500"
3) "OK"