多线程处理大数据量查询

最近在实现一个导入功能,数据量在5w左右,后续会持续增长,使用的是easyExcel,读取文件性能较好,但因为每条数据都要做复杂的数据校验,导致整体响应时间在三四十分钟,虽然说导入不是直接和用户交互,但现在数据量不算很大的情况下,这个响应时间还是需要优化的,读取文件速度在几秒以内,主要是在数据校验这块,所以就使用了多线程去工作。

主要代码:

import java.util.concurrent.*;
public class Test{
private static final int threadNum = Runtime.getRuntime().availableProcessors()*2;// 获取cpu核数
public void syncData(List<NewRegionReq> regionList) throws ExecutionException, InterruptedException {
        /**
         * 多线程分析数据
         */
        List<Future> list = new ArrayList<>();
        ExecutorService executor = Executors.newFixedThreadPool(threadNum);
        int num = (regionList.size() / threadNum) + 1;  //计算每个线程需要处理的记录数
        for (int j = 0; j < threadNum; j++) {
            //读取数据的起始位置
            int startNum = j * num;
            //读取数据的结束位置
            int endNum = startNum + num;
            //添加任务
            Callable<String> task = new ThredQuery(regionList, startNum, endNum);
            Future f = executor.submit(task);
            //接受返回结果
            list.add(f);
        }
        // 关闭线程池
        executor.shutdown();
        for (Future f : list) {
            // 从Future对象上获取任务的返回值,并输出到控制台
            System.out.println(f.get().toString()); //OPTION + return 抛异常
        }
        newRegionMapper.save(regionList);
        log.info("共导入数据 {} 行", regionList.size());
    }

class ThredQuery implements Callable<String> {
        private List<NewRegionReq> regionList;
        private int startNum;
        private int endNum;

        public ThredQuery(List<NewRegionReq> regionList, int startNum, int endNum) {
            this.regionList = regionList;
            this.startNum = startNum;
            this.endNum = endNum;
            //System.out.println(startNum + "\t" + endNum);
        }

        @Override
        public String call() {
            for (int i = startNum; i < endNum; i++) {
                //自己的业务逻辑
                //if (i < regionList.size()) {
               //}
            }
            return "成功";
        }
    }
}

思路:
1:先计算出查询总量,根据服务器的cpu核数,求每个线程应处理的条数
2.使用Callable返回结果,然后聚合数据,最后处理。

这里有个问题就是线程池合理的线程数你是如何考虑的?这也是之前面试遇到的一个题:

1.先看下机器的CPU核数,然后在设定具体参数:

System.out.println(Runtime.getRuntime().availableProcessors());

即CPU核数 = Runtime.getRuntime().availableProcessors()

2.分析下线程池处理的程序是CPU密集型,还是IO密集型

CPU密集型:核心线程数 = CPU核数 + 1

IO密集型:核心线程数 = CPU核数 * 2

MySql的乐观锁,悲观锁怎么实现的

乐观锁

    乐观锁大多是基于数据版本(version)的记录机制实现。何为数据版本?增加一个版本标识,在基于数据库表的解决方案中,一般是通过未数据库表增加一个version,在读取数据时,将版本号一起读出,之后更新时,对此版本号+1。

    此时将提交的版本数据与数据库表中对应记录的当前版本信息进行比对,如果提交的数据版本信息大于当前数据版本信息,则予以更新,否则认为是过期数据。

    优点:乐观锁机制避免了长事务中的数据库加锁开销(操作员A和操作员B操作过程中,都没对数据库数据加锁),大大提升了大并发量下的系统整体性能表现

实现方式:两种方式

1、加一个字段 数据版本(version)记录机制

2、加一个字段用时间戳代替版本
使用场景:

    高并发读取数据环境:当应用程序需要进行大量读取操作时,使用乐观锁可以提交系统性能和吞吐量。乐观锁在在大多数情况下不会锁定资源,减少了性能丢失;

    数据一致性的保障:尽管乐观锁减少对资源的锁定,但他们仍然可以提供一定的数据一致性保护。如果多个数据同时读取同一数据,并进行修改,乐观锁能够保证只有最终成功更新的线程才能继续执行后续操作

    避免长事务等待:如果程序使用了悲观锁,那么每个读取操作都需要先获得锁,这回导致长事务等待时间增加

注意:

乐观锁可以减少对资源的锁定,但在面对大量写入操作的情况下,数据冲突的可能性增大,这时可能需要更多的查询操作来重新获取数据以保持一致性
悲观锁
实现方式:

悲观锁是指在对数据进行操作之前,先加锁,保证在操作期间不会有其他用户修改改数据。

行级锁:在对某一行进行操作时,先获取该行的锁,其他用户需要等待该行锁的释放才能操作

表级锁:在对整个表进行操作时,先获取整个表的锁,其他用户需要等待整个表锁的释放才能进行操作

使用场景:

适用于频繁对数据修改和更新的场景,例如银行转账,订单处理

注:悲观锁用之前考虑清楚

定时删除6个月以上的人员数据并把删除的数据同步到另一张表

用的达梦数据库

我的做法是 定时任务 + 数据库触发器

定时任务删除 sql 6个月以上的
然后数据库触发器会把删除的数据插入到另一张表

CREATE TABLE CAR_GETOUT_LOGS_COPY (
    USER_ID VARCHAR(50),
    CAR_NAME VARCHAR(50),
    CROSS_TIME VARCHAR(50)
);

CREATE OR REPLACE TRIGGER SaveCARDeletedDataTrigger
BEFORE DELETE ON CAR_GETOUT_LOGS
FOR EACH ROW
BEGIN
  INSERT INTO CAR_GETOUT_LOGS_COPY
  (USER_ID , CAR_NAME, CROSS_TIME)
  VALUES(:OLD.USER_ID, :OLD.CAR_NAME, :OLD.CROSS_TIME);
END;


DROP TRIGGER IF EXISTS SaveCARDeletedDataTrigger;

SELECT * 
FROM USER_TRIGGERS
WHERE TABLE_NAME = 'CAR_GETOUT_LOGS';


delete from CAR_GETOUT_LOGS where EVENT_TIME < '2023-09-19 00:00:00'