redis事务与Lua脚本

1. Redis 原生事务

Redis 的事务通过 MULTI, EXEC, DISCARD, WATCH 等命令实现。

  • 工作机制

    1. MULTI:开启一个事务,后续的命令都会被放入一个队列中,而不会立即执行。

    2. 输入命令:将多个命令按顺序加入队列。

    3. EXEC:一次性、按顺序地执行队列中的所有命令。

    4. WATCH:在 MULTI 之前执行,用于监控一个或多个键,如果在 EXEC 执行前这些键被其他客户端修改,则整个事务会失败(返回 nil)。

  • 特点

    • 原子性EXEC 命令触发时,事务中的所有命令会作为一个独立的、连续的操作序列被执行。在 EXEC 命令执行期间,Redis 服务器不会处理其他客户端的任何命令。这保证了事务的原子性。

    • 没有回滚 :这是 Redis 事务最著名的特点。如果事务中的某个命令执行失败(例如,对字符串执行了 HGET 操作),后续的命令仍然会继续执行,并且之前已经执行的命令也不会回滚。Redis 认为这种错误通常是编程错误,应该在开发阶段被发现,而不是通过复杂的回滚机制来处理。

    • 隔离性 :通过 WATCH 实现乐观锁,可以保证在事务执行时,被监控的键没有被其他客户端修改,从而提供隔离性。

2. Lua 脚本

Redis 从 2.6 版本开始内置了对 Lua 脚本的支持。

  • 工作机制

    • 使用 EVALSCRIPT LOAD + EVALSHA 来执行一段 Lua 脚本。

    • 脚本中可以包含多个 Redis 命令和复杂的逻辑(如条件判断、循环等)。

  • 特点

    • 真正的原子性整个 Lua 脚本在执行时会被当作一个单命令 。脚本在执行过程中,不会被其他任何命令或脚本打断。这是比 MULTI/EXEC 更严格的原子性。

    • 减少网络开销:可以将多个操作打包在一个脚本中,一次发送,一次返回,显著减少网络延迟。

    • 复杂性:可以在脚本中实现复杂的业务逻辑,这是简单的事务命令队列无法做到的。

    • 可复用性 :通过 SCRIPT LOADEVALSHA,可以预加载脚本并多次调用,避免重复传输脚本内容。


对比总结:为什么 Lua 脚本通常是更好的选择?

特性 Redis 事务 Lua 脚本
原子性 命令级别EXEC 期间原子,但某个命令失败不影响后续。 脚本级别:整个脚本作为一个原子单位执行,要么全成功,要么全不执行。
错误处理 部分失败,无回滚。 如果脚本语法错误或 redis.call() 出错,整个脚本都不会执行。
复杂性 只能将命令简单排队,无法加入逻辑(如 if-else)。 支持复杂逻辑,可以包含条件、循环、局部变量等。
网络开销 需要发送 MULTI,N个命令,EXEC,共 N+2 次网络往返。 一次网络往返,将脚本和参数一次性发送。
性能 事务中的命令在排队时不会被解析,在 EXEC 时一次性解析和执行。 脚本在传输后被缓存,执行效率非常高。
WATCH 乐观锁 支持,是保证 CAS 操作的核心。 不支持 在脚本内直接使用 WATCH,但脚本的原子性本身就解决了大部分竞态条件问题。
阻塞风险 事务中的命令应都是快速操作。 编写糟糕的脚本(如包含长循环或死循环)会长时间阻塞整个 Redis 服务器,是危险的。

关键区别和选择场景

  1. 需要条件逻辑时,必须使用 Lua 脚本

    • 例如:只有在库存大于 0 时才执行扣减和创建订单。这在事务中无法实现,因为事务只是命令队列,无法根据中间结果做判断。
  2. 需要更严格的原子性和一致性时,推荐 Lua 脚本

    • 由于 Lua 脚本的"全有或全无"特性,它更适合需要强一致性的场景。而原生事务的"部分失败"特性需要客户端进行更复杂的错误处理。
  3. 当需要 WATCH 监控多个键,且逻辑简单时,可以使用原生事务

    • 虽然 Lua 脚本本身是原子的,但有些场景需要读取一个键的值,然后根据这个值(可能被其他客户端改变)来决定是否修改另一个键 。这种跨键的、依赖于外部状态的 CAS 操作,使用 WATCH + 事务仍然是标准做法。

    • 举例 :你想在用户积分(key A)大于 100 时,给他发一条消息(修改 key B)。你需要先 WATCH 积分 key,然后 MULTI,在事务中检查积分并决定是否发消息。如果在 EXEC 前积分被其他客户端修改,事务会失败,你可以重试。如果这个逻辑写在 Lua 脚本里,脚本读取积分的那一刻值是确定的,但无法感知到在它执行前这个积分是否已经被其他客户端修改过(因为 WATCH 机制在脚本外)。

  4. 性能敏感和网络延迟高的场景,优先使用 Lua 脚本

    • 一次网络往返的优势非常明显。

结论

Lua 脚本在功能上是 Redis 事务的超集 ,它提供了更强大的原子性、更丰富的逻辑能力和更低的网络开销。因此,对于绝大多数原本计划使用事务的场景,用 Lua 脚本来实现是更好、更推荐的选择。

唯一需要保留使用原生事务的情况是 :你需要使用 WATCH 来实现一个涉及多个键 的、逻辑简单的、依赖于在事务执行前键值是否被改变的乐观锁控制。即便如此,也可以考虑是否能用 Lua 脚本将相关键的操作合并到一个脚本中,从而从根本上避免竞态条件。

简而言之:能用 Lua 脚本就用 Lua 脚本。只有在 WATCH 是必需且逻辑无法融入单个脚本时,才使用原生事务。

相关推荐
科研面壁者1 小时前
SPSS——独立样本T检验
数据库·人工智能·机器学习·信息可视化·数据分析·spss·数据处理
云栈开源日记1 小时前
Python 开发技术栈梳理:从数据库、爬虫到 Django 与机器学习
数据库·爬虫·python·学习·机器学习·django
倔强的石头1062 小时前
openGauss数据库:从CentOS 7.9部署到实战验证
linux·数据库·centos
4***14904 小时前
MySQL调试技巧与工具
数据库·mysql
Arva .4 小时前
如何监控并优化慢 SQL?
数据库·sql
w***4247 小时前
【MySQL】复合查询
数据库·mysql
q***01777 小时前
【MySQL】表的基本操作
数据库·mysql·oracle
budingxiaomoli7 小时前
存储过程和触发器
数据库
q***12537 小时前
PostgreSQL_安装部署
数据库·postgresql
q***48258 小时前
mysql用户名怎么看
数据库·mysql