为什么要用事务
Redis的单个命令是原子性的(比如get set mget mset),如果涉及到多个命令的时候,需要把多个命令作为一个不可分割的处理序列,就需要用到事务。
例如我们之前说的用setnx实现分布式锁,我们先set,然后设置对key设置expire,防止del发生异常的时候锁不会被释放,业务处理完了以后再del,这三个动作我们就希望它们作为一组命令执行。
Redis的事务有两个特点:
- 按进入队列的顺序执行。
- 不会受到其他客户端的请求的影响。
Redis的事务涉及到四个命令:
命令
说明
multi
开启事务
exec
执行事务
discard
取消事务
watch
监视
事务用法
案例:张三(zhangsan)和李四(lisi)各有100元,张三需向李四转账50元。张三账户余额减少50元,李四的账户余额增加50元。
1127.0.0.1:6379> set zhangsan 100 2OK 3127.0.0.1:6379> set lisi 100 4OK 5127.0.0.1:6379> multi 6OK 7127.0.0.1:6379> decrby zhangsan 50 8QUEUED 9127.0.0.1:6379> incrby lisi 50 10QUEUED 11127.0.0.1:6379> exec 121) (integer) 50 132) (integer) 150 14127.0.0.1:6379> get zhangsan 15"50" 16127.0.0.1:6379> get lisi 17"150"
通过multi的命令开启事务。事务不能嵌套,多个multi命令效果一样。
multi执行后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当exec命令被调用时,所有队列中的命令才会被执行。
通过exec的命令执行事务。如果没有执行exec,所有的命令都不会被执行。
如果中途不想执行事务了,怎么办?
可以调用discard可以清空事务队列,放弃执行。
1127.0.0.1:6379> multi 2OK 3127.0.0.1:6379> set s1 1 4QUEUED 5127.0.0.1:6379> set s2 2 6QUEUED 7127.0.0.1:6379> set s3 3 8QUEUED 9127.0.0.1:6379> discard 10OK 11127.0.0.1:6379> get s1 12(error) WRONGTYPE Operation against a key holding the wrong kind of value
开通两个客户端
1####------>>>>>第一个客户端执行命令 2127.0.0.1:6379> set zhangsan 1000 3OK 4127.0.0.1:6379> get zhangsan 5"1000" 6127.0.0.1:6379> watch zhangsan 7OK 8127.0.0.1:6379> multi 9OK 10127.0.0.1:6379> incrby zhangsan 100 11QUEUED 12 13####------>>>>>第二个客户端执行命令 14127.0.0.1:6379> decrby zhangsan 100 15(integer) 900 16 17####------>>>>>第一个客户端执行命令 18127.0.0.1:6379> exec 19(nil) 20127.0.0.1:6379> get zhangsan 21"900"
事务可能遇到的问题
我们把事务执行遇到的问题分成两种,一种是在执行exec之前发生错误,一种是在执行exec之后发生错误。
在执行exec之前发生错误
比如:入队的命令存在语法错误,包括参数数量,参数名等等(编译器错误)。
1127.0.0.1:6379> multi 2OK 3127.0.0.1:6379> set list 111 4QUEUED 5127.0.0.1:6379> hset list 222 6(error) ERR wrong number of arguments for 'hset' command 7127.0.0.1:6379> exec 8(error) EXECABORT Transaction discarded because of previous errors.
在这种情况下事务会被拒绝执行,也就是队列中所有的命令都不会得到执行。
在执行exec之后发生错误
比如,类型错误,比如对String使用了Hash的命令,这是一种运行时错误。
1127.0.0.1:6379> flushall 2OK 3127.0.0.1:6379> multi 4OK 5127.0.0.1:6379> set s1 1 6QUEUED 7127.0.0.1:6379> hset s1 a b 8QUEUED 9127.0.0.1:6379> exec 101) OK 112) (error) WRONGTYPE Operation against a key holding the wrong kind of value 12127.0.0.1:6379> get s1 13"1"
最后我们发现setk11的命令是成功的,也就是在这种发生了运行时异常的情况下,只有错误的命令没有被执行,但是其他命令没有受到影响。
这个显然不符合我们对原子性的定义,也就是我们没办法用Redis的这种事务机制来实现原子性,保证数据的一致。
为什么在一个事务中存在错误,Redis不回滚?
这种方式也有其合理之处:只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。