我们学过的事务都是保证原子性的,但是 redis 的事务中执行多个指令,是不保证原子性的
redis 事务的本质
就是一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程,是按照顺序执行命令的,他们拥有
redis 的事务没有隔离级别的概念
redis 事务中,命令是这样执行的
命令放在事务中,并没有马上执行,而是发起执行命令的时候才会执行,通过 exec 触发
redis 是单条指令保证原子性,但是事务不保证原子性
执行一个事务的流程是这个样子的:
MULTI
开启一个事务
EXEC
执行事务里面的指令
执行事务完毕后,需要再使用事务,那么需要再次开启事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set name xiaozhu
QUEUED
127.0.0.1:6379(TX)> set age 19
QUEUED
127.0.0.1:6379(TX)> set hobby paly
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) OK
5) OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set city changsha
QUEUED
127.0.0.1:6379(TX)> get city
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) "changsha"
DISCARD
放弃最近开启的事务
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set name xiaozhu
QUEUED
127.0.0.1:6379(TX)> set age 10
QUEUED
127.0.0.1:6379(TX)> set city beijing
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> get city
(nil)
127.0.0.1:6379> get name
(nil)
事务出错分为两种:
代码编写出错,无法通过编译,此时事务里面编写的指令,全部执行失败
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name xiaozhu
QUEUED
127.0.0.1:6379(TX)> set age 10
QUEUED
127.0.0.1:6379(TX)> set hobby play
QUEUED
127.0.0.1:6379(TX)> set city changsha
QUEUED
127.0.0.1:6379(TX)> set dream xxx
QUEUED
127.0.0.1:6379(TX)> setget hahaha # 指令不存在,编译不通过,直接报错
(error) ERR unknown command `setget`, with args beginning with: `hahaha`,
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get city
(nil)
一旦编译出错,事务里面的所有指令都不会执行
程序运行时某一个指定的某个逻辑出现问题,仅仅影响本条执行的执行,并且本条指令会跑出异常,不影响事务中其他指令的执行
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set name xiaozhu
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> INCR name # incr 指令用法正确,只不过 name 是字符串,运行的时候,是无法执行通过的
QUEUED
127.0.0.1:6379(TX)> set age 29
QUEUED
127.0.0.1:6379(TX)> set hobby play
QUEUED
127.0.0.1:6379(TX)> exec
1) "xiaozhu"
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
127.0.0.1:6379> get hobby
"play"
127.0.0.1:6379> get name
"xiaozhu"
根据上述结果,incr
指令的用法正确,但是 incr
只能对数字进行操作,name
是一个字符串,无法做自增操作,因此运行时报错,事务中不影响其他的指令执行
watch 监控,可以说说 乐观锁 和 悲观锁
乐观锁:
悲观锁:
用 watch 来加锁模拟银行取钱
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set money 2000
OK
127.0.0.1:6379> set outer 500
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 500
QUEUED
127.0.0.1:6379(TX)> INCRBY outer 500
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 1500
2) (integer) 1000
开始测试模拟多个线程来操作
redis 客户端 1
127.0.0.1:6379> set money 2000
OK
127.0.0.1:6379> set outer 500
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 500
QUEUED
127.0.0.1:6379(TX)> INCRBY outer 500
QUEUED
客户端 1 的事务操作还未开始执行,客户端 2 就进行了如下操作
127.0.0.1:6379> set money 10000
OK
127.0.0.1:6379> set money 10000
OK
此时执行客户端 1 的 exec 指令
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> get money
"10000"
发现客户端 1 执行 exec 的时候,是一个 nil ,说明变更失败,是因为 watch 监控到 money 有变动,因此事务执行失败
发现事务执行失败了,需要使用命令 unwatch 解除监控
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 1000
QUEUED
127.0.0.1:6379(TX)> INCRBY outer 1000
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 9000
2) (integer) 1500
这就是 redis 实现乐观锁的操作,一般使用场景会放到秒杀系统里面进行应用
参考资料:
redis_doc
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~