Пакетная вставка / обновление PreparedStatement - Проблема с DeadLock - PullRequest
0 голосов
/ 07 августа 2020

Я работаю над системой, которая поддерживает несколько баз данных. Все вставки и обновления происходят массово, и то же самое достигается с помощью пакетов PreparedStatement. Однако с PostgreSQL довольно часто возникает тупик из-за обновления в пакетном режиме. Я хотел бы знать, есть ли способ избежать этого.

ОШИБКА: обнаружена взаимоблокировка Подробности: процесс 30655 ожидает ExclusiveLock в отношении 295507848 базы данных 17148; заблокирован процессом 30662.

У меня есть logi повторных попыток c, но он дает:

ОШИБКА: текущая транзакция прервана, команды игнорируются до конца блока транзакции

Я немного не понимаю, как справиться с этой ситуацией.

    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();
            }
            
        }
    }
    
}

1 Ответ

0 голосов
/ 07 августа 2020

Retry - это решение. Но вы не реализовали его должным образом.

- РЕДАКТИРОВАТЬ, как было предложено @Mark Rotteveel -

Вам нужно явно вызвать .abort() на вашем соединении, а затем вы можете повторить попытку. Вероятно, вам удастся сохранить свои объекты PreparedStatement / Statement, но если у вас все еще возникнут проблемы, подумайте о том, чтобы закрыть и воссоздать их.

- КОНЕЦ РЕДАКТИРОВАНИЯ ---

Вторая проблема отсутствует экспоненциального отката.

Компьютеры надежны. Очень надежный. Лучше, чем швейцарские часы.

Если два потока выполняют задание, и в рамках этого задания они блокируют друг друга, и оба увидят это, прервут свои транзакции и начнут заново, тогда ...

вероятно, точно то же самое повторится . И опять. И опять. И опять. Компьютеры могут быть такими надежными в неудачном сценарии ios.

Решение - случайная экспоненциальная отсрочка. Один из способов гарантировать, что два потока не будут продолжать делать что-то одинаково, в одном и том же порядке и с одинаковым временем, - это буквально начать подбрасывать монеты, чтобы принудительно сделать его менее стабильным. Это звучит глупо, но без этой концепции inte rnet не существовал бы (Ethe rnet работает именно так: все системы в сети ethe rnet немедленно отправляют данные, а затем проверяют наличие всплесков в строке, которая указывает на несколько стороны отправили все одновременно, и в результате получился нечитаемый беспорядок. Если они обнаруживают это, они случайным образом ждут с экспоненциальной отсрочкой , а затем отправляют его снова. Это, казалось бы, безумное решение выбило из колеи Token Ring сетей).

«Экспоненциальная» часть означает: по мере того, как повторяются попытки, увеличивайте задержки (и по-прежнему произвольно).

Ваша последняя ошибка заключается в том, что вы всегда пытаетесь повторить попытку, а не только тогда, когда это разумно.

Вот пример экспоненциального рандомизированного отката, который устраняет все ваши проблемы, кроме той части, где вам нужно заново создать объекты (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;
}

Или сделайте себе огромную услугу, бросьте всю эту работу и используйте библиотеку. JDB C спроектирован так, чтобы быть невероятно раздражающим, непоследовательным и уродливым для «конечных пользователей» - это потому, что целевая аудитория JDB C - не вы. Это поставщики БД. Это клей самого низкого уровня, который только можно вообразить, со всевозможными странными хитростями, чтобы все поставщики БД могли раскрыть свои любимые функции.

Например, JDBI отлично подходит и очень хорошо поддерживает повторные попытки с лямбдами.

...