Redis事务
Redis事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。 - 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
- 一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
Demo
1. 正常提交事务
SET user:1001 1000
SET user:1002 500
MULTI
DECRBY user:1001 100
INCRBY user:1002 100
EXEC
执行结果:
127.0.0.1:6379> set user:1001 1000
OK
127.0.0.1:6379> set user:1002 500
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY user:1001 100
QUEUED
127.0.0.1:6379(TX)> INCRBY user:1002 100
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 900
2) (integer) 600
事务生效,顺序执行。
2. 取消事务
开启事务 → 队列命令 → 中途取消
MULTI
DECRBY user:1001 100
INCRBY user:1002 100
DISCARD
执行结果:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY user:1001 100
QUEUED
127.0.0.1:6379(TX)> INCRBY user:1002 100
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> get user:1001
"900"
127.0.0.1:6379> get user:1002
"600"
- 什么也不会改变
- 因为 DISCARD 取消了事务队列
3. 事务中有错误命令
队列里命令写错了,比如给字符串类型执行集合命令。
SET wrong_key hello
MULTI
SADD wrong_key member1 # 错误,wrong_key是string,不是set
INCRBY user:1002 50
EXEC
执行结果:
127.0.0.1:6379> SET wrong_key hello
OK
127.0.0.1:6379>
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SADD wrong_key member1
QUEUED
127.0.0.1:6379(TX)> INCRBY user:1002 50
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 650
-
在 MULTI 阶段,Redis只是把命令入队列,不检查正确性。
-
到 EXEC 阶段,执行到 SADD wrong_key member1 报错(比如 WRONGTYPE Operation against a key holding the wrong kind of value)
-
但是注意:后续命令依然会继续执行!(即 INCRBY user:1002 50 仍然会生效)
Redis事务不是回滚型事务,失败命令不会导致其他命令撤销。
官方解释:https://redis.io/blog/you-dont-need-transaction-rollbacks-in-redis/
4. WATCH监控 + 乐观锁冲突
模拟场景:
-
客户端A监控余额
-
客户端B偷偷改余额
-
客户端A提交事务失败
客户端A:
WATCH user:1001
GET user:1001 # 读取当前余额,比如是900
客户端B:
DECRBY user:1001 200
客户端A继续:
MULTI
DECRBY user:1001 100
INCRBY user:1002 100
EXEC
客户端A执行结果:
127.0.0.1:6379> WATCH user:1001
OK
127.0.0.1:6379> GET user:1001
"900"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY user:1001 100
QUEUED
127.0.0.1:6379(TX)> INCRBY user:1002 100
QUEUED
127.0.0.1:6379(TX)> EXEC
(nil)
127.0.0.1:6379> GET user:1001
"700"
客户端B执行结果:
127.0.0.1:6379> DECRBY user:1001 200
(integer) 700
- EXEC 返回 nil
- 事务失败 ,因为被监控的key (user:1001) 被客户端B修改过了!
注意: watch 命令只能在客户端开启事务之前执行,在事务中执行 watch 命令会引发错误,但不会造成整个事务失败
例如:
MULTI
WATCH user:1001
DECRBY user:1001 100
EXEC
执行结果:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> WATCH user:1001
(error) ERR WATCH inside MULTI is not allowed
127.0.0.1:6379(TX)> DECRBY user:1001 100
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 600
即使把原对象的值重新赋值给了原对象,这个时候 watch 命令也会认为监控对象还是被修改了
例如:
客户端A:
set user:1003 100
watch user:1003
multi
set user:1003 200
客户端B:
set user:1003 100
客户端A继续:
exec
客户端A执行结果:
127.0.0.1:6379> set user:1003 100
OK
127.0.0.1:6379> watch user:1003
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set user:1003 200
QUEUED
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> get user:1003
"100"
客户端B执行结果:
127.0.0.1:6379> set user:1003 100
OK
- 事务失效
- key:user:1003的值还是100
总结
| 场景 | 说明 |
|---|---|
| 正常提交 | MULTI → 入队列 → EXEC 成功 |
| 手动取消 | MULTI → 入队列 → DISCARD 放弃 |
| 执行出错 | EXEC阶段部分命令失败,但其他命令继续 |
| 乐观锁冲突 | WATCH监控,key被改,EXEC返回nil,不执行 |
| 序号 | 命令及描述 |
|---|---|
| 1 | MULTI(标记一个事务块的开始。) |
| 2 | EXEC (执行所有事务块内的命令。) |
| 3 | DISCARD (取消事务,放弃执行事务块内的所有命令。) |
| 4 | WATCH key [key ...] (监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。) |
| 5 | UNWATCH(取消 WATCH 命令对所有 key 的监视。) |

浙公网安备 33010602011771号