当前位置: 首页 > news >正文

【redis】事务

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

Redis事务的特点

其核心特点包括:

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

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

Redis事务的本质

命令队列机制

通过三个阶段实现事务:

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

单线程保障

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

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

Redis事务命令使用

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

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

基础命令演示

事务提交成功的演示:

# 开启事务
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

事务取消的演示:

# 开启事务
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

高级用法:乐观锁

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

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

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

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操作,这时候错误命令之后的命令还会继续执行,这样原子性就被破坏了。

语法错误导致全体失败

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.

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

运行时错误部分成功

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结合,减少事务命令的网络传输时间:

# 将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脚本原子扣减库存
if redis.call('GET', KEYS[1]) >= ARGV[1] then
    return redis.call('DECRBY', KEYS[1], ARGV[1])
else
    return -1
end

Java中使用Redis事务

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

}

相关文章:

  • Reflect.get和target[key]有何不同?
  • C++学习之QT高级
  • FPGA前端设计适合哪些人学?该怎么学?
  • 越早越好!8 个反直觉的金钱真相|金钱心理学
  • System 类的核心 API
  • Deepseek应用技巧-chatbox搭建前端问答
  • 语音识别-FunASR-docker部署-【超简洁步骤】
  • Manus:成为AI Agent领域的标杆
  • 题解:CF633D Fibonacci-ish
  • 如何安装MySQL,以及数据库表格创建并插入数据
  • 微前端如何拯救大型项目
  • Sglang部署大模型常用参数详解
  • 《 PyQt5》—— 创建 Python GUI(图形用户界面)
  • 《PyQt5》——设计Python GUI(图形用户界面)实例
  • 开关模式电源转换器 EMI/EMC 的集成仿真
  • 查看电脑信息
  • STM32的Systick定时器的作用
  • 每日一题——只出现一次的数字
  • vue3:一文理解h函数的使用
  • SpringbootActuator未授权访问漏洞
  • 孟夏韵评《无序的学科》丨误读与重构的文化漂流
  • 纪念|脖子上挂着红领巾的陈逸飞
  • 国家统计局:2024年城镇单位就业人员工资平稳增长
  • 《日出》华丽的悲凉,何赛飞和赵文瑄演绎出来了
  • 泉州围头湾一港区项目炸礁被指影响中华白海豚,官方:已叫停重新评估
  • 350种咖啡主题图书集结上海,20家参展书店买书送咖啡