База данных H2 - Обновление из избранных устанавливает устаревшие данные после обновления h2database до версии 1.4.198 - PullRequest
1 голос
/ 20 февраля 2020

У нас есть простой счетчик в нашей базе данных проекта. До сих пор мы использовали версию базы данных H2 1.4.197. Выполнение приведенного ниже примера фрагмента с этой версией всегда подразумевает, что счетчик равен 5000. Обновление до версии 1.4.198 или выше приводит к тому, что код ниже возвращает противоречивые результаты, обычно между 1500 и 2000.

public static void main(String[] args) throws SQLException, InterruptedException {
        String url = "jdbc:h2:mem:testdb";

        Connection connection = DriverManager.getConnection(url);
        connection.prepareStatement("CREATE TABLE t1 (id INT PRIMARY KEY, counter INT)").execute();
        connection.prepareStatement("INSERT INTO t1 (id, counter) VALUES (1, 0)").execute();

        int threads = 10;
        int times = 500;

        ExecutorService service = Executors.newFixedThreadPool(threads);
        ExecutorCompletionService<Void> cs = new ExecutorCompletionService<>(service);

        for (int i = 0; i < threads; i++) {
            cs.submit(() -> {
                Connection conn = DriverManager.getConnection(url);
                IntStream.range(0, times).forEach($ -> {
                    try {
                        conn.prepareStatement("UPDATE t1 SET counter = (SELECT counter FROM t1 WHERE id = 1) + 1 WHERE id = 1").executeUpdate();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                });
                return null;
            });
        }

        for (int i = 0; i < threads; i++) {
            cs.take();
        }

        ResultSet resultSet = connection.prepareStatement("SELECT counter FROM t1 WHERE id = 1").executeQuery();
        resultSet.next();
        System.out.println("counter: " + resultSet.getInt("counter"));

    }

Я предполагаю, что выбор Оператор снизу SQL выполняется перед блокировкой освобождения другой транзакции из таблицы, а затем выполняется обновление с использованием устаревших данных из оператора выбора.

"UPDATE t1 SET counter = (SELECT counter FROM t1 WHERE id = 1) + 1 WHERE id = 1"

У кого-нибудь была похожая проблема?

1 Ответ

2 голосов
/ 20 февраля 2020

Normal SELECT оператор не блокирует выбранные строки сам по себе и может читать фиксированные (старые) значения из заблокированных строк. У вас нет барьера вокруг всей команды в вашем коде, поэтому более новые версии H2 с их многопоточным исполнением могут выполнять такие команды одновременно. Несколько команд могут прочитать одно и то же старое значение и попытаться обновить строку. UPDATE блокирует строку, но подзапрос уже был оценен, и блокировка снята сразу после выполнения команды из-за автоматической фиксации. Вам нужно использовать SELECT … FOR UPDATE в подзапросе.

UPDATE t1 SET counter = (SELECT counter FROM t1 WHERE id = 1 FOR UPDATE) + 1 WHERE id = 1

Кстати, H2 1.4.198 - это версия бета-качества со многими известными проблемами. Вместо него безопаснее использовать 1.4.199 или 1.4.200.

...