Java多線(xiàn)程批量操作,居然有人不做事務(wù)控制

目錄

  • 前言

  • 循環(huán)操作的代碼

  • 使用手動(dòng)事務(wù)的操作代碼

  • 嘗試多線(xiàn)程進(jìn)行數(shù)據(jù)修改

  • 基于兩個(gè) CountDownLatch 控制多線(xiàn)程事務(wù)提交

  • 基于 TransactionStatus 集合來(lái)控制多線(xiàn)程事務(wù)提交

  • 使用 union 連接多個(gè) select 實(shí)現(xiàn)批量 update

  • 總結(jié)

前言

項(xiàng)目概況如下:

  • 項(xiàng)目代碼基于:MySQL 數(shù)據(jù)

  • 開(kāi)發(fā)框架為:SpringBoot、Mybatis

  • 開(kāi)發(fā)語(yǔ)言為:Java8

項(xiàng)目代碼:

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">https://gitee.com/john273766764/springboot-mybatis-threads
</pre>

公司業(yè)務(wù)中遇到一個(gè)需求,需要同時(shí)修改最多約 5 萬(wàn)條數(shù)據(jù),而且還不支持批量或異步修改操作。于是只能寫(xiě)個(gè) for 循環(huán)操作,但操作耗時(shí)太長(zhǎng),只能一步一步尋找其他解決方案。 具體操作如下。

循環(huán)操作的代碼

先寫(xiě)一個(gè)最簡(jiǎn)單的 for 循環(huán)代碼,看看耗時(shí)情況怎么樣:

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">/***

  • 一條一條依次對(duì)50000條數(shù)據(jù)進(jìn)行更新操作
  • 耗時(shí):2m27s,1m54s
    */
    @Test
    void updateStudent() {
    List<Student> allStudents = studentMapper.getAll();
    allStudents.forEach(s -> {
    //更新教師信息
    String teacher = s.getTeacher();
    String newTeacher = "TNO_" + new Random().nextInt(100);
    s.setTeacher(newTeacher);
    studentMapper.update(s);
    });
    }
    </pre>

循環(huán)修改整體耗時(shí)約 1 分 54 秒,且代碼中沒(méi)有手動(dòng)事務(wù)控制應(yīng)該是自動(dòng)事務(wù)提交,所以每次操作事務(wù)都會(huì)提交所以操作比較慢,我們先對(duì)代碼中添加手動(dòng)事務(wù)控制,看查詢(xún)效率怎樣。

使用手動(dòng)事務(wù)的操作代碼

修改后的代碼如下:

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;

@Autowired
private TransactionDefinition transactionDefinition;

/**

  • 由于希望更新操作 一次性完成,需要手動(dòng)控制添加事務(wù)
  • 耗時(shí):24s
  • 從測(cè)試結(jié)果可以看出,添加事務(wù)后插入數(shù)據(jù)的效率有明顯的提升
    */
    @Test
    void updateStudentWithTrans() {
    List<Student> allStudents = studentMapper.getAll();
    TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
    try {
    allStudents.forEach(s -> {
    //更新教師信息
    String teacher = s.getTeacher();
    String newTeacher = "TNO_" + new Random().nextInt(100);
    s.setTeacher(newTeacher);
    studentMapper.update(s);
    });
    dataSourceTransactionManager.commit(transactionStatus);
    } catch (Throwable e) {
    dataSourceTransactionManager.rollback(transactionStatus);
    throw e;
    }
    }
    </pre>

添加手動(dòng)事務(wù)操控制后,整體耗時(shí)約 24 秒,這相對(duì)于自動(dòng)事務(wù)提交的代碼,快了約 5 倍,對(duì)于大量循環(huán)數(shù)據(jù)庫(kù)提交操作,添加手動(dòng)事務(wù)可以有效提高操作效率。

嘗試多線(xiàn)程進(jìn)行數(shù)據(jù)修改

添加數(shù)據(jù)庫(kù)手動(dòng)事務(wù)后操作效率有明細(xì)提高,但還是比較長(zhǎng),接下來(lái)嘗試多線(xiàn)程提交看是不是能夠再快一些。

先添加一個(gè) Service 將批量修改操作整合一下,具體代碼如下:

StudentServiceImpl.java:

<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;

@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;

@Autowired
private TransactionDefinition transactionDefinition;

@Override
public void updateStudents(List<Student> students, CountDownLatch threadLatch) {
    TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
    System.out.println("子線(xiàn)程:" + Thread.currentThread().getName());
    try {
        students.forEach(s -> {
            // 更新教師信息
            // String teacher = s.getTeacher();
            String newTeacher = "TNO_" + new Random().nextInt(100);
            s.setTeacher(newTeacher);
            studentMapper.update(s);
        });
        dataSourceTransactionManager.commit(transactionStatus);
        threadLatch.countDown();
    } catch (Throwable e) {
        e.printStackTrace();
        dataSourceTransactionManager.rollback(transactionStatus);
    }
}

}
</pre>

批量測(cè)試代碼,我們采用了多線(xiàn)程進(jìn)行提交,修改后測(cè)試代碼如下:

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;

@Autowired
private TransactionDefinition transactionDefinition;

@Autowired
private StudentService studentService;

/**

  • 對(duì)用戶(hù)而言,27s 任是一個(gè)較長(zhǎng)的時(shí)間,我們嘗試用多線(xiàn)程的方式來(lái)經(jīng)行修改操作看能否加快處理速度

  • 預(yù)計(jì)創(chuàng)建10個(gè)線(xiàn)程,每個(gè)線(xiàn)程進(jìn)行5000條數(shù)據(jù)修改操作

  • 耗時(shí)統(tǒng)計(jì)

  • 1 線(xiàn)程數(shù):1 耗時(shí):25s

  • 2 線(xiàn)程數(shù):2 耗時(shí):14s

  • 3 線(xiàn)程數(shù):5 耗時(shí):15s

  • 4 線(xiàn)程數(shù):10 耗時(shí):15s

  • 5 線(xiàn)程數(shù):100 耗時(shí):15s

  • 6 線(xiàn)程數(shù):200 耗時(shí):15s

  • 7 線(xiàn)程數(shù):500 耗時(shí):17s

  • 8 線(xiàn)程數(shù):1000 耗時(shí):19s

  • 8 線(xiàn)程數(shù):2000 耗時(shí):23s

  • 8 線(xiàn)程數(shù):5000 耗時(shí):29s
    */
    @Test
    void updateStudentWithThreads() {
    //查詢(xún)總數(shù)據(jù)
    List<Student> allStudents = studentMapper.getAll();
    // 線(xiàn)程數(shù)量
    final Integer threadCount = 100;

    //每個(gè)線(xiàn)程處理的數(shù)據(jù)量
    final Integer dataPartionLength = (allStudents.size() + threadCount - 1) / threadCount;

    // 創(chuàng)建多線(xiàn)程處理任務(wù)
    ExecutorService studentThreadPool = Executors.newFixedThreadPool(threadCount);
    CountDownLatch threadLatchs = new CountDownLatch(threadCount);

    for (int i = 0; i < threadCount; i++) {
    // 每個(gè)線(xiàn)程處理的數(shù)據(jù)
    List<Student> threadDatas = allStudents.stream()
    .skip(i * dataPartionLength).limit(dataPartionLength).collect(Collectors.toList());
    studentThreadPool.execute(() -> {
    studentService.updateStudents(threadDatas, threadLatchs);
    });
    }
    try {
    // 倒計(jì)時(shí)鎖設(shè)置超時(shí)時(shí)間 30s
    threadLatchs.await(30, TimeUnit.SECONDS);
    } catch (Throwable e) {
    e.printStackTrace();
    }

    System.out.println("主線(xiàn)程完成");
    }
    </pre>

多線(xiàn)程提交修改時(shí),我們嘗試了不同線(xiàn)程數(shù)對(duì)提交速度的影響,具體可以看下面表格, 多線(xiàn)程修改 50000 條數(shù)據(jù)時(shí),不同線(xiàn)程數(shù)耗時(shí)對(duì)比(秒)。

[圖片上傳失敗...(image-efa3f-1652162957414)]

根據(jù)表格,我們線(xiàn)程數(shù)增大提交速度并非一直增大,在當(dāng)前情況下約在 2-5 個(gè)線(xiàn)程數(shù)時(shí),提交速度最快(實(shí)際線(xiàn)程數(shù)還是需要根據(jù)服務(wù)器配置實(shí)際測(cè)試)。

基于兩個(gè) CountDownLatch 控制多線(xiàn)程事務(wù)提交

由于多線(xiàn)程提交時(shí),每個(gè)線(xiàn)程事務(wù)時(shí)單獨(dú)的,無(wú)法保證一致性,我們嘗試給多線(xiàn)程添加事務(wù)控制,來(lái)保證每個(gè)線(xiàn)程都是在插入數(shù)據(jù)完成后在提交事務(wù)。

這里我們使用兩個(gè) CountDownLatch 來(lái)控制主線(xiàn)程與子線(xiàn)程事務(wù)提交,并設(shè)置了超時(shí)時(shí)間為 30 秒。

我們對(duì)代碼進(jìn)行了一點(diǎn)修改:

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@Override
public void updateStudentsThread(List<Student> students, CountDownLatch threadLatch, CountDownLatch mainLatch, StudentTaskError taskStatus) {
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
System.out.println("子線(xiàn)程:" + Thread.currentThread().getName());
try {
students.forEach(s -> {
// 更新教師信息
// String teacher = s.getTeacher();
String newTeacher = "TNO_" + new Random().nextInt(100);
s.setTeacher(newTeacher);
studentMapper.update(s);
});
} catch (Throwable e) {
taskStatus.setIsError();
} finally {
threadLatch.countDown(); // 切換到主線(xiàn)程執(zhí)行
}
try {
mainLatch.await(); //等待主線(xiàn)程執(zhí)行
} catch (Throwable e) {
taskStatus.setIsError();
}
// 判斷是否有錯(cuò)誤,如有錯(cuò)誤 就回滾事務(wù)
if (taskStatus.getIsError()) {
dataSourceTransactionManager.rollback(transactionStatus);
} else {
dataSourceTransactionManager.commit(transactionStatus);
}
}
/**

  • 由于每個(gè)線(xiàn)程都是單獨(dú)的事務(wù),需要添加對(duì)線(xiàn)程事務(wù)的統(tǒng)一控制

  • 我們這邊使用兩個(gè) CountDownLatch 對(duì)子線(xiàn)程的事務(wù)進(jìn)行控制
    */
    @Test
    void updateStudentWithThreadsAndTrans() {
    //查詢(xún)總數(shù)據(jù)
    List<Student> allStudents = studentMapper.getAll();
    // 線(xiàn)程數(shù)量
    final Integer threadCount = 4;

    //每個(gè)線(xiàn)程處理的數(shù)據(jù)量
    final Integer dataPartionLength = (allStudents.size() + threadCount - 1) / threadCount;

    // 創(chuàng)建多線(xiàn)程處理任務(wù)
    ExecutorService studentThreadPool = Executors.newFixedThreadPool(threadCount);
    CountDownLatch threadLatchs = new CountDownLatch(threadCount); // 用于計(jì)算子線(xiàn)程提交數(shù)量
    CountDownLatch mainLatch = new CountDownLatch(1); // 用于判斷主線(xiàn)程是否提交
    StudentTaskError taskStatus = new StudentTaskError(); // 用于判斷子線(xiàn)程任務(wù)是否有錯(cuò)誤

    for (int i = 0; i < threadCount; i++) {
    // 每個(gè)線(xiàn)程處理的數(shù)據(jù)
    List<Student> threadDatas = allStudents.stream()
    .skip(i * dataPartionLength).limit(dataPartionLength)
    .collect(Collectors.toList());
    studentThreadPool.execute(() -> {
    studentService.updateStudentsThread(threadDatas, threadLatchs, mainLatch, taskStatus);
    });
    }
    try {
    // 倒計(jì)時(shí)鎖設(shè)置超時(shí)時(shí)間 30s
    boolean await = threadLatchs.await(30, TimeUnit.SECONDS);
    if (!await) { // 等待超時(shí),事務(wù)回滾
    taskStatus.setIsError();
    }
    } catch (Throwable e) {
    e.printStackTrace();
    taskStatus.setIsError();
    }
    mainLatch.countDown(); // 切換到子線(xiàn)程執(zhí)行
    studentThreadPool.shutdown(); //關(guān)閉線(xiàn)程池

    System.out.println("主線(xiàn)程完成");
    }
    </pre>

本想再次測(cè)試一下不同線(xiàn)程數(shù)對(duì)執(zhí)行效率的影響時(shí),發(fā)現(xiàn)當(dāng)線(xiàn)程數(shù)超過(guò) 10 個(gè)時(shí),執(zhí)行時(shí)就報(bào)錯(cuò)。

具體錯(cuò)誤內(nèi)容如下:

<pre class="prettyprint hljs groovy" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">Exception in thread "pool-1-thread-2" org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30055ms.
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:309)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)
at com.example.springbootmybatis.service.Impl.StudentServiceImpl.updateStudentsThread(StudentServiceImpl.java:58)
at com.example.springbootmybatis.StudentTest.lambdaupdateStudentWithThreadsAndTrans3(StudentTest.java:164)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30055ms.
at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:696)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:197)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:265)
... 7 more
</pre>

錯(cuò)誤的大致意思時(shí),不能為 數(shù)據(jù)庫(kù)事務(wù) 打開(kāi) jdbc Connection,連接在 30s 的時(shí)候超時(shí)了。

由于前面啟動(dòng)的十個(gè)線(xiàn)程需要等待主線(xiàn)程完成后才能提交,所以一直占用連接未釋放,造成后面的進(jìn)程創(chuàng)建連接超時(shí)。

看錯(cuò)誤日志中錯(cuò)誤的來(lái)源是 HikariPool ,我們來(lái)重新配置一下這個(gè)連接池的參數(shù),將最大連接數(shù)修改為 100。

具體配置如下:

<pre class="prettyprint hljs vbnet" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;"># 連接池中允許的最小連接數(shù)。缺省值:10
spring.datasource.hikari.minimum-idle=10

連接池中允許的最大連接數(shù)。缺省值:10

spring.datasource.hikari.maximum-pool-size=100

自動(dòng)提交

spring.datasource.hikari.auto-commit=true

一個(gè)連接idle狀態(tài)的最大時(shí)長(zhǎng)(毫秒),超時(shí)則被釋放(retired),缺省:10分鐘

spring.datasource.hikari.idle-timeout=30000

一個(gè)連接的生命時(shí)長(zhǎng)(毫秒),超時(shí)而且沒(méi)被使用則被釋放(retired),缺省:30分鐘,建議設(shè)置比數(shù)據(jù)庫(kù)超時(shí)時(shí)長(zhǎng)少30秒

spring.datasource.hikari.max-lifetime=1800000

等待連接池分配連接的最大時(shí)長(zhǎng)(毫秒),超過(guò)這個(gè)時(shí)長(zhǎng)還沒(méi)可用的連接則發(fā)生SQLException, 缺省:30秒

</pre>

<pre class="prettyprint hljs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">再次執(zhí)行測(cè)試發(fā)現(xiàn)沒(méi)有報(bào)錯(cuò),修改線(xiàn)程數(shù)為 20 又執(zhí)行了一下,同樣執(zhí)行成功了。
</pre>

基于 TransactionStatus 集合來(lái)控制多線(xiàn)程事務(wù)提交

在同事推薦下我們使用事務(wù)集合來(lái)進(jìn)行多線(xiàn)程 事務(wù)控制 ,主要代碼如下:

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@Service
public class StudentsTransactionThread {

@Autowired
private StudentMapper studentMapper;
@Autowired
private StudentService studentService;
@Autowired
private PlatformTransactionManager transactionManager;

List<TransactionStatus> transactionStatuses = Collections.synchronizedList(new ArrayList<TransactionStatus>());

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void updateStudentWithThreadsAndTrans() throws InterruptedException {

    //查詢(xún)總數(shù)據(jù)
    List<Student> allStudents = studentMapper.getAll();

    // 線(xiàn)程數(shù)量
    final Integer threadCount = 2;

    //每個(gè)線(xiàn)程處理的數(shù)據(jù)量
    final Integer dataPartionLength = (allStudents.size() + threadCount - 1) / threadCount;

    // 創(chuàng)建多線(xiàn)程處理任務(wù)
    ExecutorService studentThreadPool = Executors.newFixedThreadPool(threadCount);
    CountDownLatch threadLatchs = new CountDownLatch(threadCount);
    AtomicBoolean isError = new AtomicBoolean(false);
    try {
        for (int i = 0; i < threadCount; i++) {
            // 每個(gè)線(xiàn)程處理的數(shù)據(jù)
            List<Student> threadDatas = allStudents.stream()
                    .skip(i * dataPartionLength).limit(dataPartionLength).collect(Collectors.toList());
            studentThreadPool.execute(() -> {
                try {
                    try {
                        studentService.updateStudentsTransaction(transactionManager, transactionStatuses, threadDatas);
                    } catch (Throwable e) {
                        e.printStackTrace();
                        isError.set(true);
                    }finally {
                        threadLatchs.countDown();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    isError.set(true);
                }
            });
        }

        // 倒計(jì)時(shí)鎖設(shè)置超時(shí)時(shí)間 30s
        boolean await = threadLatchs.await(30, TimeUnit.SECONDS);
        // 判斷是否超時(shí)
        if (!await) {
            isError.set(true);
        }
    } catch (Throwable e) {
        e.printStackTrace();
        isError.set(true);
    }

    if (!transactionStatuses.isEmpty()) {
        if (isError.get()) {
            transactionStatuses.forEach(s -> transactionManager.rollback(s));
        } else {
            transactionStatuses.forEach(s -> transactionManager.commit(s));
        }
    }

    System.out.println("主線(xiàn)程完成");
}

}
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void updateStudentsTransaction(PlatformTransactionManager transactionManager, List<TransactionStatus> transactionStatuses, List<Student> students) {
// 使用這種方式將事務(wù)狀態(tài)都放在同一個(gè)事務(wù)里面
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔離級(jí)別,開(kāi)啟新事務(wù),這樣會(huì)比較安全些。
TransactionStatus status = transactionManager.getTransaction(def); // 獲得事務(wù)狀態(tài)
transactionStatuses.add(status);

students.forEach(s -> {
    // 更新教師信息
    // String teacher = s.getTeacher();
    String newTeacher = "TNO_" + new Random().nextInt(100);
    s.setTeacher(newTeacher);
    studentMapper.update(s);
});
System.out.println("子線(xiàn)程:" + Thread.currentThread().getName());

}
</pre>

由于這個(gè)中方式去前面方式相同,需要等待線(xiàn)程執(zhí)行完成后才會(huì)提交事務(wù),所有任會(huì)占用 Jdbc 連接池 ,如果線(xiàn)程數(shù)量超過(guò)連接池最大數(shù)量會(huì)產(chǎn)生連接超時(shí)。所以在使用過(guò)程中任要控制線(xiàn)程數(shù)量。

使用 union 連接多個(gè) select 實(shí)現(xiàn)批量 update

有些情況寫(xiě)不支持,批量 update,但支持 insert 多條數(shù)據(jù),這個(gè)時(shí)候可嘗試將需要更新的數(shù)據(jù)拼接成多條 select 語(yǔ)句,然后使用 union 連接起來(lái),再使用 update 關(guān)聯(lián)這個(gè)數(shù)據(jù)進(jìn)行 update。

具體代碼演示如下:

<pre class="prettyprint hljs sql" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">update student,(
(select 1 as id,'teacher_A' as teacher) union
(select 2 as id,'teacher_A' as teacher) union
(select 3 as id,'teacher_A' as teacher) union
(select 4 as id,'teacher_A' as teacher)
/* ....more data ... */
) as new_teacher
set
student.teacher=new_teacher.teacher
where
student.id=new_teacher.id
</pre>

這種方式在 MySQL 數(shù)據(jù)庫(kù)沒(méi)有配置 allowMultiQueries=true 也可以實(shí)現(xiàn)批量更新。

總結(jié)

如下:

  • 對(duì)于大批量數(shù)據(jù)庫(kù)操作,使用手動(dòng)事務(wù)提交可以很多程度上提高操作效率

  • 多線(xiàn)程對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作時(shí),并非線(xiàn)程數(shù)越多操作時(shí)間越快,按上述示例大約在 2-5 個(gè)線(xiàn)程時(shí)操作時(shí)間最快。

  • 對(duì)于多線(xiàn)程阻塞事務(wù)提交時(shí),線(xiàn)程數(shù)量不能過(guò)多

  • 如果能有辦法實(shí)現(xiàn)批量更新那是最好

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容