Redis从入门到精通3:Redis中的事务
本节来介绍一下Redis中的事务。
1.Oracle中的事务
Oracle中的事务,就是将一组DML操作打包执行,特点是要么都执行成功,要么一个也不执行(失败自动回滚)。本质首是先将这一组DML操作写入日志文件,如果写日志成功,就算该事务提交成功,然后就开始执行这组DML操作。
Oracle中的事务具有ACID特性:即原子性、一致性、独立性、持久性。
1.1 Atomicity(原子性)
原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。
1.2 Consistency(一致性)
一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务开始前是一个一致状态,事务结束后是另一个一致状态,事务将数据库从一个一致状态转移到另一个一致状态。
1.3 Isolation(独立性)
从字面上来说,独立性是其中最难理解的一点,但如果结合Oracle中的undo,也就不难理解了。所谓的独立性就是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务还未提交,它所访问的数据就不受未提交事务的影响。换句话说,一个事务的影响在该事务提交前对其它事务是不可见的。
注意:这里的Isolation跟隔离级别(Isolation Level)是无关的。
1.4 Durability(持久性)
持久性也不难理解,是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
2.Redis中的事务
Redis中的事务不是真正的事务:本质是将一组操作放入一个队列中一起执行。
Redis只支持简单的事务,复杂的事务就不行了。
对比Oracle和Redis事务:

3.示例
示例1:从Tom账户上转100元给Mike。
命令行:
127.0.0.1:6379> set tom 1000
OK
127.0.0.1:6379> set mike 1000
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby tom 100
QUEUED
127.0.0.1:6379> incrby mike 100
QUEUED
127.0.0.1:6379> exec
- (integer) 900
- (integer) 1100
Java程序:需要将jedis-2.1.0.jar包含到工程的Path中
public class TestRedis {
@Test
public void testTransaction(){
//创建一个Redis的客户端
Jedis client = new Jedis("192.168.126.110",6379);
//创建一个事务
Transaction tc = null;
try{
//开启事务
tc = client.multi();
//转账
tc.decrby("tom",100);
tc.incrby("mike",100);
//提交事务
tc.exec();
}catch(Execption ex){
ex.printStackTrace();
if(tc != null){
tc.discard();
}
}finally{
//关闭客户端
clien.disconnect();
}
}
}
示例2:买票(单用户场景和多用户场景)。
命令行(单用户):
127.0.0.1:6379> set tom 1000
OK
127.0.0.1:6379> set ticket 1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby tom 100
QUEUED
127.0.0.1:6379> decrby ticket 1
QUEUED
127.0.0.1:6379> exec
- (integer) 900
- (integer) 0
命令行(多用户):
用户1:Tom
127.0.0.1:6379> set tom 1000
OK
127.0.0.1:6379> set ticket 1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby tom 100
QUEUED
127.0.0.1:6379> decrby ticket 1
QUEUED
用户2:Mike
127.0.0.1:6379> decrby ticket 1
(integer) 0
用户1:Tom
127.0.0.1:6379> exec
- (integer) 900
- (integer) -1
事务可以执行成功,但是不符合逻辑。因为票数不能为负数。
Java程序:买票。
public class TestRedis {
@Test
public void testTransaction(){
//创建一个Redis的客户端
Jedis client = new Jedis("192.168.126.110",6379);
//创建一个事务
Transaction tc = null;
try{
//开启事务
tc = client.multi();
//买票
tc.decrby("tom",100);
tc.incrby("ticket",1);
//提交事务
tc.exec();
}catch(Execption ex){
ex.printStackTrace();
if(tc != null){
tc.discard();
}
}finally{
//关闭客户端
clien.disconnect();
}
}
}
4.Redis的锁机制
Redis的事务中,可以使用watch命令监控一个值。在执行事务时,监控的值发生了变化,该事务执行失败。这样就可以保证数据的一致性。
示例:带锁的买票事务。
命令行(多用户):
用户1:Tom
127.0.0.1:6379> set tom 1000
OK
127.0.0.1:6379> set ticket 1
OK
127.0.0.1:6379> watch ticket
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby tom 100
QUEUED
127.0.0.1:6379> decrby ticket 1
QUEUED
用户2:Mike
127.0.0.1:6379> decrby ticket 1
(integer) 0
用户1:Tom
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get tom
"1000"
127.0.0.1:6379> get ticket
"0"
可以看到:这次事务并没有执行成功。Tom的钱也没有少,票数也没有为负数。
Java程序:买票。(给变量加锁)
public class TestRedis {
@Test
public void testTransaction(){
//创建一个Redis的客户端
Jedis client = new Jedis("192.168.126.110",6379);
//给变量加锁
client.watch("ticket");
//创建一个事务
Transaction tc = null;
try{
//开启事务
tc = client.multi();
//买票
tc.decrby("tom",100);
tc.incrby("ticket",1);
//提交事务
tc.exec();
}catch(Execption ex){
ex.printStackTrace();
if(tc != null){
tc.discard();
}
}finally{
//关闭客户端
clien.disconnect();
}
}
}