簡體   English   中英

PreparedStatement 批量插入/更新 - 死鎖問題

[英]PreparedStatement Batch Insert/Update - DeadLock Issue

我正在開發一個支持多個數據庫的系統。 所有的插入和更新都是批量發生的,同樣是在 PreparedStatement 批處理的幫助下實現的。 但是,對於 PostgreSQL,有很多時候會導致批量更新死鎖。 我想知道是否有辦法避免這種情況。

錯誤:檢測到死鎖
詳情:進程 30655 等待數據庫 17148 的關系 295507848 上的 ExclusiveLock; 被進程 30662 阻止。

我有重試邏輯,但它給出了:

錯誤:當前事務被中止,命令被忽略直到事務塊結束

我對如何處理這種情況有點困惑。

    public void InsertUpdateBatch(String auditPrefix, String _tableName, TableStructure<?> ts, 
    StringBuilder sb, String operation) throws Exception {
    
    boolean retry = true;
    boolean isInsert = "insert".equalsIgnoreCase(operation) ? true : false;
    int minTry = 0;
    int maxTries = 2;

    ThreadLocal<PreparedStatement> statement = isInsert ? pstmt : updateStmt;
    ThreadLocal<List<Object[]>> dataToProcess = isInsert ? insertBatchData : updateBatchData;
    
    while (retry) {
        try {
            long t1 = System.currentTimeMillis();
            int[] retCount = {};
            
            retCount = statement.get().executeBatch();
            
            // Clearing the batch and batch data
            statement.get().clearBatch();
            dataToProcess.get().clear();
            
            if(isInsert) {
                syncReport.addInsert(ts.getTableName(), retCount.length);
            } else {
                syncReport.addUpdate(ts.getTableName(), retCount.length);
            }
            
            this.syncReport.addDatabaseTime(t1, System.currentTimeMillis());

            retry = false;
            
        } catch (Exception e) {
            // Clearing the batch explicitly
            statement.get().clearBatch();
            
            log.log(Level.INFO, "Thread " + Thread.currentThread().getName() + ": tried the operation " + operation + " for "  + (minTry + 1) + " time(s)");

            if (++minTry == maxTries) {
                retry = false;
                minTry = 0;
                e.printStackTrace();
                commitSynchException(auditPrefix, _tableName, ts, sb, operation, isInsert, e);
            } else {
                
                trackRecordCount(e, ts, !isInsert);
                // Rebuild Batch
                rebuildBatch(ts, dataToProcess.get(), e);
                // Clearing old batch data after rebuilding the batch
                dataToProcess.get().clear();
            }
            
        }
    }
    
}

重試是解決方案。 但是您沒有正確實施它。

-- 按照@Mark Rotteveel 的建議進行編輯 --

您需要在連接上顯式調用.abort() ,然后您可以重試。 您可能可以保留 PreparedStatement / Statement 對象而僥幸,但如果您仍然遇到問題,請考慮關閉並重新創建這些對象。

-- 結束編輯 ---

你的第二個問題是缺乏 nagled 指數退避。

計算機是可靠的。 非常可靠。 比瑞士手表好。

如果兩個線程完成一項工作,並且作為該工作的一部分,它們彼此死鎖,並且它們都將看到這一點,中止它們的事務並重新開始,那么...

可能完全相同的事情會再次發生 然后再次。 然后再次。 然后再次。 在不幸的情況下,計算機可能會那么可靠。

解決方案是隨機指數退避。 確保兩個線程不會以完全相同的順序和完全相同的時間以完全相同的方式繼續做事的一種方法是從字面上開始翻轉硬幣以強制使其不太穩定。 這聽起來很愚蠢,但如果沒有這個概念,互聯網就不會存在(以太網的工作原理就是這樣:以太網上的所有系統都會立即發送數據,然后檢查線路上的尖峰,表明多方同時發送,並且結果是一個不可讀的混亂。如果他們檢測到這一點,他們會隨機等待指數退避然后再次發送。這個看似瘋狂的解決方案擊敗了令牌環網絡的褲子)。

“指數”部分意味着:隨着重試的到來,使延遲更長(並且仍然是隨機的)。

您的最后一個錯誤是您總是重試,而不是僅在明智的情況下重試。

這是一個示例指數隨機退避,它可以解決您的所有問題,除了您需要重新制作 (Prepared)Statement 對象並關閉舊對象的部分; 您的代碼段並不清楚發生在哪里。

} (catch SQLException e) { // catch SQLEx, not Ex
    String ps = e.getSQLState();
    if (ps != null && ps.length() == 5 && ps.startsWith("40")) {
        // For postgres, this means retry. It's DB specific!
        retryCount++;
        if (retryCount > 50) throw e;
        try {
            Thread.sleep((retryCount * 2) + rnd.nextInt(8 * retryCount);
            continue; // continue the retry loop.
        } catch (InterruptedException e2) {
            // Interrupted; stop retrying and just throw the exception.
            throw e;
        }
     }
     // it wasn't retry; just throw it.
     throw e;
}

或者,幫自己一個大忙,放棄所有這些工作並使用圖書館。 JDBC 被設計成對“最終用戶”來說非常煩人、不一致和丑陋 - 這是因為 JDBC 的目標受眾不是你。 這是數據庫供應商。 這是可以想象的最低級別的膠水,具有各種奇怪的技巧,因此所有數據庫供應商都可以公開他們的寵物功能。

那些使用 JDBC 訪問數據庫的人應該使用構建在上面的抽象庫!

例如, JDBI很棒,並且很好地支持重試,使用 lambdas。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM