You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程Spring Boot应用致MySQL进程睡眠状态的解决方法

问题描述

我有一个Spring Boot定时任务应用,其中一项任务通过多线程异步执行,每个线程向兼容MySQL的AWS Aurora Serverless数据库插入约20万条数据。任务执行完成后,线程已经关闭,但MySQL中对应的进程仍处于睡眠状态,直到超时才会终止。

代码示例如下:

@SpringBootApplication
@EnableScheduling
public class ScheduledServiceApp implements SchedulingConfigurer {
    private static final int POOL_SIZE = 20;

    public static void main(String[] args) {
       SpringApplication.run(ScheduledServiceApp.class, args);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

        threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
        threadPoolTaskScheduler.setThreadNamePrefix("scheduled-task-pool-");
        threadPoolTaskScheduler.initialize();

        scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
     }
}

@Component
public class ScheduledTask {

    // .....

    @Scheduled(cron = "${task.cron.expression}", zone="UTC")
    // @Modifying(clearAutomatically = true, flushAutomatically = true)
    @Transactional
    public void runTask() {
        try {
            // .....

            ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_COUNT);

            for (int i = 0; i < THREAD_COUNT; ++i) {
                Runnable task = new TaskThread();
                executor.execute(task);
            }

            executor.shutdown();

            while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
                // wait until the processes in the executor will be terminated
            }

            log.info("Task is completed successfully");
        } catch (Exception e) {
            // log the error
        }
    }
}

public class TaskThread implements Runnable {
    // .....

    public void run() {
        dataService.insertData(/* parameters */);
    }
}

每个线程执行时长约30分钟,任务完成后MySQL控制台仍可见这些睡眠状态的进程。尝试过@Modifying注解但无效,我不想降低MySQL的超时时间以免影响直接在MySQL中运行的查询,希望找到能让任务结束后立即终止这些MySQL进程的方案。

补充说明:应用由容器托管,运行在Tomcat下,使用MySQL的JDBC/ODBC桥(兼容Aurora Serverless MySQL 5.7)。

解决方案

1. 修正事务注解的作用范围

当前@Transactional注解加在runTask()方法上,但子线程不会继承父线程的事务上下文,这会导致父线程的事务迟迟无法提交,或者连接被错误持有。正确的做法是将事务注解移至实际执行数据插入的insertData()方法上,确保每个子线程的操作在独立事务中完成,执行完毕后立即释放数据库连接:

@Service
public class DataService {

    @Transactional
    public void insertData(/* parameters */) {
        // 执行批量插入逻辑
    }
}

同时移除ScheduledTask.runTask()上的@Transactional注解,因为该方法仅负责调度子线程,不需要事务上下文。

2. 使用Spring托管的线程池替代临时线程池

每次任务执行时创建newFixedThreadPool会导致线程频繁创建销毁,且可能导致连接池中的连接无法正确回收。改用Spring管理的ThreadPoolTaskExecutor,统一管理线程资源:

首先在配置类中定义线程池:

@Configuration
public class ThreadPoolConfig {

    @Bean(name = "taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(THREAD_COUNT);
        executor.setMaxPoolSize(THREAD_COUNT);
        executor.setThreadNamePrefix("data-insert-pool-");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
}

然后在ScheduledTask中注入使用:

@Component
public class ScheduledTask {

    @Autowired
    @Qualifier("taskExecutor")
    private ThreadPoolTaskExecutor taskExecutor;

    @Scheduled(cron = "${task.cron.expression}", zone="UTC")
    public void runTask() {
        try {
            // .....

            List<CompletableFuture<Void>> futures = new ArrayList<>();
            for (int i = 0; i < THREAD_COUNT; ++i) {
                futures.add(CompletableFuture.runAsync(new TaskThread(), taskExecutor));
            }

            // 等待所有任务完成
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

            log.info("Task is completed successfully");
        } catch (Exception e) {
            // log the error
        }
    }
}

3. 优化数据库连接池配置

通过调整连接池参数,确保空闲连接能被主动回收,而不是依赖MySQL的超时机制。以常用的HikariCP为例,在application.properties中添加以下配置:

# HikariCP配置
spring.datasource.hikari.max-lifetime=1800000 # 连接最大生命周期(30分钟,需小于MySQL的wait_timeout)
spring.datasource.hikari.idle-timeout=600000 # 连接空闲10分钟后回收
spring.datasource.hikari.connection-timeout=30000 # 获取连接超时时间
spring.datasource.hikari.minimum-idle=5 # 最小空闲连接数
spring.datasource.hikari.maximum-pool-size=20 # 最大连接数

这里设置idle-timeout小于MySQL的默认wait_timeout(通常为8小时),让连接池主动回收长时间空闲的连接,避免MySQL端出现睡眠进程。

4. 确保批量插入后资源正确释放

如果使用JPA的EntityManager,在批量插入完成后手动清除上下文:

@Transactional
public void insertData(/* parameters */) {
    // 批量插入逻辑
    entityManager.flush();
    entityManager.clear();
}

如果使用JDBC的Connection,务必使用try-with-resources确保连接关闭:

public void insertData(/* parameters */) {
    try (Connection conn = dataSource.getConnection()) {
        // 执行批量插入
    } catch (SQLException e) {
        // 处理异常
    }
}

内容的提问来源于stack exchange,提问作者Armine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 09:10:30