[Redis#16] 事务 | vs Mysql | 命令 | WATCH的实现

目录

什么是事务

实现事务的方式

[Redis 事务与 MySQL 事务的对比](#Redis 事务与 MySQL 事务的对比)

应用场景:防止超卖

[Lua 脚本增强](#Lua 脚本增强)

事务操作

[MULTI & EXEC](#MULTI & EXEC)

DISCARD

WATCH

[WATCH 的实现原理](#WATCH 的实现原理)


什么是事务

[MySQL#12] 事务(1) | ACID | commit | 回滚 | 常见操作

Redis 的事务和 MySQL 的事务概念上是类似的,都是把一系列操作绑定成一组,让这一组能够批量执行。然而,需要注意的是 Redis 事务与 MySQL 事务存在以下几点区别:

  • 弱化的原子性 : Redis 没有 "回滚机制"。它只能保证这些操作 "批量执行",但不能做到 "一个失败就恢复到初始状态"。
  • 不保证一致性: Redis 不涉及 "约束",也没有回滚机制。MySQL 的一致性体现在运行事务前后结果的合理性,不会出现中间非法状态。
  • 不需要隔离性 : Redis 事务没有隔离级别,因为 Redis 是单线程处理请求,不会并发执行事务。
  • 不需要持久性 : Redis 数据保存在内存中,是否开启持久化由 redis-server 自行决定,这与事务无关。

Redis 事务本质上是在服务器上创建了一个 "事务队列"。

每次客户端在事务中进行一个操作,都会先将命令发送给服务器并放入 "事务队列"(但并不会立即执行),而是在收到 EXEC 命令后,才真正执行队列中的所有操作。

  • 这么多特性Redis都不具备,那么Redis的事务到底有什么意义呢?
    Redis的事务最主要的意义就是为了"打包",避免其他客户端的命令插队到中间.

最开始官网还说原子性,后来就把这句话给删了,官方也是有点虚的


实现事务的方式

  • Redis 在实现事务时引入了队列机制。每个客户端都有一个独立的队列。
  • 当开启事务时,客户端输入的命令会发送给服务器并进入这个队列中,而不是立即执行。
  • 只有当遇到 "执行事务" 的命令(如 EXEC)时,才会把队列中的任务按照录入顺序依次执行。
  • 这些事务操作是在 Redis 主线程中完成的。
  • 主线程会确保将事务中的所有操作执行完毕后,再处理其他客户端的请求。
Redis 事务与 MySQL 事务的对比

虽然 Redis 事务的功能没有 MySQL 强大,但 MySQL 为了实现强大的事务功能也付出了不小的代价:

  • 空间上 :MySQL 需要花费更多空间来存储额外的数据,例如用于回滚段和多版本控制。
  • 时间上 :MySQL 的事务处理需要更大的执行开销,包括锁管理、日志记录等。

正因为 MySQL 事务存在上述问题,Redis 提供了一种轻量级的解决方案,适用于某些特定场景。

应用场景:防止超卖

在购物网站秒杀场景中,可能会出现超卖的情况,即商家放了5000个商品,但由于并发问题导致下单成功了5001个。为避免这种情况,在多线程环境中我们曾通过加锁的方式来解决。

如果引入 Redis,则可以直接使用其事务机制解决问题。

例如,考虑两个客户端几乎同时下单的情况:

  • 客户端1开启一个事务,该事务被放入 Redis 的事务队列中;
  • 客户端2同样开启一个事务,并被放入队列;
  • 当事务1收到执行命令被执行时,它会减少库存计数(count--);
  • 当轮到事务2执行时,由于此时 count>0 的条件不再满足,所以事务2将不会改变数据库状态,从而避免了超卖问题。
Lua 脚本增强

值得注意的是,尽管 Redis 本身不支持像编程语言那样的条件判断(如 if),但它可以引入 Lua 脚本来实现复杂的逻辑判断。通过 Lua 脚本,我们可以实现上述条件的判断逻辑,进一步强化 Redis 事务的应用能力。


事务操作

MULTI & EXEC
  • multi:开启事务
  • exec:提交事务

示例:

开启事务后,不论输入什么指令,都返回QUEUE,此时会在客户端维护一个队列,将所有指令入队列。最后执行exec提交事务,所有的指令再同时返回。

DISCARD
  • discard:取消事务

示例:

开启事务后,输入两个值,再通过discard取消事务,最后查询key1,发现插入失败,因为这个请求没有提交给客户端,而是直接被取消了。

另外的,如果在事务执行的过程中,Redis崩溃,恢复后效果和discard一样,就是事务内的操作都取消了。

WATCH

现有以下场景:

  • 从时间上来看,客户端1 是先发送了 set key 222,客户端2 是后发送了 set key 333
  • 由于 客户端1 中,得是 exec 执行了,才会真正执行 set key 222 这个操作
  • set key 222 变成了实际上更晚执行的操作!! 最终值就是 222

在事务执行过程中,客户端1无法感知外部的变化,客户端2的命令被 客户端 1 事务覆盖了。

  • watch:让事务可以监听外部的变化,如果监听的数据被外部改变,操作失效

语法:

左侧的终端,在执行事务前开启了watch key,开启事务后set key 222。在事务提交前,右侧终端修改了key 333,随后左侧终端提交事务时,返回了nil表示事务操作无效。

因为watch监听到了key被外部修改,此时自己的事务提交可能会影响其它客户端,于是取消该操作。

如果想要取消watch,可以使用unwatch指令:

unwatch

这个指令没有参数,一次性取消所有key的监听。

WATCH 的实现原理

WATCH 的实现原理类似于我们在并发编程中学习的乐观锁机制,即解决 CAS(Compare-And-Swap)中的 ABA 问题的策略。乐观锁假设冲突很少发生,并在检测到冲突时采取相应的措施。与 ABA 问题中的实现策略相似,Redis 的 WATCH 基于版本号机制实现了乐观锁。

watch使用了一种版本号的机制,每个数据都有一个版本号,每次修改key的值时,都会修改其版本号。

  • 在watch key时,会记录当前的版本号。
  • 在事务提交时,检测当前的版本号是否与之前的版本号相同,如果相同那么提交成功,如果版本号不同,说明有别的用户修改了数据,导致版本号修改,当前事务将不会执行并返回失败。

WATCH 本质上是给 EXEC*++加上了一个对特定 key 的判定条件++:*只有当所有被 WATCH 的 key 自从 WATCH 开始以来没有被修改过的情况下,事务才会被执行;否则,事务将被取消。

举例说明

可以将 WATCH 比喻为判断老公是否出轨的情景:

  • 离开家之前:把枕头放到一个特定的位置,并拍下一张照片作为记录。
  • 回家之后:检查枕头的位置是否发生了改变。如果位置变了,则可以推测出在这段时间内有人移动了枕头(在这个比喻中意味着老公可能出轨了)。

同样的逻辑应用于 Redis 中:

  • WATCH key3:开始监视 key,此时记录下 key 的状态(版本号)。
  • 尝试执行事务 :如果在此期间 key 被其他客户端修改过,那么提交事务时就会发现 key 的状态已经改变,从而导致当前事务执行失败。如下就返回了 Nil

乐观锁与悲观锁

  • 乐观锁:乐观锁假设在加锁之前,锁冲突的概率较低。因此,在加锁之前不会进行任何检查,而是直接进行操作。++如果在操作完成后发现冲突,则会进行重试。++
  • 悲观锁:悲观锁假设在加锁之前,锁冲突的概率较高。因此,在加锁之前会进行检查,确保没有其他线程正在操作该资源。++如果发现冲突,则会阻塞等待。++

实现示例

  • 在 C++ 和 Linux 中涉及的锁,如 mutex 和 std::mutex,都是悲观锁。
  • Java 中的 synchronized 关键字则可以在悲观/乐观之间自适应。

事务命令 sum:

  • MULTI:开启一个事务,执行成功返回 OK。
  • EXEC:真正执行事务。每个操作加入事务时会提示 "QUEUED",表示命令已经进入客户端的队列,直到 EXEC 执行时才会真正发送给服务器。
  • DISCARD:放弃当前事务,清空事务队列,之前的操作都不会真正执行。
  • WATCH:用于监控一组具体的 key,在提交事务时如果发现 key 被其他客户端修改过,则事务执行失败。
  • UNWATCH:取消对 key 的监控,相当于 WATCH 的逆操作。
相关推荐
涛ing1 小时前
32. C 语言 安全函数( _s 尾缀)
linux·c语言·c++·vscode·算法·安全·vim
__雨夜星辰__1 小时前
Linux 学习笔记__Day2
linux·服务器·笔记·学习·centos 7
大耳朵土土垚1 小时前
【Linux】日志设计模式与实现
linux·运维·设计模式
学问小小谢1 小时前
第26节课:内容安全策略(CSP)—构建安全网页的防御盾
运维·服务器·前端·网络·学习·安全
yaoxin5211232 小时前
第十二章 I 开头的术语
运维·服务器
ProgramHan2 小时前
1992-2025年中国计算机发展状况:服务器、电脑端与移动端的演进
运维·服务器·电脑
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS体育馆管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
bohu834 小时前
亚博microros小车-原生ubuntu支持系列:16 机器人状态估计
ubuntu·机器人·imu·localization·microros·imu_tools
计算机-秋大田6 小时前
基于微信小程序的电子竞技信息交流平台设计与实现(LW+源码+讲解)
spring boot·后端·微信小程序·小程序·课程设计