本文参考于 Redis 中文文档
相关命令
- MULTI:multiple,多个,这里用来开启事务,总是返回 OK,之后的命令不会立即执行,而是进入一个命令队列。
- EXEC:execute,执行,这里用来执行队列中的事务。
- DISCARD:丢弃,这里用来丢弃队列中的所有事务。
- WATCH:这里用来监视某些键, 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消。CAS(check and set)操作。
实现
准备三个键值对
2021 年 21 岁 体重 140
127.0.0.1:6379> set year 2021
OK
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> set weight 140
OK
开启事务
## 开启事务
127.0.0.1:6379> MULTI
OK
## 过一年
127.0.0.1:6379> INCR year
QUEUED
## 长一岁
127.0.0.1:6379> INCR age
QUEUED
## 长两斤
127.0.0.1:6379> INCRBY weight 2
QUEUED
## 执行事务
127.0.0.1:6379> EXEC
## 执行结果
1) (integer) 2022
2) (integer) 22
3) (integer) 142
可能出现的错误
可能出现两种错误
- 在执行 EXEC 之前,入队的命令出现语法错误
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR year
QUEUED
127.0.0.1:6379> INCR age
QUEUED
## 没有使用INCRBY 使用了INCR 不可以带参数
127.0.0.1:6379> INCR weight 2
## 翻译:INCR 命令的参数数量错误
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379> EXEC
## 翻译:执行终止,由于先前的错误,事务被丢弃
(error) EXECABORT Transaction discarded because of previous errors.
结果
127.0.0.1:6379> get year
"2022"
127.0.0.1:6379> get age
"22"
127.0.0.1:6379> get weight
"142"
可以看到结果,事务中途命令有错误,整个事务被丢弃,并没有改变原来的键值对
- 没有发生语法错误,在执行过程中失败,例如,用列表的命令处理了字符串的键
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR year
QUEUED
127.0.0.1:6379> INCR age
QUEUED
## LLEN 返回列表的长度
127.0.0.1:6379> LLEN weight
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 2023
2) (integer) 23
## 翻译:类型错误,针对的键持有错误的值
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
结果
127.0.0.1:6379> get year
"2023"
127.0.0.1:6379> get age
"23"
127.0.0.1:6379> get weight
"142"
可以发现前两个键值对发生了改变,但是第三个因为类型错误没有改变成功。
但是 Redis 并没有回滚当前事务。
Redis 中文官方文档 认为这样做是没啥问题的:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
翻译一下:
- 开发人员的锅凭什么要我背呢
- 我就是快
乐观锁机制
val = get year
val = val +1
set year val
在编程环境下执行上述逻辑时,单个客户端执行并没有什么问题,倘若两个客户端先后执行了第一句,同时拿到 year 的当前数据 2023。执行后两句的时候都会设置为 2024,但是原有的逻辑下,正确的应该是 2025。
可以使用 WATCH 命令来监视 year 键
WATCH year
MULTI
val = get year
val = val +1
set year val
EXEC
当前逻辑下,在 WATCH 执行之后, EXEC 执行之前,如果 year 的值发生了变化,当前事务就会失败。
在 EXEC 执行之后,自动取消对键的监视,也可以使用 UNWATCH 来手动取消
也可以使用 lua 脚本的原子性来实现事务