for循环中控制事务单个提交问题

解决循环内事务提交失败
本文探讨了在for循环中每次迭代都尝试提交事务时遇到的问题。总结了使用通用mapper的updateByPrimaryKeySelective方法时,必须设置主键以避免SQL错误。同时,分析了在Spring声明式事务中,直接方法调用可能不触发代理,导致@Transactional注解失效的解决方案。

最近有个业务是for循环里面,循环一次业务提交一次,过程中发现事务不能提交,总结一下这个问题

第一点:

我用的是通用mapper,用到他的updateByPrimaryKeySelective方法,这个方法必须设置主键,不然他的where后面会带上所有字段造成sql错误

第二点:

用spring来做声明式事务的话,方法直接调用内部方法是不会走到代理类的,也就是不会走到切面,所以@Transactional可能会失效

所有我把更新业务单独提出来写在另外一个类里面就搞定了

 

 

<think>我们正在讨论在MySQL事务中使用顺序循环的实现方法。结合之前的讨论,我们知道MySQL存储过程支持多个顺序循环,并且可以在事务中运行。关键点在于将循环逻辑放在事务块中,确保所有操作要么全部成功,要么全部回滚。 根据引用[1]和[2],事务中操作顺序的重要性被强调,特别是为了避免死锁。因此,在事务中使用循环时,我们需要确保循环内的操作顺序是固定的,并且避免在循环中产生资源竞争。 实现步骤: 1. 创建存储过程,并在其中使用`START TRANSACTION`开始事务。 2. 在事务块内编写顺序循环(如`WHILE`、`REPEAT`等)。 3. 在循环内执行需要的SQL操作(如更新、插入等)。 4. 根据业务逻辑,在循环结束后提交事务(`COMMIT`)或在发生错误时回滚(`ROLLBACK`)。 注意事项: - 循环内操作应保持顺序一致,避免死锁(引用[1])。 - 使用异常处理(`DECLARE ... HANDLER`)来捕获错误并回滚。 示例:在事务中使用两个顺序循环更新两个表 假设我们有两个表:`orders`(订单表)和`order_details`(订单详情表)。我们需要在一个事务循环处理订单,然后循环处理订单详情。 步骤: 1. 创建存储过程,开始事务。 2. 第一个循环更新订单表状态。 3. 第二个循环更新订单详情表状态。 4. 提交事务,如果发生错误则回滚。 具体代码: ```sql DELIMITER $$ CREATE PROCEDURE ProcessOrdersInTransaction() BEGIN -- 声明变量 DECLARE done BOOLEAN DEFAULT FALSE; DECLARE order_id INT; -- 声明游标用于循环订单 DECLARE order_cursor CURSOR FOR SELECT id FROM orders WHERE status = 'PENDING' ORDER BY id; -- 固定顺序访问,避免死锁[^1] DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; -- 发生错误时回滚 RESIGNAL; END; START TRANSACTION; -- 第一个循环:更新订单状态 OPEN order_cursor; order_loop: LOOP FETCH order_cursor INTO order_id; IF done THEN LEAVE order_loop; END IF; -- 更新订单状态 UPDATE orders SET status = 'PROCESSING' WHERE id = order_id; END LOOP; CLOSE order_cursor; -- 重置done变量,为第二个循环准备 SET done = FALSE; -- 声明第二个游标用于订单详情 DECLARE detail_cursor CURSOR FOR SELECT id FROM order_details WHERE status = 'PENDING' ORDER BY id; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- 第二个循环:更新订单详情状态 OPEN detail_cursor; detail_loop: LOOP FETCH detail_cursor INTO order_id; IF done THEN LEAVE detail_loop; END IF; -- 更新订单详情状态 UPDATE order_details SET status = 'PROCESSING' WHERE id = order_id; END LOOP; CLOSE detail_cursor; -- 提交事务 COMMIT; END$$ DELIMITER ; ``` 说明: - 使用游标(`CURSOR`)来循环处理每一行,注意游标查询时使用`ORDER BY`保证顺序(避免死锁的关键)[^1]。 - 使用`EXIT HANDLER`捕获异常并回滚事务。 - 两个循环顺序执行,都在同一个事务中。 注意:在实际应用中,循环内的操作可能会更复杂,但核心是保持顺序一致,并注意事务的原子性。 另外,如果循环操作的数据量很大,需要考虑分批处理,避免长时间持有事务导致锁竞争。 引用[3]提到,在死锁分析中,有时难以看到完整的SQL语句,因此我们在设计事务循环时应尽量简化操作,并避免在循环中执行复杂的嵌套SQL。 总结:在事务中使用顺序循环是可行的,但必须注意操作顺序和异常处理,以避免死锁和确保数据一致性。 §§相关问题§§ 1. MySQL事务中如何避免循环操作导致的死锁? 2. 如何优化事务循环操作的性能? 3. 存储过程中事务循环结合使用的异常处理机制是怎样的?</think>### MySQL 在事务中使用顺序循环的实现方法 在事务中使用顺序循环需结合**存储过程**和**事务控制语句**,核心是保证循环操作的原子性和顺序一致性。以下是具体实现方法(基于 MySQL 8.0): #### 1. **基础实现框架** ```sql DELIMITER $$ CREATE PROCEDURE TransactionalLoopDemo() BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION -- 异常处理 BEGIN ROLLBACK; -- 出错时回滚 RESIGNAL; END; START TRANSACTION; -- 开启事务 -- ===== 第一个顺序循环 ===== DECLARE i INT DEFAULT 1; WHILE i <= 3 DO -- 业务操作(需保证顺序性) UPDATE accounts SET balance = balance + 10 WHERE id = i; -- 按固定ID顺序操作[^1] SET i = i + 1; END WHILE; -- ===== 第二个顺序循环 ===== DECLARE j INT DEFAULT 4; WHILE j <= 6 DO INSERT INTO logs(account_id, amount) VALUES (j, 10); -- 顺序插入日志 SET j = j + 1; END WHILE; COMMIT; -- 提交事务 END$$ DELIMITER ; ``` #### 2. **关键实现要素** 1. **事务控制** - `START TRANSACTION`:显式开启事务 - `COMMIT/ROLLBACK`:确保所有循环操作原子性提交或回滚 - 异常处理:通过`DECLARE EXIT HANDLER`捕获错误并回滚[^2] 2. **顺序保证机制** - **循环变量排序**:`WHERE id = i` 按固定顺序访问数据 - **操作顺序固定**:循环内操作顺序与业务逻辑一致 - **避免交叉访问**:循环操作相同资源(如表/行)[^1] 3. **死锁预防** ```sql -- 对所有操作按固定ID排序(避免死锁关键) DECLARE cur CURSOR FOR SELECT id FROM orders WHERE status='PENDING' ORDER BY id; -- 固定顺序访问[^1] ``` #### 3. **完整示例:批量订单处理** ```sql DELIMITER $$ CREATE PROCEDURE ProcessBatchOrders() proc_label: BEGIN DECLARE done BOOLEAN DEFAULT FALSE; DECLARE order_id INT; DECLARE cur CURSOR FOR SELECT id FROM orders WHERE status='PENDING' ORDER BY id; -- 关键:固定顺序[^1] DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; START TRANSACTION; -- ===== 循环1:更新订单状态 ===== OPEN cur; update_loop: LOOP FETCH cur INTO order_id; IF done THEN LEAVE update_loop; END IF; UPDATE orders SET status = 'PROCESSING' WHERE id = order_id; -- 按顺序更新 END LOOP; CLOSE cur; -- ===== 循环2:生成日志 ===== SET done = FALSE; OPEN cur; log_loop: LOOP FETCH cur INTO order_id; IF done THEN LEAVE log_loop; END IF; INSERT INTO order_logs(order_id, action) VALUES (order_id, 'Processed'); -- 顺序插入日志 END LOOP; CLOSE cur; COMMIT; END$$ DELIMITER ; ``` #### 4. **重要注意事项** 1. **锁控制** - 在循环内使用`SELECT ... FOR UPDATE`显式加锁 - 避免长事务单个事务处理超过 1000 行 ```sql SELECT balance INTO @bal FROM accounts WHERE id = i FOR UPDATE; -- 显式锁定当前行[^3] ``` 2. **性能优化** - 批量提交:每 N 条记录执行部分提交 - 索引优化:确保`ORDER BY`字段有索引 - 避免全表扫描:循环条件使用覆盖索引 3. **隔离级别** ```sql SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 推荐级别 ``` > **执行验证**: > ```sql > CALL ProcessBatchOrders(); -- 执行存储过程 > SHOW ENGINE INNODB STATUS; -- 检查死锁情况[^3] > ``` ### 死锁预防最佳实践 1. **严格顺序访问**:所有事务按相同顺序操作资源(如统一按ID升序)[^1] 2. **短事务原则**:单个事务超过 50ms 3. **索引优化**:确保`WHERE`和`ORDER BY`使用索引 4. **锁超时设置**:`SET innodb_lock_wait_timeout = 15;` ---
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值