目录
阅读下文之前建议点击下方链接了解 MySQL 事务详解
Redis 事务对比 MySQL 事务
MySQL 事务
- 原子性:将多个操作打包成一个整体,要么全部执行成功,要么全部都不执行,一旦执行出错,立刻回滚如初
- 一致性:事务执行前后,通过约束和回滚机制,保证数据合理
- 持久性:事务做出的修改会存储到硬盘上,不会随着服务器重启而丢失
- 隔离性:事务并发执行,涉及 四大隔离级别
Redis 事务
- 原子性:Redis 的事务到底有没有原子性 存在争议
- 不具备一致性:Redis 没有约束,也没有回滚机制,事务操作过程中如果某个修改操作出现失败,就可能引起不一致的情况
- 不具备持久性:Redis 本身就是内存数据库,数据是存储在内存中的,虽然 Redis 也有持久化机制,但是这里的持久化机制 和 事务没有啥直接关系
- 不涉及隔离性:Redis 是一个单线程模型的服务器程序,所有的请求 或 事务都是串行执行的
Redis 事务原子性解释
- 原子性最原本的含义:将多个操作打包到一起,要么全部执行,要么全部不执行
- Redis 和 MySQL 均做到了原子性最原本的含义
注意:
- Redis 仅能保证 多个操作全部一起执行或者不执行,但是不保证是否能够执行成功!
- 即如果事务中若干个操作,存在有失败执行失败的操作,不会进行回滚!!
- 但是 MySQL 面对上述情况会进行回滚操作,以保证事务的 一致性!
小总结:
- MySQL 提高了原子性的门槛
- 这就使得人们谈到原子性的时候,更多的是想到的 MySQL 这种带有回滚机制的原子性
- 所以 Redis 事务是否具有原子性便存在这样的歧义
- Redis 事务有原子性:可将多个操作能够打包到一起执行
- Redis 事务不具有原子性:可将多个操作能够打包到一起执行,但是不具有回滚机制,无法保证这多个操作能够正确的执行
Redis 事务详解
- Redis 事务的主要意义就是为了打包 ,以便避免其他客户端的命令插队到中间
- Redis 引入队列来实现事务,该队列 每个客户端均有一个
执行流程
- 开启事务
- 客户端输入命令,命令发给服务器并且插入到队列中,此时的命令不会立即执行
- 输入 执行事务 命令,队列中的命令 按照顺序依次执行
- 依次执行的过程,均由 Redis 主线程完成,主线程会把事务中的操作执行完,再去执行其他客户端的命令
问题:
- Redis 事务为什么这么简单,为啥不设计成和 MySQL 事务一样强大呢?
回答:
- MySQL 事务在实现上付出了很大的代价
- 空间上:花费更多的空间来存储更多的数据
- 时间上:有着更大的执行开销
- 也正是因为 MySQL 上述的代价,才有了 Redis 上场的机会
典型使用场景
- 关于超卖问题
- 一款商品放货 5000 台,如果有 5001 个人下单成功,这便属于超卖
典型写法:
- 如果不加上任何限制,便可能存在 线程安全 问题
- 在多线程中,均通过加锁的方式,来避免 插队
Redis 事务写法:
- Redis 本身就是一个单线程模型,所以其天然不具有线程安全问题
- 所以此处我们直接使用 Redis 事务,将购买商品所需进行的流程打包到一起执行即可
注意:
- Redis 服务器只有收到 执行事务 命令的时候 才会真正执行 事务队列中的命令 !
具体理解:
- 只有客户端B 的 执行事务 命令发过来之后,服务器才会真正执行客户端B 的事务队列
- 与此同时 客户端A 的事务队列已经执行完成
- 则 客户端B 事务队列中 get 到的 count 就已经是 客户端A 事务队列执行完之后的结果
- 即采用 Redis 事务方法 无需加锁也能解决上述超卖问题
小总结:
- Redis 事务的应用场景没有 MySQL 事务那么多
- Redis 如果是按照集群模式部署的,则不支持事务
问题:
- Redis 命令中能进行条件判定吗?
回答:
- Redis 原生命令中确实没有像 if 这种的条件判定
- 但是 Redis 支持 lua 脚本
- 通过 lua 脚本便可以实现上述实例那样的条件判定,并且和事务一样,也是打包批量执行
- lua 脚本的实现方式是 Redis 事务的进阶版本
Redis 事务命令
MULTI 开启事务 exec 执行事务 discard 放弃当前事务 watch 监控某个 key 是否在事务执行之前发送变化 unwatch 放弃监控
实例理解
- 此处演示正确执行一个 Redis 事务
- 此处演示放弃执行一个 Redis 事务
问题:
- 开启事务,给服务器发送若干个命令后,此时服务器重启,那么该事务将会如何呢?
回答:
- 上述场景的效果就等同于 discard
WATCH 的使用
- watch 命令用于监控某个 key 是否在事务执行之前,发生了改变
实例理解
- 从时间上来看,客户端A 先发送 set key 222,客户端B 后发送 set key 333
- 但是由于 客户端A ,只有执行了 exec 命令,才会真正执行 set key 222 命令
- 即 exec 命令 比 客户端B 的 set key 333 命令后执行
- 所以上图中,key 的最终值为 222
注意:
- 上述场景便可使用 watch 命令来监控这个 key
- 看看这个 key 在事务 MULTI 和 exec 之间,是否被 外部其他客户端修改
WATCH 实现原理
- watch 的实现 类似于一个 乐观锁
- Redis 的 watch 命令相当于基于 版本号 这样的机制来进行实现的乐观锁
- 这样的设定 在 CAS 这里 ABA 问题中也涉及过 思想方法 还是 实现上都是非常相似的
总结
- Redis 事务,要比 MySQL 事务要简单很多
- 原子性:Redis 事务,不支持回滚
- 一致性:Redis 并不会保证事务执行前和执行后,数据统一
- 持久性:Redis 主要通过内存来存储数据
- 隔离性:Redis 自身作为一个单线程的服务器模型,上面处理的请求本质上都是串行执行的
- Redis 中的 lua 脚本,也能起到类似于事务的效果
- 官方网站上,事务这里的任何能实现的效果,都可以使用 lua 脚本代替