【redis】事务

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

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();  // 解除监控
            }
        }
    }

}
相关推荐
七七知享33 分钟前
Go 语言编程全解析:Web 微服务与数据库十大专题深度精讲
数据库·web安全·网络安全·微服务·golang·web3·webkit
m0_748230941 小时前
Spring Boot 整合 Redis 步骤详解
spring boot·redis·bootstrap
Faith_xzc1 小时前
Doris vs ClickHouse 企业级实时分析引擎怎么选?
大数据·数据库·clickhouse·数据库开发
m0_748235611 小时前
MySQL 实战 4 种将数据同步到ES方案
数据库·mysql·elasticsearch
曼诺尔雷迪亚兹2 小时前
2025年四川烟草工业计算机岗位备考详细内容
数据结构·数据库·计算机网络·算法
天桥下的卖艺者3 小时前
R语言使用scitable包交互效应深度挖掘一个陌生数据库
数据库·r语言·交互
H.ZWei3 小时前
鸿蒙应用开发—数据持久化之SQLite
数据库·华为·sqlite·harmonyos
jay丿3 小时前
Django ORM 中的 RelatedManager 特殊方法
数据库·django·sqlite
残月只会敲键盘3 小时前
Django中的Cookie与Session使用指南
数据库·python·django·sqlite
Allen Bright4 小时前
【MySQL基础-1】MySQL 用户管理指南:创建用户、修改密码与权限分配
数据库·mysql