记录第一次因为数据库事务产生的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天!!!🎉🎉🎉

相关推荐
树懒_Zz2 天前
记录 io.springfox 3.0.0 整合 spring boot 2.6.x 由于 springfox bug 引发问题
windows·spring boot·bug
每天进步一大步2 天前
webSokect安卓和web适配的Bug 适用实时语音场景
android·前端·bug·web
JWASX2 天前
【BUG记录】Apifox 参数传入 + 号变成空格的 BUG
java·bug·urlencoder·urldecoder
初遇你时动了情2 天前
解决react 路由切换,页面闪屏的bug
javascript·react.js·bug
老赵的博客2 天前
QString转const char* bug
bug
andlbds3 天前
解决PCL库中pcl::VoxelGrid降采样Bug
c++·bug
乌漆嘎嘎黑3 天前
XIO: fatal IO error 22 (Invalid argument) on X server “localhost:10.0“【小白找bug】
pytorch·python·bug·matplotlib·mobaxterm
curd_boy5 天前
【BUG】记一次context canceled的报错
golang·bug
乌漆嘎嘎黑7 天前
训练的Loss和输出突然全是nan【小白找bug】
pytorch·python·bug·llama·大模型权重文件
花开花落的个人博客7 天前
Jlink调试找出程序隐藏BUG
bug