案发现场

//防止全局配置了 所以这里定义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事物未提交和回滚导致不可预知问题的更多相关文章

  1. java陷阱之spring事物管理导致锁无效

    模拟锁情况无效 1.创建一个表 SET NAMES utf8mb4; ; DROP TABLE IF EXISTS `demo`; CREATE TABLE `demo` ( `id` ) NOT N ...

  2. J2EE分布式事务中的提交、回滚方法调用异常。

    这个是昨天上班的时候,写一个后台程序的调试程序时碰到的问题,和项目经理纠结了一天,最后搞定了.于是今天上班正好闲着,花了几乎一天的时间去网上找各种相关的资料.目前了解的内容如此: 根据使用的weblo ...

  3. Spring声明式事务不回滚问题

    疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚.于是就对相关代码进行了 ...

  4. BAPI总的数据库提交和回滚

    BAPI事物中的数据提交和回滚必须通过调用SAP标准业务对象BAPI SERVICE(对象类型SAP0001)的BAPI方法bapiservic.transactioncommit和bapiservi ...

  5. 哪些异常是RuntimeException?Sql异常属于RuntimeException吗?Spring下SQL异常事务回滚

    一,为什么框架中根本没有对Exception的一般子类进行回滚配置,异常发生时,事务都进行了回滚 ,说好的只会对RuntimeException(Unchecked 非受检异常)回滚呢? 此时,我们就 ...

  6. JDBC03 利用JDBC实现事务提交与回滚【调用Connection中的方法实现事务管理】

    目录 1 Connection中的重用方法 2 JDBC事务管理经典案例 1 Connection类中常用的方法回顾 1.1 Statement createStatement() throws SQ ...

  7. RocketMQ源码分析之RocketMQ事务消息实现原下篇(事务提交或回滚)

    摘要: 事务消息提交或回滚的实现原理就是根据commitlogOffset找到消息,如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,然后将原 ...

  8. 【转】批量复制操作(SqlBulkCopy)的出错处理:事务提交、回滚

    原文地址:http://blog.csdn.net/westsource/article/details/6658109 默认情况下,批量复制操作作为独立的操作执行. 批量复制操作以非事务性方式发生, ...

  9. 14.3.2.2 autocommit, Commit, and Rollback 自动提交 提交和回滚

    14.3.2.2 autocommit, Commit, and Rollback 自动提交 提交和回滚 如果自动提交模式被启用,在InnoDB里, 所有的用户活动发生在一个事务里, 每个SQL语句 ...

随机推荐

  1. PHP反射类的理解(代码篇)

    <?php/** * Created by PhpStorm. * User: * Date: 2017/6/12 * Time: 14:34 * 关于反射类的理解 */class Person ...

  2. NSAttributedString宽高计算小技巧

    通常对于CoreText之类自己实现绘制的控件来说,计算富文本的宽高事实上须要依赖CTFramesetterSuggestFrameSizeWithConstraints这种方法. 但有些时候.我们可 ...

  3. h5-6 canvas

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. sql server drop login failed

    https://stackoverflow.com/questions/37275/sql-query-for-logins https://www.mssqltips.com/sqlserverti ...

  5. nyoj--170--网络的可靠性(水题)

    网络的可靠性 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 A公司是全球依靠的互联网解决方案提供商,也是2010年世博会的高级赞助商.它将提供先进的网络协作技术,展示其 ...

  6. strlen和mb_strlen

    在PHP中,strlen与mb_strlen是求字符串长度的函数,但是对于一些初学者来说,如果不看手册,也许不太清楚其中的区别.下面通过例子,讲解这两者之间的区别. 先看例子: <?php // ...

  7. iOS怎么判断字典中存在nil值

    遍历字典中的key,然后根据key值取出对应的value如:for (NSString *key in dict) { //处理字典的键值 NSString *value = dict[key]; i ...

  8. Cracking the Coding Interview 5.2

    Given a(decimal -e.g. 3.72)number that is passed in as a string, print the binary representation. If ...

  9. 学习英语的小习惯(IT版) 不断补充中~~~~~~

    对于一些英文单词的读音,包含各种类似于Linux,Maven,Python……这些单词,尽量第一时间去找到他们的标准或者地道的发音,不要随便读,以免后面发音很难纠正. 有道词典的屏幕取词和划词很好用, ...

  10. POJ 3613 floyd+矩阵快速幂

    题意: 求s到e恰好经过n边的最短路 思路: 这题已经被我放了好长时间了. 原来是不会矩阵乘法,快速幂什么的也一知半解 现在终于稍微明白了点了 其实就是把矩阵乘法稍微改改 改成能够满足结合律的矩阵&q ...