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监控 + 乐观锁冲突

模拟场景:

  1. 客户端A监控余额

  2. 客户端B偷偷改余额

  3. 客户端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 的监视。)
posted @ 2025-04-28 15:25  Eiffelzero  阅读(24)  评论(0)    收藏  举报