我们在学 MySQL 的时候就接触到了事务及 ACID 原则,其中很重要的一条原则:要么同时成功,要么同时失败!(原子性)。
Redis 单条命令是保存原子性的,但是 Redis 的事务是不保证原子性的
。
Redis 事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行。
一次性、顺序性、排他性!执行一系列的命令。
Redis 事务没有隔离级别的概念!
所有的命令在事务中并没有被直接执行,只有发起执行命令的时候才会执行。
Redis 事务分为三个阶段:
正常执行事务
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set key1 value1 # 命令入队
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> rpush number one two three
QUEUED
127.0.0.1:6379> lrange number 0 -1
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) "value1"
3) (integer) 3
4) 1) "one"
2) "two"
3) "three"
取消事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key2 value2
QUEUED
127.0.0.1:6379> rpush number four five
QUEUED
127.0.0.1:6379> discard # 取消事务,队列中的命令都不会被执行
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key2
(nil)
127.0.0.1:6379> lrange number 0 -1
1) "one"
2) "two"
3) "three"
如果事务中的命令有问题呢?
我们可以把 Redis 中的错误想象成 Java 中的两种异常:
编译性异常
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 value1
QUEUED
127.0.0.1:6379> lpush list l1 l2
QUEUED
127.0.0.1:6379> setone key2 value2 # 错误命令
(error) ERR unknown command `setone`, with args beginning with: `key2`, `value2`,
127.0.0.1:6379> set key3 value3
QUEUED
127.0.0.1:6379> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get key1
(nil)
127.0.0.1:6379> get key3 # 所有的命令都没有执行
(nil)
运行时异常
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name touyel
QUEUED
127.0.0.1:6379> incr name # 语法错误
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get name # 正常命令执行成功
"touyel"
127.0.0.1:6379> get age
"18"
悲观锁
乐观锁
Redis 使用乐观锁只需了解一个命令:watch
Redis 监视测试( Watch )
正常执行成功:
127.0.0.1:6379> set t:money 200
OK
127.0.0.1:6379> set r:money 100
OK
127.0.0.1:6379> watch t:money # 监视 t:money 对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby t:money 40
QUEUED
127.0.0.1:6379> incrby r:money 40
QUEUED
127.0.0.1:6379> exec # 事务正常结束
1) (integer) 160
2) (integer) 140
测试多线程修改值,使用 watch 可以当作 Redis 的乐观锁操作
127.0.0.1:6379> keys *
1) "r:money"
2) "t:money"
127.0.0.1:6379> watch t:money # 监视 t:money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby t:money 10
QUEUED
127.0.0.1:6379> incrby r:money 10
QUEUED
# 执行之前,另外一个线程修改了 t:money
127.0.0.1:6379> set t:money 300
OK
# 因为另外一个线程修改了 t:money ,导致事务执行失败
127.0.0.1:6379> exec
(nil)
如果修改失败,获取最新的值就好
127.0.0.1:6379> unwatch # 如果发现事务执行失败,就先解锁
OK
127.0.0.1:6379> watch t:money # 再次监视 t:money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby t:money 100
QUEUED
127.0.0.1:6379> decrby r:money 100
QUEUED
127.0.0.1:6379> exec # 事务执行成功
1) (integer) 400
2) (integer) 40
原文:https://www.cnblogs.com/touyel/p/12733451.html