这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战
Redis事务
Redis事务是一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!Redis事务不像MySQL等关系性数据库一样,没有隔离级别的概念、不能保证原子性、不支持事务的回滚。
Redis事务命令
-
Multi 该命令为显示地开启一个事务,使用该命令开启一个事务后,后续的操作命令将会进入事务对应的执行队列,直到执行Exec命令后,入队的操作命令才会按顺序真正执行,该命令执行后客户端会从
非事务状态切换为事务状态
。 -
Watch key [key ...]
WATCH
命令的使用是为了解决事务并发
产生的不可重复读
和幻读
的问题。该命令为指定监视某个对象,只能在MULTI命令之前执行.如果监视的对象被其他客户端修改,当执行Exec命令时将会放弃事务执行队列中的所有操作命令,并直接向客户端返回(nil)表示事务执行失败。(Watch可以当做Redis乐观锁操作,每次更新数据时都会去判断一下,在此期间是否有人修改过这个数据)
-
Unwatch key [key ...] 取消对对象的监视
-
Exec 该命令为正式执行事务,调用该命令后会顺序执行事务队列中的所有操作命令。如果WATCH在Exec命令之前被调用,只有监视中的对象没有被修改,命令才会被执行,否则停止执行。
-
Discard 该命令为清除事务执行队列中的操作命令,并将当前的事务状态改为非事务状态,如果Watch命令在该命令之前被调用,则会释放被Watch命令监视的对象。
🚦总来说,Redis实现事务主要的通过Multi(开启事务)、命令入队、Exec(执行事务)这三个命令。如下示例
127.0.0.1:6379> multi #开启一个事务
OK
#命令入队
127.0.0.1:6379(TX)> set key1 v1 #进入事务队列
QUEUED
127.0.0.1:6379(TX)> set key2 v2 #进入事务队列
QUEUED
127.0.0.1:6379(TX)> set key3 v3 #进入事务队列
QUEUED
127.0.0.1:6379(TX)> set key4 v4 #进入事务队列
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) OK
3) OK
4) OK
取消事务(discard),事务队列中的命令都不会被执行 :
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> set key3 v3
QUEUED 127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get key1 (nil)
127.0.0.1:6379> get key2 (nil)
127.0.0.1:6379> get key3 (nil)
Watch命令的使用
Watch
命令是在Multi命令之前执行的,表示监视任意数量的对象,与它对应的命令就是Unwatcch
命令,取消监视。使用示例如下:
- 以下示例模拟一个转账操作,事务开启之前,监听money对象,在执行money-20的操作中,会判断money在这个期间有没有被修改过。
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监听money对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20 #事务正常结束,在执行事务队列中的命令时没有发生数据修改
QUEUED
127.0.0.1:6379(TX)> incrby out 20 QUEUED 127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> get money
- 下面我们测试多客户端下,监听money对象,在线程操作money对象之前,在另一个客户端对该值进行修改。 客户端1
#客户端1
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 20
OK
127.0.0.1:6379> watch money #监听money对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
#后面提交执行事务
客户端2,客户端2对客户端1监视的对象money进行修改
#客户端2
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> get out
"20"
127.0.0.1:6379> set money 120
OK
127.0.0.1:6379> get money
"120"
下面我们看看客户端1执行事务,会出现什么情况?
127.0.0.1:6379(TX)> exec
(nil) #redis监听到了另一个客户端2修改了money的值,所以此时的事务执行失败
从上面的执行结果可以看出,客户端1提交执行事务,发现监视的money对象已经被修改了,所以此时的事务执行失败,此时,我们可以使用Unwatch命令取消对该money对象的监视,然后再使用Watch命令重新监视该对象,获取最新的值,然后再执行-20操作,如下:
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 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec #对比监听的值是否发现变化,没有的话则事务能执行成功
1) (integer) 100
2) (integer) 40
事务中的错误
编译型异常
编译型异常是指,事务队列中的命令语法等存在错误,无法编译通过,即在执行命令入队时出现的错误
,在这种情况下,事务队列中的所有命令都不会被执行。如下示例:
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> getset key2 #编译型异常,事务队列中的所有命令不会被执行
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常
如果事务队列中存在运行时错误,即执行Exec后出现错误
,例如对字符串类型的数据做自增,除0操作等,那么执行该条命令时,其他命令是可以正常执行的,而错误的命令会抛异常。如下示例:
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1 #运行时异常,字符串不能做自增 QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range #运行时异常,字符串不能做自增
2) OK
3) OK
4) "v3"
🏁以上就是对Redis事务的简单介绍,如果有错误的地方,还请留言指正,如果觉得本文对你有帮助那就点个赞👍吧😋😻😍