Redis进阶 事务:Redis事务详解

Redis事务相关命令 Redis事务执行步骤

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。@pdai

什么是Redis事务

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务相关命令和使用

MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。

  • MULTI :开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列。
  • EXEC:执行事务中的所有操作命令。
  • DISCARD:取消事务,放弃执行事务块中的所有命令。
  • WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
  • UNWATCH:取消WATCH对所有key的监视。

标准的事务执行

给k1、k2分别赋值,在事务中修改k1、k2,执行事务后,查看k1、k2值都被修改。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"22"
127.0.0.1:6379>

事务取消

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 33
QUEUED
127.0.0.1:6379> set k2 34
QUEUED
127.0.0.1:6379> DISCARD
OK

事务出现错误的处理

  • *语法错误(编译器错误)

在开启事务后,修改k1值为11,k2值为22,但k2语法错误,最终导致事务提交失败,k1、k2保留原值。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> sets k2 22
(error) ERR unknown command sets, with args beginning with: k2, 22,
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
  • *Redis类型错误(运行时错误)

在开启事务后,修改k1值为11,k2值为22,但将k2的类型作为List,在运行时检测类型错误,最终导致事务提交失败,此时事务并没有回滚,而是跳过错误命令继续执行, 结果k1值改变、k2保留原值

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k1 v2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 11
QUEUED
127.0.0.1:6379> lpush k2 22
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>

CAS操作实现 乐观锁

WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。

  • CAS? 乐观锁?Redis官方的例子帮你理解

被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。

举个例子, 假设我们需要原子性地为某个值进行增 1 操作(假设 INCR 不存在)。

首先我们可能会这样做:

val = GET mykey
val = val + 1
SET mykey $val

上面的这个实现在只有一个客户端的时候可以执行得很好。 但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。举个例子, 如果客户端 A 和 B 都读取了键原来的值, 比如 10 , 那么两个客户端都会将键的值设为 11 , 但正确的结果应该是 12 才对。

有了 WATCH ,我们就可以轻松地解决这类问题了:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上面的代码, 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。

这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。

  • watch是如何监视实现的呢

Redis使用WATCH命令来决定事务是继续执行还是回滚,那就需要在MULTI之前使用WATCH来监控某些键值对,然后使用MULTI命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。

当使用EXEC执行事务时,首先会比对WATCH所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,Redis都会取消执行事务前的WATCH命令。

Redis进阶 事务:Redis事务详解

著作权归https://pdai.tech所有。 链接:https://pdai.tech/md/db/nosql-redis/db-redis-x-trans.html

  • *watch 命令实现监视

在事务开始前用WATCH监控k1,之后修改k1为11,说明事务开始前k1值被改变,MULTI开始事务,修改k1值为12,k2为22,执行EXEC,发回nil,说明事务回滚;查看下k1、k2的值都没有被事务中的命令所改变。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> WATCH k1
OK
127.0.0.1:6379> set k1 11
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 12
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> EXEC
(nil)
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
  • *UNWATCH取消监视
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> WATCH k1
OK
127.0.0.1:6379> set k1 11
OK
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 12
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379> get k1
"12"
127.0.0.1:6379> get k2
"22"
127.0.0.1:6379>

Redis事务执行步骤

通过上文命令执行,很显然Redis事务执行是三个阶段:

  • 开启:以MULTI开始一个事务
  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
  • 执行:由EXEC命令触发事务

当一个客户端切换到事务状态之后, 服务器会根据这个客户端发来的不同命令执行不同的操作:

  • 如果客户端发送的命令为 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令的其中一个, 那么服务器立即执行这个命令。
  • 与此相反, 如果客户端发送的命令是 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令以外的其他命令, 那么服务器并不立即执行这个命令, 而是将这个命令放入一个事务队列里面, 然后向客户端返回 QUEUED 回复。

Redis进阶 事务:Redis事务详解

著作权归https://pdai.tech所有。 链接:https://pdai.tech/md/db/nosql-redis/db-redis-x-trans.html

更深入的理解

我们再通过几个问题来深入理解Redis事务。

为什么 Redis 不支持回滚?

如果你有使用关系式数据库的经验, 那么 ” Redis 在事务失败时不进行回滚,而是继续执行余下的命令“这种做法可能会让你觉得有点奇怪。

以下是这种做法的优点:

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

有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。

如何理解Redis与事务的ACID?

一般来说,事务有四个性质称为ACID,分别是原子性,一致性,隔离性和持久性。这是基础,但是很多文章对Redis 是否支持ACID有一些异议,我觉的有必要梳理下:

  • *原子性atomicity

首先通过上文知道 运行期的错误是不会回滚的,很多文章由此说Redis事务违背原子性的;而官方文档认为是遵从原子性的。

Redis官方文档给的理解是, Redis的事务是原子性的:所有的命令,要么全部执行,要么全部不执行。而不是完全成功。

  • *一致性consistency

redis事务可以保证命令失败的情况下得以回滚,数据能恢复到没有执行之前的样子,是保证一致性的,除非redis进程意外终结。

  • *隔离性Isolation

redis事务是严格遵守隔离性的,原因是redis是单进程单线程模式(v6.0之前),可以保证命令执行过程中不会被其他客户端命令打断。

但是,Redis不像其它结构化数据库有隔离级别这种设计。

  • *持久性Durability

redis事务是不保证持久性的,这是因为redis持久化策略中不管是RDB还是AOF都是异步执行的,不保证持久性是出于对性能的考虑。

Redis事务其它实现

  • 基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行,其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完
  • 基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐

参考文章

本文主要参考了

此外还参考了

  • https://www.runoob.com/redis/redis-transactions.html
  • https://zhuanlan.zhihu.com/p/101902825

Original: https://www.cnblogs.com/wangcp-2014/p/16194605.html
Author: 飘飘雪
Title: Redis进阶 事务:Redis事务详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/528930/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • zabbix自定义监控(当会话登录超过三个就报警)

    安装过程在此省略。 1.agent端去修改配置文件 2.调用自定义内容 vim /etc/zabbix/zabbix_agentd.d/login.conf UserParamet…

    Linux 2023年6月6日
    081
  • k8s vs k3s: 差异解析

    2. K3s的优势 小型 K3s 的最大优势是它的尺寸最小(小于 100 MB),这有助于它以最少的设置在小型硬件中启动 Kubernetes 集群。 快速部署 curl -sfL…

    Linux 2023年6月13日
    0108
  • ruoyi接口权限校验

    此文章属于ruoyi项目实战系列 ruoyi系统在前端主要通过权限字符包含与否来动态显示目录和按钮。为了防止通过http请求绕过权限限制,后端接口也需要进行相关权限设计。 @Pre…

    Linux 2023年6月7日
    0145
  • 表单校验

    HTML <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Typ…

    Linux 2023年6月13日
    091
  • http代理连接

    基于Linux服务器的http代理连接 1. 准备工作 &#x76EE;&#x6807;&#x670D;&#x52A1;&#x5668; &…

    Linux 2023年6月14日
    075
  • Windows10 下使用 telnet 命令

    正常情况下 windows 是使用不了 telnet 命令的: 打开控制面板-》程序和功能-》启用或关闭 Windows 功能 勾选 “Telnet客户端”…

    Linux 2023年6月13日
    085
  • linux自动备份mysql数据库

    备份脚本记录一下–(单个数据库) 2021-11-15 1.新建shell脚本:vim **.sh #!/bin/bashCKUP=/data/backup/db #获…

    Linux 2023年5月27日
    0109
  • Shell grep 命令扩展增强

    背景: 线上排查问题需要找出有某个特征的日志,并获取这些日志的完整调用日志链路。 比如, 通过某个tag 获取到了这些日志的 请求id 【Span ID】, 然后还需要再一步取得所…

    Linux 2023年5月28日
    090
  • arch安装桌面环境

    arch可以安装图形用户界面需要的软件包有:xorg-server,xorg-xinit,xfce4 xorg是linux桌面环境下的服务程序,xorg-init是启动xorg的客…

    Linux 2023年6月13日
    081
  • LeetCode 907: 子数组的最小值之和-单调栈的运用 |Sum of Subarray Minimums-Fantasy use of Monotonic Stack

    Tags: #MonoStack #Stack Problem Description Given an array of integers arr, find the sum o…

    Linux 2023年6月13日
    072
  • 源码安装Nginx以及用systemctl管理

    一、源码安装Nginx: 下载 nginx软件包 进入nginx-1.20.1目录 安装依赖 /configure软件检查( ./configure–prefix=/u…

    Linux 2023年6月13日
    088
  • Java轻松实现,每天给对象发情话!

    一、引言 最近看到一篇用js代码实现表白的文章,深有感触。然后发现自己也可以用java代码实现,然后就开始写代码了,发现还挺有意思的,话不多说开搞实现思路: 使用HttpClien…

    Linux 2023年6月14日
    081
  • phpredis中文手册——《redis中文手册》 php版

    将值value关联到key,并将key的生存时间设为seconds(以秒为单位)。 这个命令类似于以下两个命令: O(1) SETRANGE SETRANGE key offset…

    Linux 2023年5月28日
    074
  • Linux ARM中断后的处理(5)【转】

    1. 中断进入自定义函数 在中断发生后,经历ARM通用的处理阶段,到达irq_handler宏,转入C语言阶段。 //arch/arm/kernel/entry-armv.S/**…

    Linux 2023年6月8日
    085
  • ThinkPHP5 远程命令执行漏洞

    一、ThinkPHP介绍 轻量级框架,内部OOP和面向过程代码都存在,是国人自己开发的框架。ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,诞生于2006年初,…

    Linux 2023年6月14日
    073
  • 更改网卡名称

    CentOS7使用了”一致性网络命名方法” 更改配置文件内容 关闭”一致性网络设备命名法” 更新GRUB、内核配置 grub2-mk…

    Linux 2023年6月6日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球