redis事务的ACID特性

事务,可以理解为一个独立的工作单元,或者说是一组独立的操作。这里举一个银行转账的例子,假设要从A的银行账户转账100元到B的账户,那么这个事务最少应该包含三个步骤:

  1. 检查A账户余额大于100元;
  2. 从A账户余额扣掉100元;
  3. 往B账户转入100元

虽然这个事务很简单,但是我们考虑一下这些可能异常的场景:

  1. 检查A账户余额之后,另一个事务又从A账户中的钱全部取走了,这时A账户余额为0,那么执行第2步的时候就会出错。
  2. 从A账户扣掉100元后,但还没有转入B账户 的时候,系统突然死机,这时候A的钱别划走了但是B的钱却没有增加,数据就不正确了。

也就是说事务执行的过程中可能会发生各种异常情况,因为就需要事务满足一些特性来保证事务能正确无误的执行。这些特性就是事务的ACID特性。 

数据库事务的ACID特性分别指的是:原子性(Atomicity),一致性(Consistency)和隔离性(Isolation),持久性(Durability)。

原子性

事务的原子性是指一个事务必须是像原子一样不可分割的最小工作单元,整个事务的所有操作要么全部执行成功,要么全都执行失败回滚,不可能只执行其中一部分。

上面那个例子中,如果只执行到第二步就不执行了,那么A的钱就会被白白扣掉,这是决不允许的。举个全都执行失败的例子:如果 A的余额只有90元,那么第一步就会执行失败,这时候就不能再执行下去,相当于全部失败。

一致性

数据库总是从一个一致性的状态迁移到另一个一致性状态。

 这个很容易理解,就是指数据库中的数据在事务执行前后数据是一致的。上面举例的第二种异常场景就是不一致的场景。

隔离性

一个事务所做的修改在最终提交以前,对其它事务是不可见的。或者说数据库的事务之间是独立互不影响的,数据库在执行一个事务时,其它操作无法存取到正在执行事务访问的数据。 

上面举例的第一个异常场景就是不满足隔离性的一个例子。

持久性

一旦事务提交,则其所做的修改就会永久保存到数据库中。 即使系统崩溃,修改的数据也不会丢失。

 接下来看一下Redis 是如何实现事务机制的。

事务的执行过程包含三个步骤,Redis 提供了 MULTI、EXEC 两个命令来完成这三个步骤。下面我们来分析下。

第一步,客户端要使用MULTI命令显式地开启一个事务。

第二步,客户端把事务中要执行的具体命令(例如GET、SET 等)发送给服务器端。不过,这些命令发送到了Redis服务器之后,Redis 实例只是把这些命令暂存到一个命令队列中,并不会立即执行。

第三步,客户端向服务器端发送提交事务的命令EXEC,让数据库实际执行第二步中发送的具体操作。当服务器端收到 EXEC 命令后,才会实际执行命令队列中的所有命令并将结果返回给客户端。

原子性

如果事务正常执行,没有发生任何错误,那么,MULTI 和 EXEC 配合使用是没有问题的。但是,天有不测风云,如果事务执行发生错误了,原子性还能保证吗?现在看一下一下几种异常场景。

第一种情况,在执行 EXEC 命令前,客户端发送的操作命令本身就有错误(比如语法错误,或者使用了不存在的命令),在命令入队时就被 Redis 实例判断出来了。对于这种情况,在命令入队时,Redis 就会报错并且记录下这个错误。此时,我们还能继续提交命令操作。等到执行了 EXEC 命令之后,Redis 就会拒绝执行所有提交的命令操作,返回事务失败的结果。这种情况下,事务中的所有命令都不会再被执行了,保证了原子性。

 举个例子:


#开启事务
127.0.0.1:6379> MULTI
OK
#发送事务中的第一个命令,但是Redis不支持该命令,返回报错信息
127.0.0.1:6379> POP RMB 50000000000000000000
(error) ERR unknown command `POP`, with args beginning with: `RMB`, `50000000000000000000`, 
#发送事务中的第二个命令,这个操作是正确的命令,Redis把该命令入队
127.0.0.1:6379> DECR money
QUEUED
#实际执行事务,但是之前命令有错误,所以Redis拒绝执行
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

这个例子中,事务里包含了一个 Redis 本身就不支持的 POP命令,所以,在 POP命令入队时,Redis 就会返回错误。虽然,事务里还有一个正确的 DECR 命令,但是,在最后执行 EXEC 命令后,整个事务被放弃执行了。

 当redis返回错误时,我们是可以使用discard命令来清空命令缓冲区重新来过。

第二种情况,事务操作入队时,命令和操作的数据类型不匹配,但 Redis 实例没有检查出错误。但是,在执行完 EXEC 命令以后,Redis 实际执行这些事务操作时,就会报错。不过,需要注意的是,虽然 Redis 会对错误命令报错,但还是会把正确的命令执行完。在这种情况下,事务的原子性就无法得到保证了。


#开启事务

127.0.0.1:6379> SET money 99999999999999999999999
127.0.0.1:6379> MULTI
OK
#发送事务中的第一个操作,LPOP命令操作的数据类型不匹配,此时并不报错
127.0.0.1:6379> LPOP money
QUEUED
#发送事务中的第二个操作
127.0.0.1:6379> DECR money
QUEUED
#实际执行事务,事务第一个操作执行报错
127.0.0.1:6379> EXEC
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 8

在MySQL的事务中,会提供rollback回滚机制,当事务执行发生错误时,事务中的所有操作都会撤销,已经修改的数据也会被恢复到事务执行前的状态。但是在redis中并没有类似的回滚机制,即使事务队列中的某个命令在执行期间出现错误,整个事务也会 继续执行下去,直到将事务队列中的所有命令都执行完毕为止。redis的作者在功能的文档中解释说,不支持事务回滚是因为这种复杂的功能和redis追求的简单高效的设计主旨不符合,并且他认为,redis事务的执行时错误通常都是编程错误造成的,只能由程序员自己去保证。加上这种错误通常只会出现在调试环境中,而很少会在实际的生产环境中出现,所以他认为没有必要为redis开发事务回滚功能。

第三种情况,在执行事务的 EXEC 命令时,Redis 宕机。如果 Redis 开启了 AOF 日志,可能只会有部分的事务操作被记录到 AOF 日志中。我们需要使用 redis-check-aof 工具检查 AOF 日志文件,这个工具可以把未完成的事务操作从 AOF 文件中去除。这样 AOF 恢复实例后,事务操作不会再被执行,从而保证了原子性。如果redis压根就没有开启持久化,那么redis重启后数据是空的,此时,也就谈不上原子性了。

综上所述可以得出以下结论:

命令入队时就报错,会放弃事务执行,保证原子性;

命令入队时没报错,实际执行时报错,不保证原子性;

EXEC 命令执行时实例故障,如果开启了 AOF 日志,可以保证原子性。

一致性

现在分析一下在一些异常场景redis如何保证数据一致性。

如果命令入队时就报错,那么事务压根就不会执行,这种情况数据肯定是一致的。如果命令入队时没报错,实际执行时报错,这种情况下,错误的命令没有执行,数据库也还是一致的。

对于redis异常复位从持久化文件中恢复的场景:

  1. 没开启持持久化,重启后数据为空,一定是一致的。
  2. RDB 持久化,在执行事务的时候是不可能执行RDB的,所以,事务命令操作的结果不会被保存到 RDB 文件中,使用 RDB 快照进行恢复时,数据库里的数据和事务执行前一样,是一致的。
  3. AOF 持久化,而事务操作还没有被记录到 AOF 日志时,实例就发生了故障,那么,使用 AOF 日志恢复的数据库数据是一致的。如果只有部分操作被记录到了 AOF 日志,我们可以使用 redis-check-aof 清除事务中已经完成的操作,数据库恢复后也是一致的。

隔离性 

在MySQL中总共有四种隔离级别:未提交读,提交读,可重复读,可串行化。由于redis是使用单线程来执行事务,所以它的隔离级别相当于MySQL中隔离性最高的可串行级别。

持久性 

事务的耐久性指的是,当一个事务执行完毕时,执行这个事务所得的结果已经被保持到永久存储介质(磁盘,SSD)里面。redis提供了RDB和AOF这两种持久化方案, 下面分析一下各种持久化配置场景。

  1. 如果没开启,免谈;
  2. 开启RDB,因为RDB持久化是在满足一些条件下才开始执行,比如每天执行一次,每小时执行一次,所以是保证不了持久性的;
  3. 配置AOF为always,即每条命令都会同步到磁盘,这种情况是满足持久性的;
  4. 配置AOF为everysec,即每秒同步一次磁盘,是存在命令丢失的风险,所以不满足持久性
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值