java陷阱之spring事物未提交和回滚导致不可预知问题
案发现场
//防止全局配置了 所以这里定义sprnig 不托管事物
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean deductNumber(Long id,int i){ DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置事物传播行为
TransactionStatus status = null;
try {
//开启事物
status = transactionManager.getTransaction(def);
if(id==null){
return false;//库存编号不能为空
}
if(i<=0){
return false;//扣除库存不能小于0
}
//提交事务
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
return false;
}
return true;
}
导致问题
线上出现 订单下单 提示用户订单创建成功 但是订单不在了 对应的钱没扣 库存也没扣 看代码没有问题 日志也是正常打印 没抛错没回滚
mysql 部分表出现锁等待 单表操作无并发也出现锁等待
自己的坑自己填
1.day 1
怀疑是数据库的问题 事物提交了 但是数据库没持久 在下单操作打印了日志判断是否回滚并打印日志
if(status.isRollbackOnly()){
logger.info("======事物已回滚=========");
return false;
}
2.day 2
第二天又出现这种情况 同时发现伴随着后台有个事物一一直 在select * from information_schema.innodb_trx; runing 持续半个小时以上
但是发现丢单的地方======事物已回滚========
这个时候很疑惑 因为下单很多逻辑怀疑是某个方法设置了回滚状态但是抛出true 来log4j开启事物管理器debug日志
log4j.logger.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
开启之后开启事物 设置事物状态合并事物都会打印对应的日志
同时为了定位到runing的事物是在哪个地方 在重写了log4j layout 打印了事物号和线程号
log4j配置log4j.appender.stdout.layout =com.chinayanghe.dms.log4jUtil.MyPatternLayout
public class MyPatternLayout extends PatternLayout {
/**
* 日志打印
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
private static ThreadLocal<String> localTreadIds = new ThreadLocal<String>();
public static void removeTreadLocalCache() {
localTreadIds.remove();
}
@Override
public String format(LoggingEvent event) {
String logStr = super.format(event);
try {
if (logStr.indexOf("SELECT trx_id FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID()") < 0
&&logStr.indexOf("SELECT trx_mysql_thread_id FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();")<0) {
//表示是sql打印尝试获得事物号
if (logStr.indexOf("[org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:145)] ==> Preparing:") >= 0) {
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
String treadId = localTreadIds.get();
if (StringUtil.isEmpty(treadId)) {
JDBCTransactionInfoService jdbcTransactionInfoService = (JDBCTransactionInfoService) wac.getBean("jDBCTransactionInfoServiceImpl");
List<String> treadIds = jdbcTransactionInfoService.selectTrxMysqlThreadId();
if (CollectionUtils.isNotEmpty(treadIds)) {
treadId = treadIds.get(0);
localTreadIds.set(treadId);
}
}
if (treadId != null) {
logStr = logStr.replace("[DEBUG]", "[DEBUG][事务号" + treadId + "]");
}
} else if (logStr.indexOf("Transactional code has requested rollback") >= 0) {
logStr = logStr.replace("Transactional code has requested rollback", "Transactional code has requested rollback,线程id:" + Thread.currentThread().getId());
}
} else {
return "";
}
long treadId = Thread.currentThread().getId();
logStr = logStr.replace("[DEBUG]", "[DEBUG][线程id:" + Thread.currentThread().getId() + "]").replace("[INFO]", "[INFO][线程id:" + treadId + "]");
} catch (Exception e) {
logger.info(e.getMessage());
}
return logStr;
}
}
3.day3出现丢单根据日志发现 丢单的都是同一个线程id 事物号也相同 同时丢单的地方都没有开启事物日志而是事物已存在合并的日志
根据日志 一个一个看 发现规律 线程id最后一次打印 creating transaction 之后 再也没打印 然后定位到最后一次打印事物 的方法是手动开启事物的方法 有个地方忘记回滚 直接return了
这个时候反应过来 是这个方法没提交一直挂起 spring事物是基于线程缓存 tomcat会回收线程到线程池 下单分配到这个线程 因为手动开启事物还存在 所以公用同一个事物 事物一致挂起 当遇到RR模式锁等待抛出wai lock
statu的回滚状态会设置为true 所以才会出现我的下单日志打印都是回滚
优化代码
//防止全局配置了 所以这里定义sprnig 不托管事物
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean deductNumber(Long id,int i){ DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置事物传播行为
TransactionStatus status = null;
try {
//开启事物
status = transactionManager.getTransaction(def);
if(id==null){
transactionManager.rollback(status);
return false;//库存编号不能为空
}
if(i<=0){
return false;//扣除库存不能小于0
}
//提交事务
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
return false;
}finally {
//加上此代码 防止忘记
if(status!=null&&!status.isCompleted()){
transactionManager.rollback(status);
return false;
}
} return true;
}
总结原因:
事物一直没有被提交,事物状态是通过线程缓存实现的,当线程回收到线程池,后面再分分派的这个线程使用的同一个连接和事物,事物其实一直挂起
java陷阱之spring事物未提交和回滚导致不可预知问题的更多相关文章
- java陷阱之spring事物管理导致锁无效
模拟锁情况无效 1.创建一个表 SET NAMES utf8mb4; ; DROP TABLE IF EXISTS `demo`; CREATE TABLE `demo` ( `id` ) NOT N ...
- J2EE分布式事务中的提交、回滚方法调用异常。
这个是昨天上班的时候,写一个后台程序的调试程序时碰到的问题,和项目经理纠结了一天,最后搞定了.于是今天上班正好闲着,花了几乎一天的时间去网上找各种相关的资料.目前了解的内容如此: 根据使用的weblo ...
- Spring声明式事务不回滚问题
疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚.于是就对相关代码进行了 ...
- BAPI总的数据库提交和回滚
BAPI事物中的数据提交和回滚必须通过调用SAP标准业务对象BAPI SERVICE(对象类型SAP0001)的BAPI方法bapiservic.transactioncommit和bapiservi ...
- 哪些异常是RuntimeException?Sql异常属于RuntimeException吗?Spring下SQL异常事务回滚
一,为什么框架中根本没有对Exception的一般子类进行回滚配置,异常发生时,事务都进行了回滚 ,说好的只会对RuntimeException(Unchecked 非受检异常)回滚呢? 此时,我们就 ...
- JDBC03 利用JDBC实现事务提交与回滚【调用Connection中的方法实现事务管理】
目录 1 Connection中的重用方法 2 JDBC事务管理经典案例 1 Connection类中常用的方法回顾 1.1 Statement createStatement() throws SQ ...
- RocketMQ源码分析之RocketMQ事务消息实现原下篇(事务提交或回滚)
摘要: 事务消息提交或回滚的实现原理就是根据commitlogOffset找到消息,如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,然后将原 ...
- 【转】批量复制操作(SqlBulkCopy)的出错处理:事务提交、回滚
原文地址:http://blog.csdn.net/westsource/article/details/6658109 默认情况下,批量复制操作作为独立的操作执行. 批量复制操作以非事务性方式发生, ...
- 14.3.2.2 autocommit, Commit, and Rollback 自动提交 提交和回滚
14.3.2.2 autocommit, Commit, and Rollback 自动提交 提交和回滚 如果自动提交模式被启用,在InnoDB里, 所有的用户活动发生在一个事务里, 每个SQL语句 ...
随机推荐
- iOS GCD使用指南
Grand Central Dispatch(GCD)是异步运行任务的技术之中的一个. 一般将应用程序中记述的线程管理用的代码在系统级中实现.开发人员仅仅须要定义想运行的任务并追加到适当的Dispat ...
- Git与SVN区别 \git学习
http://blog.csdn.net/w13770269691/article/details/38705473/ https://git-scm.com/book/zh/v2
- TestNG升级
TestNG 6.5.1 or above is required,please update your TestNG or uncheck 'Use project TestNG jar' from ...
- Android布局文件经验
1.父控件中含有多个子控件时.往往遵循长子优先的原则,即长子假设非常大可能占满父空间.使次子们出局: 2.如果TableLayout有2行,当中一行未设定列间长度比例.而还有一行设定了,则未设定行可能 ...
- svn代码回滚命令【转】
本文转载自:http://www.cnblogs.com/jndream/archive/2012/03/20/2407955.html 取消对代码的修改分为两种情况: 第一种情况:改动没有被提交 ...
- php传值调用和传值调用和变量函数
php传值调用和传值调用和变量函数 代码 <?php //传值调用,$m的值不改变 function text($i){ $i = 'Clive'; echo $i; } text(123); ...
- 2014.9.20Hashtable概述
hashtable叫哈希表,用于表示键值的集合,这些键值对根据键的哈希代码进行组织,其每个元素都存储于DictionaryEntry对象中的键值对.键不能为空引用. count:获取包含在hashta ...
- linux下nmap工具的使用
NMap,也就是Network Mapper,是Linux下的网络扫描和嗅探工 具包,其基本功能有三个,一是探测一组主机是否在线:其次是扫描主机端口,嗅探所提供的网络服务:还可以推断主机所用的操作系统 ...
- NOIP2013 D2T1 积木大赛
[NOIP2013T4]积木大赛 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 noip2013day2 描述 春春幼儿园举办了一年一度的"积木大 ...
- guice基本使用,常用的绑定方式(四)
guice在moudle中提供了良好的绑定方法. 它提供了普通的绑定,自定义注解绑定,按名称绑定等. 下面直接看代码: package com.ming.user.test; import com.g ...