惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 青石路

Claude Code安装,接入阿里云百炼模型,蹭蹭免费额度 异源数据同步 → 记一次 DataX 已同步数据量优化 明明连接的是Redis的DB0,为什么能查到DB3的数据? TINYINT(1) 类型的字段,明明数据存的是 2,为什么查出来是 true 用了MySQL的INSERT ON DUPLICATE KEY UPDATE,怎么还报唯一索引冲突错误 关于布尔类型的变量不要加 is 前缀,被网友们吐槽了,特来完善下 都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑我了嘛 安全漏洞修复导致SpringBoot2.7与Springfox不兼容,问题排查与处理 记一次SQL隐式转换导致精度丢失问题的排查 → 不规范就踩坑 经由同个文件多次压缩的文件MD5都不一样问题排查,感慨AI的强大! 记一次cannot access its superinterface问题的的排查 → 强如Spring也一样写Bug SpringBoot支持Kafka多源配置的同时还要支持启停配置化,是真的会玩 如果XXL-JOB执行器在执行某任务中被重启了,重启后该任务能够被自动弥补调度吗 Spring Boot读取外部配置文件失败,原因绝对出乎你意料 不依赖 Spring,你会如何自实现 RabbitMQ 消息的消费(一) 异源数据同步 → DataX 同步启动后如何手动终止? 异源数据同步 → 如何获取 DataX 已同步数据量? 记一次 RabbitMQ 消费者莫名消失问题的排查 不升级 POI 版本,如何生成符合新版标准的Excel 2007文件 以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题? maven 插件之 maven-shade-plugin,解决同包同名 class 共存问题的神器
那些年不该放到事务中的操作,你实现过哪些
青石路 · 2025-09-12 · via 博客园 - 青石路

开心一刻

一天在公厕里,忽然听到厕间有人说话:朋友,有手纸吗

我翻了翻口袋:抱歉,没有

过了几秒钟,那人又问:朋友,有小块报纸吗

我无奈一笑,说到:对不起,没有,我只是来尿尿

又过了几秒钟,厕间门缝塞出一张10元人民币:朋友,能破成10张1块的吗

我默默的接过10元,掏出10个钢镚递了过去:朋友,10个够吗,不够我兜里还有

开心一刻

事务最小化

关于 事务最小化 原则

尽可能缩短事务的持续时间、减少事务内部的操作数量和锁定的数据量

我相信大家都知道,也都是这么执行的

哪些操作应该是一个事务,你们肯定也知道

但哪些操作不应该放到事务中,你们肯定容易忽略,为什么是 肯定,因为我也经常忽略

贱打

接下来,我们一起捋一下那些不该放到事务中的操作,来看看你们是不是也这么干过

消息发送

我们来看一个场景,上游系统完成数据持久化后,往下游推送一条消息

@Override
@Transactional(rollbackFor = Exception.class)
public boolean update(User user) {
    // 更新数据
    boolean updated = this.saveOrUpdate(user);
    // TODO 其他持久化操作
    // 往下游发送消息
    rabbitTemplate.convertAndSend(QSL_FANOUT_EXCHANGE, null, user.getUserId());
    return updated;
}

下游收到消息后,通过 HTTP 请求从上游获取数据,然后进行相关处理

@Value("${qsl-front.url}")
private String frontUrl;

@Resource
private RestTemplate restTemplate;

@Override
@RabbitListener(queues = QSL_QUEUE)
public void onMessage(Message message, Channel channel) {
    String userId = new String(message.getBody(), StandardCharsets.UTF_8);
    log.info("收到front消息,userId={}", userId);
    // 1、调接口查询数据
    ResponseEntity<User> userResp = restTemplate.getForEntity(frontUrl.replace("{userId}", userId), User.class);
    if (HttpStatus.OK != userResp.getStatusCode()) {
        log.error("查询front接口失败,status = {}", userResp.getStatusCode());
        // TODO 请求失败处理
    } else {
        User user = userResp.getBody();
        log.info("front接口响应值:{}", user);
        // TODO 用 user 数据进行业务处理
    }
}

这代码有没有很眼熟,你们平时是不是也经常这么写?

我们来分析下,这代码是不是有 bug

有没有可能下游收到消息,然后通过 HTTP 请求上游查 User 数据时,上游事务还未提交?

如果有可能,那下游查到的 User 数据是不是有可能是旧的?

如果 User 数据是旧的,下游业务处理是不是就不对了?

上游 UPDATE 操作,下游查到的数据可能是旧的,如果上游是 INSERT 操作,下游是不是可能都查不到数据?

这代码确实有 bug 吖!

除了常规的 Message QueueKafkaRacketMQRabbitMQActiveMQRedis 也能实现消息队列的功能,用这些组件实现上述功能的时候,都有可能出现类似问题

异步处理

在同个系统中,有些费时的操作需要异步处理,我们会这么实现

@Override
@Transactional(rollbackFor = Exception.class)
public boolean update(User user) {
    // 更新 User
    boolean updated = this.saveOrUpdate(user);
    // TODO 其他持久化操作
    // 异步处理
    CompletableFuture.runAsync(() -> {
		// 费时处理
    });
    return updated;
}

如果费时处理中,去数据库中查询了当前 User,并且用到该 User 的相关数据,是不是就出现上述 消息发送 中的问题呢?

事务还未提交,异步处理中查询 User,查到的旧的,甚至查不到,这是不是 bug

你们可能会说,异步处理的时候把 User 作为参数传进去,不去数据库查,不就没问题呢?

你们说的非常对,但如果实现更 装逼 一些,引入 生产者与消费者 模式

事务中往队列添加消息,作为 生产者,费时处理作为 消费者

考虑到消息体的简单性,往往只会传递相关 id,消费者消费的过程中再通过这些 id 去查数据库

查到的数据是不是就可能是旧的,或者查不到?

RPC

分布式系统和微服务架构,肯定少不了远程调用

RPC : Remote Procedure Call

如果在事务中进行远程调用,例如

@Override
@Transactional(rollbackFor = Exception.class)
public boolean update(User user) {
    // 更新 User
    boolean updated = this.saveOrUpdate(user);
    // TODO 其他持久化操作
    // 远程调用vip服务
    vipServer.update(user.getId());
    return updated;
}

vip 服务的 update 中,根据 id 查询 User 信息

是不是也会出现查到的是旧数据,甚至查不到?

拎出非事务操作

如果确定有些操作不需要放到一个事务中,一定要把这些操作从事务中拎出来,保证事务最小化

怎么拎,我已经替你们总结好

事务提交之后再执行某些操作 → 你有哪些实现方式?

如果涉及到分布式事务,那就要用分布式事务解决方案了,RocketMQ 事务消息是方案之一

关于 RocketMQ 事务消息的正确打开方式 → 你学废了吗

总结

  1. 事务最小化原则

    尽可能缩短事务的持续时间、减少事务内部的操作数量和锁定的数据量

    相关的查询也尽量不要放到事务中

    能够大大降低死锁概率,同时也能大大提高系统吞吐量和并发量

  2. 从事务中拎出非事务操作

    推荐 2 种做法

    1. 新增一层 Manager,先调事务操作,然后调非事务操作;Manager 中不要加事务注解
    2. TransactionSynchronizationManager,Spring 框架中提供的一个工具类,操作事务很方便
  3. RocketMQ 事务消息只能保证最终一致性,并不能做到事务回滚