1. 程式人生 > >記一次java實現mysql的批量更新

記一次java實現mysql的批量更新

背景

專案中開發一個任務執行模組以下簡稱執行模組,由別的模組呼叫執行模組計算任務的結果,計算完成後需要把結算結果、計算狀態、計算耗時等資訊更新到Mysql庫裡,執行模組是來一個任務執行一次更新一次,後來發現這樣需要頻繁的與Mysql互動,且需要等待寫庫結果返回,效率堪憂拖慢其他模組。

優化

第一步:用執行緒池來更新,將更新程式碼提交到執行緒池中,由執行緒池排程入庫
缺點:沒有解決與資料庫頻繁互動的問題。
第二步:執行模組不管更新結果,只需將更新任務放入一個佇列中然後直接返回;用Spring的定時任務註解@Scheduled,指定一個方法,隔一段時間呼叫一次入庫方法;入庫的邏輯是,獲取隊列當前任務個數cnt,迴圈poll任務然後新增到一個List中,poll夠cnt個之後,通過批量更新方法將List更新到資料庫。
缺點:定時執行無法控制佇列大小,可能一次會取出很多條任務,也可能會把佇列撐得過大。
第三步:使用阻塞佇列放更新任務,用守護執行緒poll的佇列中的任務,當條數等於5000條(此值根據實際情況),則批量更新一次,在poll時設定超時時間為30秒,當超過30秒還是沒有取到任務,則也批量把已經渠道的任務更新一次。

總結:沒有三板斧都不好意思寫程式碼!!!

佇列實現

@Service
public class JobExecutorDataServiceImpl {
    //定義一個容量為10000的阻塞佇列,BlockingQueue執行緒安全可以多個生產者同時put
    private BlockingQueue<Job> dataQueue = new LinkedBlockingQueue<>(10000);

    //put任務的方法,供生產者呼叫
    public void recordJob(Job job) {
        try {
            dataQueue.put(job);
        } catch
(InterruptedException e) { LOGGER.info("批量更新Job入佇列異常"); Thread.currentThread().interrupt(); } } //初始化即呼叫 @PostConstruct private void batchUpdate() { Thread thread = new Thread(() -> { LOGGER.info("啟動批量更新守護執行緒,啟動時間{}", new Date(System.currentTimeMillis())); while
(Boolean.TRUE) { Job poll = null; boolean pollTimeOut = false; long startTime; long endTime; try { // poll時設定超時時間為30秒 poll = dataQueue.poll(30, TimeUnit.SECONDS); } catch (InterruptedException e) { LOGGER.info("批量更新Job異常"); Thread.currentThread().interrupt(); } if (null != poll) { // poll到任務新增到List中 jobs.add(poll); } else { // poll超時,設定超時標誌位 pollTimeOut = true; } // 如果任務List等於5000或poll超時且List中還有任務就批量更新 if (jobs.size() == 5000 || (pollTimeOut && !CollectionUtils.isEmpty(jobs))) { startTime = System.currentTimeMillis(); jobMapper.batchUpdateByPrimaryKeySelective(jobs); endTime = System.currentTimeMillis(); LOGGER.info("Job任務批量更新{}條任務,耗時{}毫秒", jobs.size(), endTime-startTime); jobs.clear(); } } }); thread.setName("job-batchUpdate-deamon"); // 設定啟動的執行緒為守護執行緒 thread.setDaemon(true); thread.start(); } }

生產者方法就不說了,主要說消費者方法:

  1. @PostConstruct作用是在Bean初始化之前執行消費者方法
  2. poll = dataQueue.poll(30, TimeUnit.SECONDS);
    使用阻塞佇列的poll方法,設定poll超時時間,當超過時間返回一個null值
  3. jobs.size() == 5000,如果List中值等於5000了就執行批量更新
  4. pollTimeOut && !CollectionUtils.isEmpty(jobs),如果poll超時了,說明當前生產者暫時沒有生產任務或不再生產任務,把List中剩餘的任務批量更新
  5. thread.setDaemon(true),設定執行緒為守護執行緒,直到jvm停了才停止

    Mysql批量更新實現

    <update id="batchUpdateByPrimaryKeySelective" parameterType="java.util.List">
        update abacus_job
        <trim prefix="set" suffixOverrides=",">
            <trim prefix="status =case" suffix="end,">
                <foreach collection="jobs" item="it" index="index">
                    when job_id=#{it.jobId,jdbcType=BIGINT} then #{it.status,jdbcType=TINYINT}
                </foreach>
            </trim>
            <trim prefix="result =case" suffix="end,">
                <foreach collection="jobs" item="it" index="index">
                    when job_id=#{it.jobId,jdbcType=BIGINT} then #{it.result,jdbcType=VARCHAR}
                </foreach>
            </trim>
        </trim>
        where job_id in
        <foreach collection="jobs" index="index" item="it" separator="," open="(" close=")">
            #{it.jobId,jdbcType=BIGINT}
        </foreach>
    </update>

    實際生成出來的SQL語句像以下格式:

    update abacus_job
     set status =
     case when job_id=1 then 1
             when job_id=2 then 2
             ...
     end,
     set result =
     case when job_id=1 then 'true'
             when job_id=2 then false
             ...
     end
    where job_id in (1, 2...)

    其實mysql批量更新還有一種寫法:

    <update id="batchUpdateByPrimaryKeySelective" parameterType="java.util.List">
    <foreach collection="jobs" item="item" index="index" open="" close="" separator=";">
      update abacus_job
      <set>
        <if test="item.status != null">
          status = #{item.status,jdbcType=TINYINT},
        </if>
        <if test="item.result != null">
          result = #{item.result,jdbcType=VARCHAR},
        </if>
      </set>
      where job_id = #{item.jobId,jdbcType=BIGINT}
    </foreach>
    </update>

    這樣生成的SQL是這樣:

    update abacus_job set status = 1, result = 'true' where job_id = 1;
    update abacus_job set status = 2, result = 'false' where job_id = 2;

    其實第二種方式的執行效率比第一種好,但是由於用了公司的資料連線中介軟體來做分表,用第二種方式更新的話,公司的中介軟體不支援,只好使用第一種,具體情況明天測試之後再看。

最近工作起來很費勁,感覺腦子不好使了,啥都容易忘,也是自己一直沒怎麼用心學,hold不住了,生活不止眼前的苟且,還有左右和後邊的苟且。

特別鳴謝一直陪伴在身邊的陳美美!!!

相關推薦

java實現mysql批量更新

背景 專案中開發一個任務執行模組以下簡稱執行模組,由別的模組呼叫執行模組計算任務的結果,計算完成後需要把結算結果、計算狀態、計算耗時等資訊更新到Mysql庫裡,執行模組是來一個任務執行一次更新一次,後來發現這樣需要頻繁的與Mysql互動,且需要等待寫庫結果返回

java socket學習(簡單實用多執行緒,實現多對多群聊)

學習過程是艱苦,學習結束是快樂的 目錄 用 [TOC]來生成目錄: 本來想寫一些文字描述描述,可是想不出來說啥。。。所以直接記錄程式碼了。。。 程式碼塊 因為喜歡把常量都提取出來 所以上來就是常量類: public class Const

java程序內存溢出問題

大文本 啟動 充足 嘗試 b-s 分析 無法 都是 root 一個自然語言處理程序,在封裝為web-service後,部署到線上運行。 但最近出現了內存溢出的情況,頻繁的out of memory。 先盲目嘗試在啟動腳本中增加-XX:-UseGCOverheadLimit。

Java的內存泄露分析

新項目 引用 極限 out size exce -a 場景 tpc 當前環境 jdk == 1.8 httpasyncclient == 4.1.3 代碼地址 git 地址:https://github.com/jasonGeng88/java-network-prog

PHP實現JS的無符號右移(>>>)

cti UNC 爬蟲 val 無符號 ret IV 使用 pri 舉例: JS: 5>>>2 PHP function uright($a, $n) { $c = 2147483647 >> ($n - 1);

常規的Mysql數據庫訪問的時間分析

客戶端 tcp三次握手 alt 時間 res src img gree 分析 背景:記一次常規的數據訪問的時間分析(插入操作) 1. TCP三次握手 SYN ---> <--- SYN,ACK ACK ---> 花費時間: 386.718-38

用PXE+kickstart批量為20臺新服務器安裝centos7

根據 命令行界面 exe user size linux c mman win8 windows 基於PXE+kickstart批量安裝centos7: 本文是純文字,後續會補充圖片 1.環境: CentOS Linux release 7.4.1708 (Core)ker

【國慶】mysqld_safe引發mysql進程故障

更新mysql 小結 未能 should sta 服務 tro boot 競爭力 今天是舉國歡慶的日誌,身為奮青的我,學習和工作,首日計劃安排必須是學習任務呀;但是今天心血來潮,Mariadb密碼忘記了,於是巴拉巴拉的執行"mysqld_safe --skip-grant-

Java動態代理實踐

導語:在Java生態中,我們經常直接或者間接的用到動態代理,比如通過動態代理呼叫遠端服務,再比如通過動態代理實現解耦。本文結合京東服務框架JSF,講述京東使用動態代理進行抽象的一次實踐,以達到升級資料庫訪問層的目的。 劉世傑,京東商城Java高階開發工程師,一直從服務端研發

JAVA中的Random使用經驗

專案中遇到這樣一個需求,在資訊流中拉取其他源的新聞,每次大概會拉取到10條,其中有些是沒有標題或者標題不符合規範的,需要對這種的標題進行識別並替換,隨機替換為預先設定的大概5個預設標題中的一個。需求簡單,核心在於隨機選取,一下就想到了Random類,遍歷每次的10條新聞,判斷為需要替換標題時用Ran

sql使用遊標迴圈更新資料

過程: ALTER PROCEDURE [dbo].[updateCode] AS BEGIN -- routine body goes here, e.g. -- SELECT 'Navicat for SQL Server' declare @gid

maven依賴不能更新的解決

程式碼結構大致是A是最頂級模組,B和C是A的子模組,D是C的子模組,B依賴D,ABCD都是0.0.1-SNAPSHOT版本。 在B的pom檔案裡,有D和C的依賴引用。 更新D的介面(添加了一個引數,變成2個引數)後,重新install D的模組,然後重新install C

MSSQL到MySQL大資料遷移過程

工作中遇到一個需求 要將MSSQL資料庫中共計12張表的資料大概1000W行資料遷移到MySQL資料庫中,去年另一個同事負責這件事情,他採用的方法是先將MSSQL資料庫裡的資料生成同MySQL資料庫表結構一致的12張表,然後用我提供的一個delphi寫的一個工具來進行遷移。工具用的UniDAC的TCRBa

揪心的MySQL資料恢復過程

先說下背景,公司其中一個專案所有服務都部署在客戶的機房內,機房較小,沒有UPS。其中一個MySQL例項(單機,無主從,windows server 2008,MySQL5.6.19)存放大量的日誌資料,每天幾十G的資料,定期清除(儲存大概四個月的資料),由於硬碟

Linux修改MySQL配置不生效的問題

背景 自己手上有一個專案服務用的是AWS EC2,最近從安全性和效能方面考慮,最近打算把騰訊雲的MySQL資料庫遷移到AWS RDS上,因為AWS的出口規則和安全組等問題,我需要修改預設的3306埠和Bind Address限制特定的IP訪問,我在Stackoverflow上查詢瞭如何修改,但是網上的資料

MySQL基礎系列之 利用儲存過程實現2600萬資料水平分表

日常開發中我們經常會遇到大表的情況,所謂的大表是指儲存了百萬級乃至千萬級條記錄的表。這樣的表過於龐大,導致資料庫在查詢和插入的時候耗時太長,效能低下,如果涉及聯合查詢的情況,效能會更加糟糕。分表的目的就是減少資料庫的負擔,提高資料庫的效率,通常點來講就是提高表的增刪改查效率,本文將介紹我

java對象在內存中的分析

數據 ots 字節對齊 位數 數據位 64位 數組 內存大小 特殊 java 對象 占內存大小 計算方式 及 常用類型的占用 HotSpot的對齊方式為8字節對齊 ----計算公式:(對象頭 + 實例數據 + padding) % 8等於0且0 <= padding

mysql事故---紀念逝去的一上午

not 路徑 內部 oca oss ica relay its scala 虛擬機關機後第二天mysql起不來,回想一下我關機前和關機後的操作發現:關機前沒關閉mysqld服務就直接init 0了,關機後將虛擬機內存由1G降到724M。筆者保證再也做過別的騷操作了。

mysql中文字符亂碼的問題排查

mysql mysql中文亂碼 mysql字符集 今天開發反應兩樣的程序往一個庫裏面插入數據正常,往另外一個庫裏面插入數據有亂碼。第一反應就是兩個數據庫關於字符集的配置不一樣。在兩個庫分別查看參數:show variables like "%char%";+--------------------

MySQL存儲過程和遊標的使用

MySQL存儲過程 MySQL遊標 需求: 有三張表:Player、Consumption、Consumption_other。Player表中記錄用戶信息(playerid、origin等字段),Consumption和Consumption_other記錄用戶的消費信息。現需要根據Playe