文章目录
  1. 1. 事务
  2. 2. Redis事务
  3. 3. 与MySQL事务的区别
  4. 4. 不支持回滚
  5. 5. 总结

为了确保连续多个操作的原子性, 一个成熟的数据库通常都会有事务支持, Redis 也不例外, Redis 通过MULTI, DISCARD, EXEC, WATCHUNWATCH 五个命令来实现事务功能;

事务

事务提供了一种”将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令。

Redis事务

一个基本Redis事务从MULIT命令开始一个事务, 然后将多个命令入队到事务中, 最后由EXEC命令触发事务,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
127.0.0.1:6379> `MULTI`
OK
127.0.0.1:6379> set name mike
QUEUED
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> set city shanghai
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get city
QUEUED
127.0.0.1:6379> `EXEC`
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "mike"
5) "shanghai"
127.0.0.1:6379>
  • MULTI命令唯一做的就是, 将客户端的 Redis_MULTI 选项打开, 让客户端从非事务状态切换到事务状态;
  • 当客户端处于非事务状态下时, 所有发送给服务器端的命令都会立即被服务器执行; 当客户端进入事务状态之后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回QUEUED, 表示命令已入队;
  • 事务队列里的所有命令被执行完之后, EXEC命令会将回复队列作为自己的执行结果返回给客户端, 客户端从事务状态返回到非事务状态, 事务执行完毕;
  • 从上述示例中可得当incr name失败后, 依然继续执行后继的set city等命令,可见Redis事务不保证执行原子性操作, 仅满足隔离性执行;

事务与非事务状态的区别
事务中的命令和普通命令在执行上还是有一点区别的,其中最重要的两点是:

非事务状态下的命令以单个命令为单位执行,前一个命令和后一个命令的客户端不一定是同一个; 而事务状态则是以一个事务为单位,执行事务队列中的所有命令:除非当前事务执行完毕,否则服务器不会中断事务,也不会执行其他客户端的其他命令;

在非事务状态下, 执行命令所得的结果会立即被返回给客户端; 而事务则是将所有命令的结果集合到回复队列,再作为EXEC命令的结果返回给客户端;

  • 并不是所有的命令都会被放进事务队列, 其中的例外就是EXEC, DISCARD, MULTIWATCHUNWATCH命令;

DISCARD 命令用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串OK给客户端, 说明事务已被取消;

Redis的事务是不可嵌套的, 当客户端已经处于事务状态, 而客户端又再向服务器发送MULTI时, 服务器只是简单地向客户端发送一个错误, 然后继续等待其他命令的入队; MULTI命令的发送不会造成整个事务失败, 也不会修改事务队列中已有的数据;

WATCH只能在客户端进入事务状态之前执行, 在事务状态下发送 WATCH 命令会引发一个错误, 但它不会造成整个事务失败, 也不会修改事务队列中已有的数据(和前面处理 MULTI 的情况一样);

WATCH命令用于在事务开始之前监视任意数量的键, 当调用EXEC命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败;

MySQL事务的区别

MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务;

事务使用的目的是统一管理insert, update, delete 这些write操作,以此来维护数据完整性。

命令区别
MySQL

BEGIN: 显式地开启一个事务;
COMMIT: 提交事务,将对数据库进行的所有修改变成为永久性的;
ROLLBACK: 结束用户的事务,并撤销正在进行的所有未提交的修改;

Redis

MULTI: 标记事务的开始;
EXEC: 执行事务的commands队列;
DISCARD: 结束事务,并清除commands队列;

默认状态
MySQL

MySQL会默认开启一个事务,且缺省设置是自动提交,即,每成功执行一个SQL,一个事务就会马上 COMMIT。所以不能ROLLBACK

Redis

Redis默认不会开启事务,即command会立即执行,而不会排队。并不支持ROLLBACK

使用方式
MySQL包含两种

BEGIN, ROLLBACK, COMMIT 显式开启并控制一个 新的 Transaction。
执行命令SET AUTOCOMMIT=0, 用来禁止当前会话自动COMMIT, 控制默认开启的事务。

Redis

MULTI, EXEC, DISCARD, 显式开启并控制一个Transaction(注意这里没有强调新的, 因为默认是不会开启事务的);

实现原理
显然RedisMySQL中事务的区别其根本原因就是实现不同方式造成的;

MySQL

MySQL实现事务,是基于UNDO/REDO日志;
UNDO日志记录修改前状态, ROLLBACK基于UNDO日志实现;
REDO日志记录修改后的状态, COMMIT基于REDO日志实现;
MySQL中无论是否开启事务, 每一个SQL都会被立即执行并返回执行结果。但是事务开启后执行后的状态只是记录在REDO日志, 执行COMMIT, 数据才会被写入磁盘。

Redis

Redis实现事务, 是基于COMMANDS队列;
如果没有开启事务, command将会被立即执行并返回执行结果, 并且直接写入磁盘;
如果事务开启, command不会被立即执行, 而是排入队列并返回排队状态。调用EXCE才会执行COMMANDS队列;

不支持回滚

Redis事务不支持回滚, 官方解释,

Redis命令只会因为错误的语法而失败(并且这些问题不能在入队时发现), 或是命令用在了错误类型的键上面; 从实用性的角度来说, 失败的命令是由编程错误造成的, 而这些错误应该在开发的过程中被发现, 而不应该出现在生产环境中;
因为不需要对回滚进行支持,所以Redis的内部可以保持简单且快速;

总结
  • 事务提供了一种将多个命令打包, 然后一次性, 有序地执行的机制; 且在执行过程中不会被中断, 所有事务命令执行完之后, 事务才能结束;
  • 多个命令会被入队到事务队列中,然后按先进先出(FIFO)的顺序执行;
  • Redis事务仅保证了事务的隔离执行; 不保证原子性:Redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚;
  • 可以通过管道技术对事务进行优化;
  • 通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回NullMULTI-bulk应答以通知调用者事务执行失败
  • 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁;
  • 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改, 所以不会上锁, 但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新;
  • 对比分析了Redis事务与MySQL事务的异同点;
  • 从官方解释中阐述为何Redis事务没有必要支持回滚机制;

作者署名:朴实的一线攻城狮
本文标题:redis专题15 事务系列问题
本文出处:http://researchlab.github.io/2018/10/12/redis-15-transaction/
版权声明:本文由Lee Hong创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处, 否则保留追究法律责任的权利。

@全栈炼狱之路

关注微信公众号 @全栈炼狱之路

总访问:
总访客: