redis专题15 事务系列问题
  热度 °
为了确保连续多个操作的原子性, 一个成熟的数据库通常都会有事务支持, Redis
也不例外, Redis
通过MULTI
, DISCARD
, EXEC
, WATCH
和UNWATCH
五个命令来实现事务功能;
事务
事务提供了一种”将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令。
Redis
事务
一个基本Redis
事务从MULIT命令开始一个事务, 然后将多个命令入队到事务中, 最后由EXEC
命令触发事务,
1 | 127.0.0.1:6379> `MULTI` |
MULTI
命令唯一做的就是, 将客户端的Redis
_MULTI
选项打开, 让客户端从非事务状态切换到事务状态;- 当客户端处于非事务状态下时, 所有发送给服务器端的命令都会立即被服务器执行; 当客户端进入事务状态之后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回QUEUED, 表示命令已入队;
- 事务队列里的所有命令被执行完之后,
EXEC
命令会将回复队列作为自己的执行结果返回给客户端, 客户端从事务状态返回到非事务状态, 事务执行完毕; - 从上述示例中可得当
incr name
失败后, 依然继续执行后继的set city
等命令,可见Redis
事务不保证执行原子性
操作, 仅满足隔离性
执行;
事务与非事务状态的区别
事务中的命令和普通命令在执行上还是有一点区别的,其中最重要的两点是:
非事务状态下的命令以单个命令为单位执行,前一个命令和后一个命令的客户端不一定是同一个; 而事务状态则是以一个事务为单位,执行事务队列中的所有命令:除非当前事务执行完毕,否则服务器不会中断事务,也不会执行其他客户端的其他命令;
在非事务状态下, 执行命令所得的结果会立即被返回给客户端; 而事务则是将所有命令的结果集合到回复队列,再作为
EXEC
命令的结果返回给客户端;
- 并不是所有的命令都会被放进事务队列, 其中的例外就是
EXEC
,DISCARD
,MULTI
和WATCH
及UNWATCH
命令;
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
(注意这里没有强调新的
, 因为默认是不会开启事务的);
实现原理
显然Redis
与MySQL
中事务的区别其根本原因就是实现不同方式造成的;
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)国际许可协议进行许可,转载请注明作者及出处, 否则保留追究法律责任的权利。