啥? Redis也支持事务

1,730 阅读5分钟

这是我参与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事务的简单介绍,如果有错误的地方,还请留言指正,如果觉得本文对你有帮助那就点个赞👍吧😋😻😍

默认标题_动态分割线_2021-07-15-0.gif