Redis事物

昨天介绍了Redis Pipeline,Pipeline能帮我们组装命令一次发送给Redis,但是Pipeline中的命令不具有原子性,所以如果我们需要自己组装一个原子性的操作,使用Pipeline是无法实现的,庆幸的是Redis提供了事物和支持Lua脚本来实现原子性操作。

今天我们来了解一下Redis事物。

很多关系型数据库都支持事物操作,保证在一个事物内的操作是原子性的,要么都执行,要么都不执行。

Redis也提供了简单的事物功能,这里为什么说Redis提供的事物是简单的事物功能呢?等下面再揭晓。

1、事物的使用

Redis提供了一个 multi 命令开启事物,exec 命令提交事物,在它们之间的命令是在一个事物内的,能保证原子性。

通过上面的命令可以看到使用 multi 命令开启事物之后,执行的Redis命令返回结果 QUEUED,表示命令并没有执行,而是暂时保存在Redis事物中,直到执行 exec 命令后才会执行上面的命令并且返回结果。

一般的关系型数据库比如Mysql中的事物会存在事物的隔离级别,可能会存在脏读、不可重复读以及幻读的情况。

比如说脏读,A事物可以读到B事物未提交的事物,那么预示着B事物在还没提交的时候就已经将命令发送到Mysql了,事物的提交与否只会影响到事物是否回滚,即使B没有提交事务,A也能读取到B事物中对数据的修改。

Redis的事物做的很简单,没有像关系型数据库那样把事物的隔离级别划分的那么细,Redis在事物没提交之前不会执行事物中的命令,会等到事物提交的那一刻再执行事物中的所有命令。所以上面的案例中,如果在 exec 命令未执行之前另一个Redis客户端调用 get tran1 命令返回值会是null,因为事物没有提交之前食物中的命令还没有执行。

下面这个是Jedis客户端执行事物的代码:

需要注意的是开启事物之后,执行命令的对象不是Jedis对象,而是Transaction对象,否则会抛出下面的异常:

2、事物对异常的处理机制

Redis执行命令的错误主要分为两种:

1.命令错误:执行命令语法错误,比如说将 set 命令写成 sett

2.运行时错误:命令语法正确,但是执行错误,比如说对 List 集合执行 sadd 命令

Redis事物中如果发生上面两种错误,处理机制也是不同的。

命令错误处理机制

开启事物之后,往事物中添加的命令如果有命令错误(语法错误),那么整个事物中的命令都不会执行。

上面案例中,开启事务后第一条命令添加返回QUEUED,第二条命令语法错误,最后提交事务。

可以看到,事物提交后 get a1 返回值是null,所以第二条命令的语法错误导致整个事物中的命令都不会执行。

运行时错误处理机制

如果语法没有错误,而执行过程中发生了运行时错误,Redis不仅不会回滚事物,还会跳过这个运行时错误,继续向下执行命令

上面这个案例中,先创建了三个List类型 l1、l2、l3,然后开启事物,第一条命令往l1中插入元素,第二条命令使用 sadd 命令往List类型的l2中添加元素,第三天命令往l2中插入元素,最后提交事务。

可以看到最后事物的执行结果是第一条和第三条命令执行成功,第二条命令执行失败,所以第二条命令的执行失败不仅没有回滚事务而且还不会影响后续第三条命令的执行。

3、watch命令

虽然事物能保证事物内的操作是原子性的,但是无法保证在事物开启到事物提交之间事物中的key没有被其他客户端修改。

有点类似关系型数据库中的不可重复读的概念,在Redis的一个事物中是可以读取到其他事物提交的内容的。

上面这个案例,watchtest的初始值是"hello",开启了一个事物,并且往watchtest中append " world",我们预期的结果是"hello world",但是在事物执行过程中有另一个jedis客户端往watchtest中append " xxx",所以上面这段代码会在控制台打印

我们往往希望当前事物的执行不会受到其他事物的影响,所以这个结果明显不是我们所预期的。

Redis提供了一个 watch 命令来帮我们解决上面描述的这个问题,在 multi 命令之前我们可以使用 watch 命令来"观察"一个或多个key,在事物提交之前Redis会确保被"观察"的key有没有被修改过,没有被修改过才会执行事物中的命令,如果存在key被修改过,那么整个事物中的命令都不会执行,有点类似于乐观锁的机制。

还是上面的案例,如果在开启事物那一行上面添加 watch 命令:

最终控制台打印结果会变成:

可以看出,使用 watch 命令之后,由于watchtest被其他客户端修改过,所以事物中append " world"的命令就不会执行,所以最终会打印 "hello xxx"。

一般乐观锁都需要配合重试机制来实现,所以这里 watch 命令也可以配合重试机制来实现:

上面这段代码是使用 watch 命令实现了Redis中的incr命令,这里为了演示 watch 命令配合重试的机制,就不去校验key对应的数据结构是否是int类型。

4、总结

在介绍Redis事物的时候就提到了,Redis提供的事物是简单的,这个简单主要体现在不像关系型数据库那样将事务隔离级别划分的那么细以及不支持事物的回滚。

当然了,Redis这么做的目的也是为了性能考虑的,体现了它“keep it simple”的特性。

不得不说Redis事物是一个好功能,能帮我们实现一些原子性的操作,但是实际正常开发中很少遇到使用Redis事物的场景,因为Lua脚本同样可以帮我们实现Redis事物相关功能,并且功能要强大很多。

下一篇文章再来介绍Redis使用Lua脚本。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180903G0H2RW00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券