news 2026/10/17 11:54:51

Spring事务传播行为详解:7种策略与回滚机制实战避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring事务传播行为详解:7种策略与回滚机制实战避坑

先说个真实事故。某天晚上,线上支付回调突然大量重试,原因是订单状态被更新成了“已支付”,但账户余额却一分没扣。查到最后,业务代码里一个Service方法成功提交了订单状态,另一个Service的扣款方法抛了异常,却因为事务传播行为配置不当,让异常被吞掉——扣款回滚了,订单状态却提交了。事后复盘,根子就在@Transactional默认的传播行为上。

这篇东西要解决的,就是Spring事务传播行为这回事。什么场景下内外事务合并?什么场景下必须各管各的?什么场景下允许局部回滚?如果你写过涉及订单、库存、账户、积分这些相互调用的业务,迟早会撞上这些问题。本文会把这7种传播行为逐一拆解,配上完整的代码示例,再把我踩过的坑一并说清楚。

1. 一个事故带出的核心问题:事务边界到底谁说了算

1.1 先搞懂什么是事务传播行为

Java后端做数据一致性,靠的是数据库事务。一个方法加了@Transactional注解之后,Spring会通过AOP在方法执行前开启事务、执行后提交或回滚。这是绝大多数人都知道的第一层。

但业务不可能只有一个方法。很多时候是ServiceA调ServiceB,ServiceB又调ServiceC,每个Service方法都可能带事务注解。这时候问题就来了:B加入A的事务,还是自己开一个新事务?如果B回滚了,A要不要跟着回滚?如果A已经执行了一半,B失败了,A能不能只回滚到调用B之前的状态?

“一个事务方法调用另一个事务方法时,事务该如何传递、如何界定边界”,这就是事务传播行为要回答的问题。Propagation枚举里定义了7种策略:REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED。每一种都规定了方法被执行时,当前线程上如果已经存在事务,应该怎么办;如果不存在事务,又应该怎么办。

1.2 为什么默认的REQUIRED不是万能的

Spring默认用的是REQUIRED,翻译过来就是“有事务就加入,没事务就新建”。这个默认值对大多数单一写操作是合理的,但它掩盖了很多边界问题。

举个例子,一个下单接口里,主流程要写订单表、扣库存、加积分、发消息。这四个操作如果全是REQUIRED,那它们会合并成一个物理事务。好处是只要任何一个环节失败,全部回滚,数据不会出现“订单存在但库存没扣”的中间状态。坏处也很明显:事务粒度太大,一个地方出问题,整个事务标记成rollback-only,就算外层代码捕获了异常,最终也会抛UnexpectedRollbackException。

更麻烦的是日志、通知这类操作。业务本身失败要回滚,但日志希望留下来,REQUIRED做不到这一点,它只会跟着一起回滚。这时候就需要REQUIRES_NEW来开一个独立事务。理解了这一点,就会发现7种传播行为不是理论概念,每一个都有它存在的具体场景,选错了,线上就会出事故。

2. 7种事务传播行为拆解:每种都该在什么场景用

2.1 REQUIRED:默认行为,绝大多数写操作的选择

如果当前存在事务,则加入当前事务; 如果当前不存在事务,则新建一个事务。

这是最常用的一种,定义在Propagation.REQUIRED。它保证同一个事务边界内,多个方法共享同一个数据库事务。假设A方法调B方法,两者都是REQUIRED,A开启事务后,B发现当前线程已经绑定了事务,就直接加入,不新开。

这个传播行为的核心价值是“原子性”。订单保存、库存扣减、明细写入,这些操作必须同时成功或同时失败,就应该统一用REQUIRED。我见过很多项目把所有方法都加上@Transactional(propagation = Propagation.REQUIRED),这本身没错,但如果所有业务都堆在一个大事务方法里,后续拆分时会非常痛苦。

实际使用中要注意的是:加入当前事务的方法,它的异常会污染整个事务。一旦有个子方法抛了RuntimeException,整个事务会被标记为rollback-only,即使外层方法用try-catch包住了异常,最终提交时Spring也会强制回滚。这一点后面会专门展开。

2.2 REQUIRES_NEW:独立事务,日志与审计的最优解

无论当前是否存在事务,都新建一个事务; 如果当前存在事务,则把当前事务挂起,新开一个独立事务。

REQUIRES_NEW是最容易被误解,也最容易用错的一种。它的关键是“完全独立”:新事务有自己的提交和回滚边界,外层事务不会影响它,它也不会影响外层事务。

最适合的场景就是操作日志、审计记录、消息通知。比如订单创建失败要回滚,但“谁在什么时间尝试创建过订单、因为什么失败”这条日志必须留下来。如果把日志方法设成REQUIRED,它和主流程共享事务,主流程一回滚,日志也没了。要日志不受牵连,就得用REQUIRES_NEW。

但代价是:主事务被挂起期间,新事务独占一个数据库连接。如果这个独立事务逻辑耗时长,连接会被长时间占用。在高并发场景下,大量REQUIRES_NEW会让连接池很快耗尽。这一点我在第4章的坑里详细讲。

2.3 SUPPORTS:查询方法的宽松模式

如果当前存在事务,则加入当前事务; 如果当前不存在事务,则以非事务方式执行。

SUPPORTS翻译过来就是“可有可无”。它不会主动创建事务,别人有事务就搭个便车,别人没事务自己也不折腾。查询、枚举、主数据加载这类只读操作,用SUPPORTS很合适,避免明明不需要事务,还白白占用一个数据库事务连接。

一个隐藏的好处是:当查询方法被一个事务方法调用时,它会加入当前事务,这样查询到的数据在同一个事务边界内保持一致,不会出现不同快照导致的数据不一致问题。当它被非事务方法调用时,也能正常执行。

2.4 NOT_SUPPORTED:挂起事务跑非事务逻辑

如果当前存在事务,则把当前事务挂起,以非事务方式运行; 如果当前不存在事务,也以非事务方式运行。

NOT_SUPPORTED是“强制不要事务”。它会把外层事务挂起,让自己以非事务模式跑完,然后再恢复外层事务。适合场景是:批量清理缓存、临时表写入、某些外部系统交互逻辑。这些操作不需要事务,也怕事务长时间锁住数据库资源。

我实际用过一次是在对接某外部文件导出功能时:文件生成耗时很久,如果放在事务里,数据库连接要一直被占用,别的请求等连接等到超时。改成NOT_SUPPORTED后,连接在进入方法前就释放了,文件生成过程中完全不留事务资源。

要注意的是,这种方法里如果自己抛了异常,只会影响自己;外层事务还挂在那边,方法结束后由Spring恢复。两边互不干扰,这点和REQUIRES_NEW类似,但NOT_SUPPORTED自己不会开启任何新事务。

2.5 MANDATORY:强制要求事务环境

如果当前存在事务,则加入当前事务; 如果当前不存在事务,则抛出异常。

MANDATORY的意思是“必须有事务,否则直接报错”。它适合那些内部逻辑严重依赖事务环境的操作,比如事务内的数据校验、前置检查。如果调用方没有开启事务,在这里直接抛IllegalTransactionStateException,让问题在入口处就暴露,而不是等数据写乱了再排查。

老实说,这种传播行为我在业务代码里用得不多,但在写一些框架性质的基础服务时很有价值。比如一个幂等校验组件,它要求调用方必须在事务内执行,否则校验和写入不在同一个事务边界内,幂等就失去意义。这时候用MANDATORY,等于用异常强迫调用方遵守约定。

2.6 NEVER:明确拒绝事务

如果当前存在事务,则抛出异常; 如果当前不存在事务,则以非事务方式运行。

NEVER是MANDATORY的反面。它强制要求当前没有事务,一旦发现有事务就抛异常。适用的场景是:某些操作如果出现在事务上下文里,说明调用关系已经错了,应该尽早暴露。

比如一个独立的定时任务执行器,它内部自己维护一批业务操作,本来就不应该被嵌入到其他事务里。如果某天代码重构后,有人把这个执行器的方法包进了一个事务方法中,NEVER就会通过异常提示调用方:这个操作不该在事务里跑,请重新设计调用链。

2.7 NESTED:基于保存点的局部回滚

如果当前存在事务,则在当前事务内部创建一个保存点(Savepoint), 后续代码可以只回滚到保存点,而不影响外层事务; 如果当前不存在事务,则新建一个事务执行。

NESTED是七种里最需要理解的。它不是一个独立物理事务,而是在现有事务基础上,利用数据库的保存点机制,实现“部分回滚”。

场景很典型:批量导入Excel。一万条数据,前五千条都插成功了,第六千条出了错,业务要求“出错的那条跳过,其余继续”。如果用REQUIRED,一条异常整个事务回滚;如果用REQUIRES_NEW,每条数据一个独立事务,数据是躲过了回滚,但频繁开关事务性能太差。NESTED的分寸刚好:外层事务还在跑,内层方法被标记为保存点,内层异常只回滚到保存点,外层还能继续提交。

要注意的是,NESTED能不能真正启用保存点,取决于底层数据库是否支持SAVEPOINT。MySQL和PostgreSQL都支持,用起来没问题。如果数据库不支持,Spring会退化为REQUIRED的行为,那就和预期不一致了。

2.8 七种传播行为速查表

传播行为当前有事务当前无事务典型场景
REQUIRED加入当前事务新建事务订单、库存、账户等原子性写入
REQUIRES_NEW挂起当前事务,新建独立事务新建事务操作日志、审计、消息通知
SUPPORTS加入当前事务非事务执行只读查询、主数据加载
NOT_SUPPORTED挂起当前事务,非事务执行非事务执行长时间外部交互、缓存清理
MANDATORY加入当前事务抛异常必须依赖事务的校验组件
NEVER抛异常非事务执行不允许出现在事务里的清理任务
NESTED创建保存点,局部回滚新建事务批量导入、逐条独立失败处理

3. 代码实战:7个场景完整演示

3.1 实战环境与基础代码

我用Spring Boot + JdbcTemplate来做示例,这样不引入额外的ORM复杂度,把注意力全放在传播行为上。先准备两张表:账户表和操作日志表。

CREATE TABLE account ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_code VARCHAR(32) NOT NULL UNIQUE, balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 ); CREATE TABLE operation_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_type VARCHAR(32) NOT NULL, content VARCHAR(255) NOT NULL, create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );

项目的核心依赖就是spring-boot-starter-jdbc和spring-boot-starter-web,配合一个MySQL数据源。配置我放在application.yml里:

spring: datasource: url: jdbc:mysql://localhost:3306/demo?useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver

数据访问层用两个简单的Dao,一个是账户操作,一个是日志插入。

@Repository public class AccountDao { @Autowired private JdbcTemplate jdbcTemplate; public void deduct(String userCode, BigDecimal amount) { jdbcTemplate.update( "UPDATE account SET balance = balance - ? WHERE user_code = ?", amount, userCode ); } public void add(String userCode, BigDecimal amount) { jdbcTemplate.update( "UPDATE account SET balance = balance + ? WHERE user_code = ?", amount, userCode ); } public BigDecimal getBalance(String userCode) { return jdbcTemplate.queryForObject( "SELECT balance FROM account WHERE user_code = ?", BigDecimal.class, userCode ); } }
@Repository public class OperationLogDao { @Autowired private JdbcTemplate jdbcTemplate; public void insert(String bizType, String content) { jdbcTemplate.update( "INSERT INTO operation_log(biz_type, content) VALUES (?, ?)", bizType, content ); } }

3.2 场景一:REQUIRED——事务合并与回滚

最典型的REQUIRED场景就是转账。从A账户扣钱,到B账户加钱。我用TransferService包住这两个操作:

@Service public class TransferService { @Autowired private AccountDao accountDao; @Transactional(propagation = Propagation.REQUIRED) public void transfer(String from, String to, BigDecimal amount) { accountDao.deduct(from, amount); accountDao.add(to, amount); } }

这是最简单的单物理事务:扣钱和加钱在同一个事务里,任何一步抛异常,两步都回滚。用REQUIRED在这里没有任何争议。

再看一个更贴近真实的场景:创建订单时扣库存。OrderService调StockService,两个方法都声明REQUIRED,内部会合并成一个事务。

@Service public class OrderService { @Autowired private OrderDao orderDao; @Autowired private StockService stockService; @Transactional(propagation = Propagation.REQUIRED) public void createOrder(OrderCreateVO vo) { orderDao.insert(vo); stockService.deductStock(vo.getProductId(), vo.getCount()); } }
@Service public class StockService { @Autowired private StockDao stockDao; @Transactional(propagation = Propagation.REQUIRED) public void deductStock(Long productId, int count) { stockDao.deduct(productId, count); } }

这里的关键是:StockService.deductStock发现自己执行时当前线程已经存在事务,就加入进去,不会新开事务。所以最终只有一个物理事务。createOrder成功,订单和库存一起提交;deductStock抛异常,订单和库存一起回滚。这就是REQUIRED的合并效果。

3.3 场景二:REQUIRES_NEW——日志独立提交

继续用下单场景。业务要求:“无论订单创建成功还是失败,操作日志必须保留。”如果用REQUIRED,日志会跟主事务一起回滚,这就不符合要求了。所以日志方法要改成REQUIRES_NEW。

@Service public class OrderService { @Autowired private OrderDao orderDao; @Autowired private OperationLogService logService; @Transactional(propagation = Propagation.REQUIRED) public void createOrder(OrderCreateVO vo) { try { orderDao.insert(vo); // 模拟核心业务失败 if (vo.getCount() > 100) { throw new RuntimeException("库存不足"); } logService.recordLog("ORDER", "订单创建成功: " + vo.getOrderNo()); } catch (RuntimeException e) { logService.recordLog("ORDER", "订单创建失败: " + e.getMessage()); throw e; } } }
@Service public class OperationLogService { @Autowired private OperationLogDao operationLogDao; @Transactional(propagation = Propagation.REQUIRES_NEW) public void recordLog(String bizType, String content) { operationLogDao.insert(bizType, content); } }

注意看执行过程:主事务开启后,订单写进去了,然后模拟库存不足抛异常。在catch块里调用recordLog时,当前线程是存在事务的,但REQUIRES_NEW会让Spring先把主事务挂起,新开一个日志事务,插入成功后立刻提交,然后恢复主事务。最后createOrder重新抛出异常,主事务回滚,订单数据没了,但日志已经提交,留了下来。

这个场景在支付回调、异步对账中特别常见:主数据要不要保留是一回事,审计轨迹必须完整是另一回事。

3.4 场景三:NESTED——批量导入部分成功

一张用户导入清单,要逐条插入。业务规定:单条失败不影响其他条,失败的记录下来,成功的照常保存。如果把每条插入声明成NESTED,外层保存点就能发挥“局部分支回滚”的作用。

先看数据访问层:

@Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public void insert(UserImportVO user) { jdbcTemplate.update( "INSERT INTO user_info(user_code, nickname, status) VALUES (?, ?, ?)", user.getUserCode(), user.getNickname(), user.getStatus() ); } }

批量导入服务:

@Service public class BatchImportService { @Autowired private UserImportHelper userImportHelper; @Transactional(propagation = Propagation.REQUIRED) public int importUsers(List<UserImportVO> users) { int successCount = 0; for (UserImportVO user : users) { try { userImportHelper.insertOne(user); successCount++; } catch (Exception e) { // 单条失败,只回滚这条的保存点,不影响外层 System.out.println("导入失败: " + user.getUserCode() + ", " + e.getMessage()); } } return successCount; } }

注意,这里我把单条插入逻辑放到了另一个独立的Helper里:

@Service public class UserImportHelper { @Autowired private UserDao userDao; @Transactional(propagation = Propagation.NESTED) public void insertOne(UserImportVO user) { userDao.insert(user); } }

为什么要拆成两个Bean?因为Spring的@Transactional走的是代理机制,如果我在BatchImportService内部用this.insertOne(user)直接调用,那只是普通方法调用,NESTED注解根本不生效。拆开成BatchImportService调UserImportHelper.insertOne,才真正走代理,传播行为才会被Spring处理。

在这个示例里,外层开启一个事务。每次进入UserImportHelper.insertOne时,当前已有事务,Spring就在事务里创建一个保存点。如果插入成功,保存点继续往下;如果插入失败,事务回滚到保存点位置,外层已经执行过的成功记录不受影响。循环结束后,外层事务统一提交。

这就是NESTED和REQUIRES_NEW最本质的区别:NESTED的回滚是局部的,外层事务没有被分开,最终数据仍在一个事务里提交;而REQUIRES_NEW直接拆出了独立物理事务,两边各自提交、各自回滚。

3.5 场景四:SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER——四种特殊场景验证

SUPPORTS的代码最简单:

@Service public class AccountQueryService { @Autowired private AccountDao accountDao; @Transactional(propagation = Propagation.SUPPORTS) public BigDecimal getBalance(String userCode) { return accountDao.getBalance(userCode); } }

单独调用getBalance时,Spring发现当前没有事务,就不新建事务,直接作为非事务查询执行。如果这个方法被另一个事务方法调用,它就会加入那个事务。这就是“有就搭车,没有就自己走着去”。

NOT_SUPPORTED更适合耗时操作:

@Service public class ExternalSyncService { @Transactional(propagation = Propagation.NOT_SUPPORTED) public void syncToExternal() { // 模拟耗时的外部系统交互 // 当前没有事务,数据库连接不会一直被占用 } }

假设这个syncToExternal是从一个事务方法里调用的,Spring会把外层事务挂起,让它以无事务方式执行。执行期间不占用数据库连接资源,结束后再恢复外层事务。好处很明显,事务持有的连接不会因为慢逻辑而长时间不释放。

MANDATORY和NEVER代码上更像“校验器”:

@Service public class VerificationService { @Transactional(propagation = Propagation.MANDATORY) public void verifyInTransaction() { // 只有当前存在事务时能执行,否则抛异常 } }
@Service public class CleanupService { @Transactional(propagation = Propagation.NEVER) public void cleanupTmpData() { // 只有当前没有事务时能执行,否则抛异常 } }

这两个方法平时很少单独出现在业务逻辑中,但当你设计一些基础组件时,它们能帮你把“调用契约”用异常直接固化下来。

4. 你一定会踩的4个坑

4.1 自调用导致事务失效

我在第3章写NESTED时特别提到把Helper拆成独立Bean,就是因为自调用问题。这个坑极其常见。

@Service public class BadService { @Transactional public void outerMethod() { // 这里其实是this调用的普通方法,注解不生效 this.innerMethod(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void innerMethod() { // 期望新开事务,实际不会 } }

Spring对@Transactional的处理是通过AOP生成代理对象,事务逻辑全在代理上。this.innerMethod()调用的是原始对象的方法,根本没有经过代理,所以事务控制全部失效。解决方式有三种:

第一种,把内层方法拆到另一个Service中,由外层注入后引用。这也是我代码实战里用的方案。

第二种,用@Autowired注入自己,然后通过代理调用。

@Service public class SomeService { @Autowired private SomeService self; @Transactional public void outerMethod() { self.innerMethod(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void innerMethod() { // 通过self调用,走代理,事务生效 } }

第三种,用ApplicationContext手动拿到代理,或者直接使用TransactionTemplate做编程式事务。对于复杂场景,编程式事务反而更直观。

4.2 外层捕获内层异常却还是回滚:rollback-only之谜

这个坑是事务传播里最费解的一个,线上排查极容易翻车。场景是这样的:外层方法A用REQUIRED开启事务,调用了也是REQUIRED的内层方法B。B抛了异常,A用try-catch把异常接住了,想着继续执行后面的逻辑。但最终A提交时,Spring直接抛UnexpectedRollbackException,所有数据全部回滚。

原因要从事务状态说起。A和B共用一个物理事务。B抛了运行时异常,Spring把当前事务标记成了rollback-only,意思是“这个事务已经被判了死刑,不需要再执行任何提交”。这个标记是全局的,A接住异常根本没有用,事务的最终结局早就定好了。

解决思路有两个方向:

一是改内层传播行为,把B改成REQUIRES_NEW。这样B回滚自己的事务时,只是把独立事务标记为rollback-only,A事务完全不知道,接下来照常提交。

二是别接异常,让B的异常直接往上抛。这样A也进入回滚流程,整体数据一致,虽然业务上可能不满,但起码没有脏数据。

我个人的习惯是:除非非常确信自己知道在做什么,否则不要轻易在外层捕获内层事务方法抛出的异常。捕获前先问一句:这个事务还要不要提交?如果要,就选REQUIRES_NEW或NESTED,再配合异常处理。

4.3 连接池被REQUIRES_NEW耗尽

REQUIRES_NEW独立事务看起来很爽,但代价是物理连接。假设连接池只有10个连接,一个主事务占着1个,进入REQUIRES_NEW方法时,Spring挂起主事务,库连接还锁在主事务上,新事务需要再拿1个连接。如果一段业务里连续嵌套多个REQUIRES_NEW,连接占用就会成倍增加。

我在一个批处理任务里曾经写过三层嵌套:主事务 -> 处理任务 -> 记录日志,日志方法用了REQUIRES_NEW。一次跑了几千条记录,每调一次日志方法就抢一个连接,等其他方法需要连接时,连接池已经空了,整个任务卡死。

这种场景最合适的做法是尽量缩小日志粒度,或者用异步方式记录日志,让日志提交不阻塞主流程。如果必须同步REQUIRES_NEW,一定评估连接池最大连接数,压测后再上线。

4.4 受检异常默认不回滚

@Transactional默认只对RuntimeException和Error回滚,受检异常(Checked Exception)抛出时不会回滚。这一点很容易被忽略,尤其是初学阶段。

比如一个导入方法里,解析Excel抛出一个自定义的ImportFormatException,这个异常如果继承的是Exception,那Spring默认不会回滚。结果就是:数据已经写了一半,异常也抛了,但前面写入的半截数据被提交了。

解决办法是显式指定回滚条件:

@Transactional(rollbackFor = ImportFormatException.class) public void importExcel(...) { // ... }

或者统一约定:业务里使用的异常都继承RuntimeException,让默认回滚策略覆盖所有异常。两种做法都行,但团队里必须统一,否则就会出现“有的人回滚了,有的人没回滚”的混乱局面。

5. 平时我是怎么选传播行为的

写业务代码时,我给自己总结了一套简单粗暴的选择逻辑。

常规写操作,默认REQUIRED,绝不滥用。一个事务能涵盖的原子操作就让它涵盖,别一个方法反复拆事务。真正需要独立提交的,比如日志、审计、消息发送,才用REQUIRES_NEW。批量处理中想实现单条失败不影响整体的,优先考虑NESTED。只读查询用SUPPORTS或干脆不加事务。耗时外部交互用NOT_SUPPORTED。基础框架预置规则用MANDATORY和NEVER做强制约束。

我在实际项目里被事务问题坑过很多次,最惨的一次就是文章开头说的凌晨被叫起来核对订单状态。后来我给自己定了一条规矩:凡是写入操作,能用一个事务包住的绝不用两个;凡是必须拆开的,一定用REQUIRES_NEW并且提前评估连接池。这习惯帮我躲过了好几次事故。

如果你正在排查事务相关的问题,建议先把这7种传播行为的定义背下来,再针对性看代码里实际调用的链路。把每个方法的传播行为、异常类型、调用关系画清楚,问题基本就浮出水面了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 11:54:47

GIS大赛上午场空间分析综合题全流程实操指南

全国大学生GIS应用技能大赛办到第八届&#xff0c;矩阵题型的核心其实越来越清楚了&#xff1a;上午场的基本功好不好&#xff0c;几乎直接决定全天心态。我常跟学弟学妹讲&#xff0c;上午那三小时不是拼谁会的高级功能多&#xff0c;而是拼谁能在压力状态下&#xff0c;把Arc…

作者头像 李华
网站建设 2026/10/17 11:54:43

单卡5090跑125B大模型:MoE显存优化与Strata实战

这段时间在硬件圈和大模型圈子里&#xff0c;"单卡 5090 跑 125B 大模型"这件事被反复刷屏。第一次看到 Strata 这个名字的时候&#xff0c;我也跟大多数人一样先算了一笔账&#xff1a;125B 参数&#xff0c;就算用 4-bit 量化&#xff0c;光权重也得 60 多 GB&…

作者头像 李华
网站建设 2026/10/17 11:53:23

AgentGym-RL实战:从零搭建LLM智能体多轮强化学习训练流水线

AgentGym-RL 这个名字听起来像个“健身房”&#xff0c;实际上它就是把 LLM 智能体的训练过程搬进了一个可交互、可反馈、可无限重来的环境里。我过去大半年一直在折腾这类事&#xff1a;把一个能写代码、能查资料、能操作工具的 LLM 智能体&#xff0c;从“会回答问题”练到“…

作者头像 李华
网站建设 2026/10/17 11:51:42

Chrome DevTools CSS调试完全指南:Styles与Computed面板实战

写CSS写到怀疑人生的时候&#xff0c;有一半的问题不是"代码不会写"&#xff0c;而是"代码写了不知道为什么不生效"。这篇是这个系列的第三篇&#xff0c;专门聊Chrome调试工具里最实用的一块——查看CSS属性。第一篇讲选择器和优先级&#xff0c;第二篇讲…

作者头像 李华
网站建设 2026/10/17 11:51:36

MySQL线程池实战避坑指南:5.7启用与8.0兼容性详解

简介&#xff1a;本资源是一份面向MySQL数据库管理员、性能优化工程师及高并发系统运维人员的实战型技术文档&#xff0c;聚焦解决生产环境中因高并发请求引发的线程创建开销大、响应延迟高、稳定性不足等核心性能瓶颈。文档系统阐述MySQL线程池插件&#xff08;thread_pool&am…

作者头像 李华