【redis】事务

简单地说,事务表示一组动作,要么全部执行,要么全部不执行。Redis事务是一组命令的集合,通过MULTI、EXEC等命令实现批量操作的原子性执行。

Redis事务的特点

其核心特点包括:

  • 顺序性:命令按入队顺序执行
  • 隔离性:单线程模型保证事务执行不被打断
  • 弱原子性:不支持回滚机制(运行时错误不会中断后续命令)
  • 乐观锁支持 :通过WATCH命令监控键值变化

注意:Redis事务不同于传统数据库事务,它更类似于命令批处理机制。

Redis事务的本质

命令队列机制

通过三个阶段实现事务:

  1. 开启事务 :MULTI命令创建命令队列
  2. 命令入队:将操作指令存入内存队列(不执行语法检查)
  3. 执行事务 :EXEC触发队列命令的FIFO顺序执行

单线程保障

Redis主线程依次执行事务队列中的命令,保证:

  • 执行期间不会被其他客户端请求打断
  • 天然实现隔离性(串行化执行)

Redis事务命令使用

有关事务类型的命令可以通过help @transactions命令来查看。有关命令的使用可以通过help 命令来查看,例如help exec。

shell 复制代码
127.0.0.1:6379> help @transactions

  DISCARD -
  summary: Discard all commands issued after MULTI
  since: 2.0.0

  EXEC -
  summary: Execute all commands issued after MULTI
  since: 1.2.0

  MULTI -
  summary: Mark the start of a transaction block
  since: 1.2.0

  UNWATCH -
  summary: Forget about all watched keys
  since: 2.2.0

  WATCH key [key ...]
  summary: Watch the given keys to determine execution of the MULTI/EXEC block
  since: 2.2.0

基础命令演示

事务提交成功的演示:

shell 复制代码
# 开启事务
127.0.0.1:6379> multi
OK

# 命令进入队列,并没有直接执行
127.0.0.1:6379(TX)> set xxoo ooxx
QUEUED

# 命令进入队列,并没有直接执行
127.0.0.1:6379(TX)> incr count
QUEUED

# 执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) (integer) 1

事务取消的演示:

shell 复制代码
# 开启事务
127.0.0.1:6379> multi
OK

# 命令进入队列,并没有直接执行
127.0.0.1:6379(TX)> set ooxx xxoo
QUEUED

# 命令进入队列,并没有直接执行
127.0.0.1:6379(TX)> incr count
QUEUED

# 取消事务
127.0.0.1:6379(TX)> discard
OK

高级用法:乐观锁

乐观锁获取锁成功的演示:

shell 复制代码
127.0.0.1:6379> set balance:Morris 1000
OK

# 监视账户余额,类似cas
127.0.0.1:6379> watch balance:Morris
OK

127.0.0.1:6379> multi
OK

127.0.0.1:6379(TX)> decrby balance:Morris 100
QUEUED

127.0.0.1:6379(TX)> incrby balance:Bob 100
QUEUED

127.0.0.1:6379(TX)> exec
1) (integer) 900
2) (integer) 100

乐观锁获取锁失败的演示:

bash 复制代码
127.0.0.1:6379> set balance:Morris 1000
OK

127.0.0.1:6379> watch balance:Morris
OK

127.0.0.1:6379> multi
OK

127.0.0.1:6379(TX)> decrby balance:Morris 100
QUEUED

127.0.0.1:6379(TX)> incrby balance:Bob 100
QUEUED

# 此时balance:Morris被其他客户端修改,事务失败返回(nil)
127.0.0.1:6379(TX)> exec
(nil)

原子性缺陷演示

Redis在事务执行时,如果在命令入队时就有语法错误,比如命令不存在或者参数错误,那么整个事务会被拒绝执行,这时候是原子性的。但如果在执行过程中出现运行时错误,比如对字符串进行INCR操作,这时候错误命令之后的命令还会继续执行,这样原子性就被破坏了。

语法错误导致全体失败

shell 复制代码
127.0.0.1:6379> multi
OK

127.0.0.1:6379(TX)> set k1 v1
QUEUED

# 命令不存在,语法错误
127.0.0.1:6379(TX)> xxx k1 
(error) ERR unknown command `xxx`, with args beginning with: `k1`,

127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.

原子性表现:若存在入队时即可检测的语法错误​(如命令不存在、参数错误),整个事务会被拒绝执行,此时具有原子性。

运行时错误部分成功

shell 复制代码
127.0.0.1:6379> multi
OK

127.0.0.1:6379(TX)> set k1 v1
QUEUED

# 类型错误(对字符串执行SADD)
127.0.0.1:6379(TX)> sadd k1 oo xx
QUEUED

127.0.0.1:6379(TX)> set k2 v2
QUEUED

127.0.0.1:6379(TX)> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK

原子性缺陷:事务中运行时错误(如数据类型不匹配)不会影响后续命令执行,结果中前两条命令生效,第三条失败,导致部分成功

与传统数据库事务对比

特性 Redis事务 MySQL事务
原子性 部分支持(无回滚机制) 完全支持(UNDO日志回滚)
隔离性 天然隔离(单线程模型) 通过锁机制实现多隔离级别
持久性 依赖AOF配置 默认保证(redo日志)
一致性 需配合WATCH实现 外键/约束自动保障
执行时机 命令延迟执行 实时执行

Pipeline和事务的区别

服务端行为VS客户端行为

  • 事务 :是Redis服务端原生支持的原子操作机制,通过MULTI开启事务模式后,服务端会将后续命令存入队列缓存,直到执行EXEC时才一次性按顺序执行所有命令。

  • Pipeline:本质是客户端批量发送命令的优化策略,服务端无法区分普通命令与Pipeline命令,仅作为连续的命令请求处理。

原子性保障

  • 事务 :提供弱原子性 保证,事务队列中的命令在EXEC执行时会作为整体串行执行,但运行时错误(如类型操作错误)不会中断后续命令

  • Pipeline:完全不具备原子性,命令可能被其他客户端的操作打断,仅在命令量少(能被内核缓冲区容纳)时可能保持原子性

执行流程对比

特性 事务 Pipeline
命令执行时机 EXEC触发批量执行 立即发送到服务端
网络交互次数 MULTI+命令+EXEC共3次往返 单次批量发送所有命令
错误处理 语法错误导致全体失败,运行时错误部分执行 无特殊处理,按普通命令逐条响应
阻塞风险 长事务可能阻塞其他请求 无服务端阻塞风险

应用场景与优化策略

适用场景

  • 事务 :需要简单原子性操作的场景(如库存扣减、转账),配合WATCH实现乐观锁控制

  • Pipeline:批量非原子性操作(如日志写入、数据预热),减少高并发场景下的网络开销

组合优化方案

可将事务与Pipeline结合,减少事务命令的网络传输时间:

python 复制代码
# 将MULTI/EXEC封装在Pipeline中
pipe = redis.pipeline(transaction=True)
pipe.multi()
pipe.set('a', 1)
pipe.incr('b')
pipe.execute()  # 单次网络请求完成事务

特殊注意事项

事务的局限性:

  • 不支持回滚机制,需通过WATCH+重试策略实现数据一致性
  • 事务内不能执行阻塞命令(如BLPOP),否则会退化为普通命令

Pipeline的风险点:

  • 大流量Pipeline可能触发服务端缓冲区溢出(需配置client-output-buffer-limit)
  • Cluster模式下需确保所有命令指向同一节点

选型建议

指标 选择事务 选择Pipeline
需要原子性 ✔️ ❌
高吞吐量需求 ❌(事务阻塞风险) ✔️
涉及跨键操作 ✔️(配合WATCH) ❌
命令间存在逻辑依赖 ✔️(Lua脚本更优) ❌

扩展方案:对于需要强原子性的复杂操作,推荐使用Lua脚本替代事务,可实现真正的原子性执行。

lua 复制代码
-- Lua脚本原子扣减库存
if redis.call('GET', KEYS[1]) >= ARGV[1] then
    return redis.call('DECRBY', KEYS[1], ARGV[1])
else
    return -1
end

Java中使用Redis事务

java 复制代码
package com.morris.redis.demo.transaction;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;

/**
 * jedis中事务的使用
 */
public class JedisTransactionDemo {

    public static void main(String[] args) {

        try (Jedis jedis = new Jedis("localhost")) {
            // 监控关键键
            jedis.watch("balance:Morris");

            Transaction tx = jedis.multi();
            try {
                tx.decrBy("balance:Morris", 100);
                tx.incrBy("balance:Bob", 100);
                List<Object> results = tx.exec();  // 提交事务
                System.out.println("转账成功:" + results);
            } catch (Exception e) {
                tx.discard();  // 放弃事务
                System.out.println("数据被修改,事务回滚");
            } finally {
                jedis.unwatch();  // 解除监控
            }
        }
    }

}
相关推荐
时间静止不是简史19 小时前
生产事故排查:Redis 宕机导致补偿队列消费线程死亡,通话录音未上送业务平台
java·redis
2301_32241428041 天前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
catoop1 天前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
Web极客码1 天前
我用 Claude 分析 Redis 哨兵日志,实现跨机房数据不一致自动对齐
数据库·redis·php
库库的数据库笔记1 天前
国产信创数据库的MySQL兼容性深度对比解析
大数据·数据库·mysql·oceanbase·政务
microrain1 天前
SagooIoT 的缓存换后端只改一行配置,文件后端连 Redis 都不需要
物联网·缓存·golang·sagooiot
姚远Oracle ACE1 天前
为什么无法连接到 PostgreSQL?
数据库·postgresql
ss2731 天前
AI时代首先数据库-PostgreSQL实战笔记 | 22 PostgreSQL实例与集群
数据库·笔记·postgresql
ss2731 天前
AI时代首先数据库-PostgreSQL实战笔记 | 21 PostgreSQL进程与内存架构介绍
数据库·笔记·postgresql