一、Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
● 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
● 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
● 开始事务(MULTI)
● 命令入队
● 执行事务(EXEC)
二、Redis事务命令
MULTI:标记一个事务块的开始。
DISCARD:取消事务,放弃执行事务块内的所有命令。
EXEC:执行所有事务块内的命令。
WATCH key [key ...]:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
UNWATCH:取消 WATCH 命令对所有 key 的监视。
三、事务执行
● 正常执行事务:
127.0.0.1:6379> MULTI // 开启事务 OK // 命令入队 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> EXEC // 执行事务 1) OK 2) OK 3) "v2" 4) OK
● 放弃事务:
127.0.0.1:6379> MULTI // 开启事务 OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> set k6 v6 QUEUED 127.0.0.1:6379> DISCARD // 取消事务 OK 127.0.0.1:6379> get k6 // 事务队列中的命令都不会被执行 (nil)
- 编译时异常(代码有错,命令有错误):事务中所有的命令都不会被执行
127.0.0.1:6379> MULTI // 开启事务 OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> getset k2 // 错误命令 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> EXEC // 执行事务报错 (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k1 // 所有命令都不会被执行 (nil)
- 运行时异常(语法错误):如果事务队列存在语法性错误,在执行的时候其它命令可以正常执行
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> INCR k1// 执行的时候失败 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> get k1 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) ERR value is not an integer or out of range// 虽然该命令报错,但是其它命令依旧执行成功! 3) OK 4) OK 5) "v2" 6) "v1"
四、监控 - WATCH
● 悲观锁:很悲观,认为什么时候都会出现问题,无论做什么都会加锁!
● 乐观锁:很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
● 测试 - 正常执行成功
127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> set out 0 OK 127.0.0.1:6379> WATCH money // 监视money对象 OK 127.0.0.1:6379> MULTI // 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功! OK 127.0.0.1:6379> DECRBY money 20 QUEUED 127.0.0.1:6379> INCRBY out 20 QUEUED 127.0.0.1:6379> EXEC 1) (integer) 80 2) (integer) 20
● 测试 - 多线程修改值,使用watch可以当做redis的乐观锁!
127.0.0.1:6379> WATCH money out // 监视money out OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> INCRBY out 10 QUEUED 127.0.0.1:6379> DECRBY money 10 QUEUED 127.0.0.1:6379> EXEC // 执行之前,另一个线程,修改了监视对象的值,会导致事务执行失败 (nil)
如果修改失败,获取最新的值再次监视执行事务
127.0.0.1:6379> UNWATCH// 发现事务执行失败,取消监视 OK 127.0.0.1:6379> WATCH money out// 获取最新的值,再次监视(select version) OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> INCRBY out 100 QUEUED 127.0.0.1:6379> DECRBY money 100 QUEUED 127.0.0.1:6379> EXEC// 最新的值没有被该事务之外的其它操作修改,执行成功! 1) (integer) 120 2) (integer) 980