事务管理

特性

局限性很大,不能实现真正意义上的事务

redis的事务本质是命令队列,依次执行,不具备原子性和隔离性

即事务中如果某操作出错,之前的操作并不会回滚,之后的操作也不会终止

它只能保证事务执行时其他命令不能插队(由单线程保证)

常见用途:批量操作,伪乐观锁(不能从根本上避免插队,只适合不关注原值的场景)

实现多指令的原子性应借助Lua脚本

MULTI|EXEC|DISCARD

127.0.0.1:6379> SELECT 9
OK

# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 操作一一入队
127.0.0.1:6379[9]> SET k1 v1
QUEUED
127.0.0.1:6379[9]> SET k2 v2
QUEUED
127.0.0.1:6379[9]> SET k3 v3
QUEUED
127.0.0.1:6379[9]> GET k1
QUEUED
# 把入队的操作一次性执行完毕
127.0.0.1:6379[9]> EXEC
1) OK
2) OK
3) OK
4) "v1"

# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 操作入队
127.0.0.1:6379[9]> SET k4 v1
QUEUED
127.0.0.1:6379[9]> LPUSH ls1 e1 e2 e3
QUEUED
# 放弃事务
127.0.0.1:6379[9]> DISCARD
OK

# 查看结果
127.0.0.1:6379[9]> KEYS *
1) "k3"
2) "k1"
3) "k2"

# 以下操作展示了顺序性
127.0.0.1:6379[9]> FLUSHDB
OK
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> SET k1 v1
QUEUED
127.0.0.1:6379[9]> GET k2
QUEUED
127.0.0.1:6379[9]> SET k2 v2
QUEUED
127.0.0.1:6379[9]> EXEC
1) OK
2) (nil)
3) OK

# 执行时错误不回滚、不终止
# 
127.0.0.1:6379[9]> FLUSHDB
OK
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> SET k1 v1
QUEUED
127.0.0.1:6379[9]> INCR k1
QUEUED
127.0.0.1:6379[9]> SET k2 v2
QUEUED
# 执行结果,错误被跳过
127.0.0.1:6379[9]> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK

# 执行前错误(类似编译错误)事务被强制取消
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> SETN k1 v1
(error) ERR unknown command 'SETN'
127.0.0.1:6379[9]> SET k2 v2
QUEUED
# 执行结果,事务被取消
127.0.0.1:6379[9]> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

WATCH|UNWACH

# 监视或取消监视必须在开启事务之前
# 即:即使在事务中首先进行了取消监视,如果事务执行前监视的键被修改,事务依然会执行失败
# 这实现了对redis数据库本身的乐观锁,保证了在监视操作和事务操作之间不会被其他操作插队
# 但请注意:一般事务操作前势必要先获取键(根据值决定事务逻辑或是否进行执行),而这个获取键和对其设置监视的过程中仍然存在插队的可能性,因此监视并不能完全实现真正的乐观锁
# 不关注原值,即不需要根据原值来决定事务逻辑或是否执行的场景,监视是完全可行的,例如计数器,标记设置(频率限制、库存扣减等就不适用)等

127.0.0.1:6379[9]> FLUSHDB
OK
# 设置两个键
127.0.0.1:6379[9]> SET total 100
OK
127.0.0.1:6379[9]> SET sale 0
OK
# 开始监视
127.0.0.1:6379[9]> WATCH total sale
OK
# 在执行前另开客户端对监视的键的值进行操作,例如
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 操作入队
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
# 执行事务,但发现监视的键被改变,执行失败
127.0.0.1:6379[9]> EXEC
(nil)

# 一旦事务 执行或取消 之后,会自动取消原来监视的键

# 重新执行
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
# 执行成功
127.0.0.1:6379[9]> EXEC
1) (integer) 10
2) (integer) 190


# 事务中UNWATCH
127.0.0.1:6379[9]> FLUSHDB
OK
# 设置两个键
127.0.0.1:6379[9]> SET total 100
OK
127.0.0.1:6379[9]> SET sale 0
OK
# 开始监视
127.0.0.1:6379[9]> WATCH total sale
OK
# 插队修改了被监视的键
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
# 取消监视,此时取消无效
127.0.0.1:6379[9]> UNWATCH
QUEUED
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
# 执行失败
127.0.0.1:6379[9]> EXEC
(nil)

# 事务前的UNWATCH
127.0.0.1:6379[9]> FLUSHDB
OK
127.0.0.1:6379[9]> SET total 100
OK
127.0.0.1:6379[9]> SET sale 0
OK
# 监视
127.0.0.1:6379[9]> WATCH total sale
OK
# 插队
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 取消监视
127.0.0.1:6379[9]> UNWATCH
OK
# 或者在这插队
# 127.0.0.1:6379[9]> INCRBY total 100
# (integer) 200
# 开启事务
127.0.0.1:6379[9]> MULTI
OK
127.0.0.1:6379[9]> DECRBY total 10
QUEUED
127.0.0.1:6379[9]> INCRBY sale 10
QUEUED
127.0.0.1:6379[9]> INCRBY total 100
(integer) 200
# 执行成功
127.0.0.1:6379[9]> EXEC
1) (integer) 190
2) (integer) 10

全部评论

相关推荐

点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务