Топ-100Изоляция транзакций. Грязное чтение - CodOrbits
LogoCodOrbits

Раздел: JDBC

Научитесь подключаться к базам данных через JDBC, выполнять SQL-запросы и обрабатывать результаты в Java.

Все разделы
Иконка JDBC

Изоляция транзакций. Грязное чтение

Last updated: 9 мая 2025 г.

Две или более выполняющиеся параллельно транзакции часто должны быть изолированы друг от друга.

Example

Есть три случая когда необходимо изолировать транзакции друг от друга:

  • грязное чтение,
  • неповторяющееся чтение
  • фантомное чтение.

Начнем с грязного чтения.

В прошлых уроках о транзакциях мы говорили, что выполнение группы запросов, и соответственно изменение БД происходит только после вызова commit.

Но транзакции могут вести себя и по другому.

Например, можно сделать так, чтобы группа запросов даже до коммит производила изменения в БД, но если коммит так и не произошел, эти изменения откатятся. То есть коммит в этом случае просто делает так, чтобы изменения произведенные в базе там остались.

По умолчанию, как уже можно было понять, стоит первый вариант из двух приведенных. Он неявно установлен с помощью поля TRANSACTION_READ_COMMITTED.

Первый вариант исключает возможность грязного чтения. При втором же варианте возможно грязное чтение. Его уже нужно устанавливать явно с помощью поля TRANSACTIONS_READ_UNCOMMITED.


Грязное чтение

Допустим есть транзакция А и транзакция В, которые выполняются в разных потоках параллельно при этом обе транзакции выполняются в режиме TRANSACTIONS_READ_UNCOMMITED, то есть грязное чтение возможно.

К транзакции А в потоке где она выполняется будет впоследствии применен rollback.

Значит данные в БД измененные транзакцией А в конце концов окажутся недействительными, так как к ним будет применен rollback.

Но пока rollback не произошел к БД, которая была изменена транзакцией А могут совершать запросы другие потоки, например, поток в котором выполняется транзакция В.

Очевидно, что если транзакция В обратиться к данным в БД, которые изменила транзакция А и которые в результате окажутся недействительными из-за примененного к этим данным rollback, это может быть очень нежелательным. То есть транзакция В будет работать с данными, которых в БД в результате не будет, так как к ним будет применен rollback.

Давайте уберем грязное чтение

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

Пример программы:

1import java.sql.*;
2public class IsolationsDirty {
3    public static void main(String[] args)
4        throws ClassNotFoundException,
5        SQLException, InterruptedException {
6
7        //здесь идет транзакция А
8        Class.forName("com.mysql.cj.jdbc.Driver");
9        Connection connection =
10            DriverManager.getConnection(
11            "jdbc:mysql://localhost/storage",
12            "root", "07998MSD");
13        Statement statement = connection.createStatement();
14        connection.setAutoCommit(false);
15
16        //установим режим транзакции при котором
17        //теперь невозможно грязное чтение.
18        //На самом деле TRANSACTION_READ_COMMITTED
19        //как уже говорилось установлен по умолчанию
20        //поэтому эту строчку кода можно не писать.
21        connection.setTransactionIsolation(
22            Connection.TRANSACTION_READ_COMMITTED);
23
24        //изменим имя второй книги в таблице
25        //Важно что теперь грязное чтение невозможно
26        //то есть запрос ниже выполнится только при следующем
27        //коммите. То есть транзакция В не будет читать
28        //данные, которые в итоге окажутся недействительными
29        statement.executeUpdate("update books set name = "
30            +"‘another name’ where id = 2);
31        //Запускаем поток транзакции В
32        new TransactionB().start();
33        //при этом на две секунды останавливаем
34        //поток транзакции А, то есть текущий.
35        Thread.sleep(2000);
36        //После того как данные книг считаны
37        //транзакцией В, с помощью rollback делаем
38        //так чтобы изменения БД, которые должна сделать
39        //транзакция А при следующем коммите
40        //(то есть изменение второй книги) не случились.
41        connection.rollback();
42    }
43    static class TransactionB extends Thread {
44        @Override
45        public void run() {
46            //здесь идет транзакция В
47            try {
48                Connection connection =
49                    DriverManager.getConnection(
50                    "jdbc:mysql://localhost/storage",
51                    "root", "07998MSD");
52                Statement statement = connection.createStatement();
53                connection.setAutoCommit(false);
54                connection.setTransactionIsolation(
55                    Connection.TRANSACTION_READ_COMMITTED);
56
57                //Пока транзакция А остановлена на 2 сек
58                //Извлекаем и выводим на консоль данные книг.
59                //В этом случае данные второй книги
60                //не изменены в БД транзакцией А.
61                //Так как чтобы они вступили в силу нужно
62                //чтобы случился коммит транзакции А.
63                ResultSet resultSet =
64                    statement.executeQuery("SELECT * FROM books");
65                while (resultSet.next()) {
66                    System.out.println("name: "
67                        + resultSet.getString("name"));
68                }
69            } catch (SQLException e){}
70        }
71    }
72}

Скомпилируем, запустим программу:

Как видим, грязного чтения не произошло. Update запрос, который хотела выполнить транзакция А не был закоммичен, поэтому транзакция В не читала никаких таких данных, которые в итоге оказались бы недействительными.


Следующие уроки

Изоляция транзакций. Неповторяющееся чтение

15
мин.

Similar Articles Icon
Divider

Изоляция транзакций. Фантомное чтение

14
мин.

Similar Articles Icon
Divider

Безопасные запросы с PreparedStatement

12
мин.

Similar Articles Icon