百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

为什么 Redis 不支持事务回滚(为什么redis的事务不支持回滚)

mhr18 2025-04-09 18:22 22 浏览 0 评论

Redis 具有两种自动执行多个操作的主要机制:MULTI/EXEC 事务和 Lua 脚本。 Redis 中事务的一大特点是缺乏回滚机制,这常常让新手感到困惑。 在我担任 Redis 开发倡导者期间,我与一些具有传统 SQL 背景的工程师交谈过,他们发现这令人不安,因此我想通过此博客分享我对此主题的看法,并认为在Redis中您不需要回滚。

MULTI/EXEC transactions

Redis 中的事务以 MULTI 命令开始。 一旦发送,连接就会切换模式,并且通过连接发送的所有后续命令将由 Redis 排队,而不是立即执行,但 DISCARD 和 EXEC 除外(这将分别导致事务中止或提交)。 提交事务意味着执行先前排队的命令。

MULTI
SET mykey hello
INCRBY counter 10
EXEC

事务(和 Lua 脚本)确保了两件重要的事情:

  1. 其他客户端不会看到部分状态,这意味着所有客户端都将在应用事务之前或之后看到状态。
  2. 如果节点发生故障,一旦 Redis 重新启动,它将重新加载整个事务,或者不从 AOF 文件中加载任何事务。

关于事务要记住的最后一件基本事情:即使启动了 MULTI 事务,Redis 仍将继续为其他客户端提供服务。 仅当通过调用 EXEC 提交事务时,Redis 才会短暂停止应用其他客户端的命令。 这与 SQL 数据库有很大不同,在 SQL 数据库中,事务利用 DBMS 内的各种机制来提供不同程度的隔离保证,并且客户端可以在执行事务时从数据库读取值。 在 Redis 中,事务是“一次性”的,换句话说,只是一次性执行的一系列命令。 那么,如何创建依赖于 Redis 中数据的事务呢? 为此,Redis 实现了 WATCH,这是一个执行乐观锁定的命令。

Optimistic locking with WATCH

让我从实战层面向您展示为什么在事务中无法从 Redis 读取值:

MULTI
SET counter 42
GET counter
EXEC

如果在redis-cli中运行这一系列命令,“GET counter”的回复将是“QUEUED”,并且只有调用EXEC才会返回值“42”,同时执行返回“OK” 设置命令。

要编写依赖于从 Redis 读取数据的事务,必须使用 WATCH。 一旦运行,该命令将确保只有在调用 EXEC 之前被监视的键没有更改的情况下才会执行后续事务。

例如,如果 INCRBY 不存在,这就是实现原子增量操作的方法:

WATCH counter
GET counter
MULTI
SET counter 
EXEC

在此示例中,我们首先通过“counter”键创建一个 WATCH 触发器,然后获取其值。 请注意 GET 在我们启动事务主体之前是如何发生的,这意味着它将立即执行并返回键的当前值。 此时,我们使用 MULTI 启动事务,并通过在客户端计算“counter”的新值来应用更改。

如果多个客户端尝试同时对“counter”这个key执行相同的事务,则某些事务将被 Redis 自动丢弃。 此时,重试事务通常是客户端的工作。 这类似于 SQL 事务,较高的隔离级别有时会导致事务中止,从而使客户端需要重试它。

Lua scripts

虽然 WATCH 对于执行铰接事务非常有用,但当您需要执行依赖于 Redis 中数据的多个操作时,使用 Lua 脚本通常更容易、更高效。 使用 Lua 脚本,您可以将逻辑发送到 Redis(以脚本本身的形式),并让 Redis 在本地执行代码,而不是像我们在上面的示例中那样将数据推送到客户端。 这样做更快有几个原因,但最重要的一个原因是:Lua 脚本可以从 Redis 读取数据,而不需要乐观锁定。

这就是前一个事务作为 Lua 单行代码实现的方式:

EVAL "redis.call('SET', KEYS[1], tonumber(redis.call('GET', KEYS[1]) or 0) + tonumber(ARGV[1]))" 1 counter 42

在我看来,在一些合理的情况下,您可能会更喜欢使用乐观锁定的事务而不是 Lua:

  • 您的事务所依赖的键不会被频繁修改,这意味着您确信乐观锁定几乎永远不会中止事务。
  • 可能是,第三方服务编写的大量逻辑,因此没有简单的方法可以将该逻辑移动到 Lua 脚本。

除非这两点同时满足,否则都推荐使用 lua。

Errors in transactions

回顾一下:MULTI/EXEC 事务(没有 WATCH)和 Lua 脚本永远不会被 Redis 丢弃,而 MULTI/EXEC + WATCH 将导致 Redis 中止依赖于相应键被监视后更改的值的事务。 Lua 脚本比简单(即无 WATCH)事务更强大,因为它们还可以从 Redis 读取值,并且比“WATCH”事务更高效,因为它们不需要乐观锁定来读取值。

乐观锁的关键点在于,当 WATCHed key 发生更改时,当客户端使用 EXEC 提交事务时,整个事务将立即被丢弃。 Redis 有一个主要的单线程命令执行循环,因此当事务队列正在执行时,不会运行其他命令。 这意味着Redis事务具有真正的可序列化隔离级别,也意味着不需要回滚机制来实现WATCH。

但是当事务出现错误时会发生什么? 答案是Redis将继续执行所有命令并报告发生的所有错误。

更准确地说,Redis 可以在客户端调用 EXEC 之前捕获某些类型的错误。 一个基本的例子是明显的语法错误:

MULTI
GOT key? (NOTE: Redis has no GOT command and, after season 8, it never will)
EXEC

但并非所有错误都可以通过检查命令语法来发现,这些错误可能会导致事务行为异常。 举个例子:

MULTI
SET counter banana
INCRBY counter 10
EXEC

上面的示例将被执行,但 INCRBY 命令将失败,因为“counter”键不包含数字。 这种类型的错误只有在运行事务时才能发现(没关系,在这个简化的示例中,我们是设置错误初始值的人)。

此时此刻,人们可能会说回滚是件好事。 如果不是出于两个考虑,我可能会同意:

  1. 实现回滚所需的快照机制会产生相当大的计算成本。 这种额外的复杂性与 Redis 的理念和生态系统格格不入。
  2. 回滚无法捕获所有错误。 在上面的示例中,我们将“counter”设置为“banana”以显示明显的错误,但在现实世界中,以错误方式使用“counter”键的进程可能会删除它,或者放入一个 例如,信用卡号码。 回滚会增加相当多的复杂性,并且仍然无法完全解决问题。

第二点特别重要,因为它也适用于 SQL:SQL DBMS 提供了许多机制来帮助保护数据完整性,但即使它们也不能完全保护您免受编程错误的影响。 在这两个平台上,编写正确交易的负担仍然由您承担。

Rollbacks in SQL DBMSs

如果这似乎与您使用 SQL 数据库的体验相冲突,让我们看看「依靠报错来强制约束」与「依靠报错来保护数据免受代码中的错误影响」之间的区别。

SQL 中的常见做法是使用索引来实现对数据的约束,并依赖客户端的这些索引来确保正确性。 一个常见的示例是向“用户名”列添加“UNIQUE”约束,以确保每个用户具有不同的用户名。 此时,客户端将尝试插入新用户,并预计当另一个同名用户已存在时插入会失败。

这是 SQL 数据库的完全合法使用,但是依靠约束来实现应用程序逻辑与期望回滚来保护您免受事务逻辑本身的错误有很大不同。

在 AWS re:Invent 2019 上,当一位与会者问我“为什么 Redis 没有回滚功能?” 我的回答是基于:举例人们在 SQL 中使用回滚的原因。 在我看来,这样做的主要原因只有两个:

First reason to use rollbacks: concurrency

最常见的 SQL 数据库是多线程应用程序,当客户端请求高隔离级别时,DBMS 更愿意触发异常,而不是停止为所有其他客户端提供服务。 这对于 SQL 生态系统来说是有意义的,因为 SQL 事务是“喋喋不休的”:客户端锁定几行,读取一些值,计算要应用的更改,最后提交事务。

在 Redis 中,事务并不意味着具有交互性。 Redis 中主事件循环的单线程性质确保事务运行时不会执行其他命令。 这可确保所有事务真正可序列化,而不会违反隔离级别。 当事务使用乐观锁定时,Redis 将能够在执行事务队列中的任何命令之前中止事务,这不需要回滚。

Second reason to use rollbacks: leveraging index constraints

在 SQL 中,通常使用索引约束来实现应用程序中的逻辑。 我提到了 UNIQUE,但这同样适用于外键约束等。 前提是应用程序依赖于已正确配置的数据库并利用索引约束以有效的方式实现逻辑。 但我确信每个人都见过应用程序行为不当,例如,当有人忘记添加 UNIQUE 约束时。

虽然 SQL DBMS 在保护数据完整性方面做得很好,但您不能指望能够避免事务代码中的所有错误。 有一类重要的错误不会违反类型检查或索引约束。

Redis 没有内置索引系统(Redis 模块是另一回事,不适用于此)。 例如,要强制唯一性,您可以使用 Set(或等效)数据类型。 这意味着 Redis 中表达操作的正确方式看起来与 SQL 中的等效方式不同。 Redis的数据模型和执行模型与SQL有很大不同,相同的逻辑操作会根据平台以不同的方式表达,但应用程序必须始终与数据库的状态同步。

尝试 INCRBY 包含非数字值的键的应用程序与期望 SQL 架构与数据库上的内容不一致的应用程序相同。

Redis vs. SQL

如果您有 SQL 背景,您可能会对 Redis 中事务的工作方式感到惊讶,这是可以理解的。 鉴于 NoSQL 已经证明关系数据库并不是存储数据的唯一有价值的模型,因此不要错误地认为任何偏离 SQL 提供的功能本质上都是低劣的。 SQL 事务非常繁琐,基于多线程模型,并与其他子系统进行互操作,以在发生故障时利用回滚。 相比之下,Redis 事务更注重性能,并且没有索引子系统可用于强制执行约束。 由于这些差异,您在 Redis 中使用的事务编写“风格”与 SQL 有着根本的不同。

这意味着 Redis 中缺乏回滚并不会限制表达能力。 所有合理的 SQL 事务都可以重写为功能等效的 Redis 事务,但在实践中并不总是那么容易。 推理最初在 Redis 中的 SQL 中阐明的问题需要您以不同的方式思考数据,并且还需要考虑不同的执行模型。

最后,回滚确实有助于保护数据免受编程错误的影响,但它们并不是解决该问题的方法。 作为基于键值结构的多模型数据库,Redis 无法提供与 SQL 相同级别的“类型检查”便利性,但有一些技术可以帮助实现这一点,正如 Kyle Davis,Head of Developer Advocacy at Redis,在最近的博客文章中进行了解释: Bullet-Proofing Lua Scripts in RedisPy.

也就是说,无论是使用关系数据库还是使用 Redis,您的应用程序都需要与数据库中的内容同步。 对于 Redis 而言,回滚的效用不会超过性能和额外复杂性方面的成本。 如果您想知道 Redis 为何比其他数据库快得多,这也是一个原因。

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: