记录第一次因为数据库事务产生的BUG

背景是在开发12306项目中,一键生成座位接口中,想着使用线程池优化一下

java 复制代码
    @Override
    @Transactional
    public void genTrainSeat(String trainCode) throws InterruptedException {
        //使用线程池进行优化
        //根据CPU核心数创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        //使用计数器(需要用阻塞的方式让开启的所有线程都执行完成才结束整个的任务方法,等待下一轮任务)
        CountDownLatch countDownLatch = new CountDownLatch(trainCarriages.size());

        //循环生成每个车厢的座位
        trainCarriages.forEach(carriage -> {
            executorService.execute(() -> {
                try {
                    循环生成座位的代码(太多,不进行展示,会向数据库插入很多条记录)
                } finally {
                    //计数器-1,直到把计数器减为0就不再阻塞
                    //try,finally的目的是无论是中途return还是正常执行,最后计数器都会-1
                    countDownLatch.countDown();
                }
            });
        });
        //阻塞,直到计数器减为0,阻塞就放行(需要指定最大限度的等待时间,阻塞最多等待一定的时间后就解除阻塞)
        countDownLatch.await(30, TimeUnit.MINUTES);
        executorService.shutdownNow();
    }

然后调试的时候就给我报错了,异常信息如下:

java 复制代码
org.springframework.dao.CannotAcquireLockException: 
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may exist in file [/Users/jalen/idea_project/train/train-business/target/classes/mapper/TrainSeatMapper.xml]
### The error may involve com.jalen.train.business.mapper.TrainSeatMapper.insert-Inline
### The error occurred while setting parameters
### SQL: insert into train_seat (id, train_code, carriage_index,                             `row`, col, seat_type, carriage_seat_index,                             create_time, update_time)     values (?, ?, ?,             ?, ?, ?,             ?,             ?, ?)
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction
	at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:272)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:439)
	at jdk.proxy2/jdk.proxy2.$Proxy82.insert(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:62)
	at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:141)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)
	at jdk.proxy9/jdk.proxy9.$Proxy194.insert(Unknown Source)
	at com.jalen.train.business.service.impl.TrainSeatServiceImpl.lambda$genTrainSeat$0(TrainSeatServiceImpl.java:164)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at com.jalen.train.business.service.impl.TrainSeatServiceImpl.lambda$genTrainSeat$1(TrainSeatServiceImpl.java:152)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:123)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
	at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:370)
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
	at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.execute(HikariProxyPreparedStatement.java)
	at jdk.internal.reflect.GeneratedMethodAccessor66.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:58)
	at jdk.proxy4/jdk.proxy4.$Proxy124.execute(Unknown Source)
	at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:48)
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:75)
	at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
	at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
	at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:425)

刚开始还以为sql问题,往下看发现不对劲,MySQL事务回滚异常!!!

然后开始问chat,得出的结论事务范围过大或事务中包含耗时操作,导致锁持有时间过长。

解决方案是减少锁持有时间:确保事务尽可能短,避免在事务中执行耗时操作。

解决并测试成功后的代码:

java 复制代码
public void genTrainSeat(String trainCode) throws InterruptedException {
        // 使用线程池进行优化
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        CountDownLatch countDownLatch = new CountDownLatch(trainCarriages.size());

        // 循环生成每个车厢的座位
        for (TrainCarriage carriage : trainCarriages) {
            executorService.execute(() -> {
                try {
                    insertSeatsForCarriage(trainCode, carriage, now);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }

        // 阻塞,直到计数器减为0
        countDownLatch.await(30, TimeUnit.MINUTES);
        executorService.shutdown();
        executorService.awaitTermination(30, TimeUnit.MINUTES);
    }

    @Transactional
    public void insertSeatsForCarriage(String trainCode, TrainCarriage carriage, Date now) {
        ......
}

总结:因为在一个事务中包含了太多的插入操作,导致锁竞争和锁超时问题。这种情况在高并发环境下尤为明显,因为多个线程同时尝试插入数据,可能会导致数据库锁定资源的时间过长,从而引发锁等待超时。

孩子记住啦。恭喜自己入职第41天!!!🎉🎉🎉

相关推荐
这个软件需要设计一下5 小时前
ninedata安装磁盘不足问题解决
运维·bug
热爱生活的五柒6 小时前
cc-switch安装方法、介绍及遇到的bug
bug·cc-switch
Greenland_126 小时前
Android 混淆与混淆后bug日志问题定位
android·bug
应用市场7 小时前
踩坑记录:有符号整数位运算的那些隐蔽Bug——符号扩展、算术右移与补码
java·开发语言·bug
一灰灰blog1 天前
Jar包会自己消失?Excel会“记忆“数据?我遇到了两个灵异bug
java·spring boot·bug·excel
王家视频教程图书馆2 天前
修复服务端500相应,修复客户端上传文件.tmp 服务端接受不到文件bug
bug
qq_401700412 天前
Qt开发过程中遇到哪些经典的bug
qt·bug
0白露4 天前
关闭搜狗输入法右下角广告,可以适用于大多数应用系统通知的广告
windows·bug
一只自律的鸡5 天前
【Linux驱动】bug处理 ens33找不到IP
linux·运维·bug
Lichenpar6 天前
Springboot采用FastJson2作为MessageConverter时,配置的全局日期类型序列化转换BUG
java·开发语言·bug