Изоляция транзакций. Грязное чтение
Last updated: 9 мая 2025 г.Две или более выполняющиеся параллельно транзакции часто должны быть изолированы друг от друга.
Есть три случая когда необходимо изолировать транзакции друг от друга:
- грязное чтение,
- неповторяющееся чтение
- фантомное чтение.
Начнем с грязного чтения.
В прошлых уроках о транзакциях мы говорили, что выполнение группы запросов, и соответственно изменение БД происходит только после вызова commit
.
Но транзакции могут вести себя и по другому.
Например, можно сделать так, чтобы группа запросов даже до коммит производила изменения в БД, но если коммит так и не произошел, эти изменения откатятся. То есть коммит в этом случае просто делает так, чтобы изменения произведенные в базе там остались.
По умолчанию, как уже можно было понять, стоит первый вариант из двух приведенных. Он неявно установлен с помощью поля TRANSACTION_READ_COMMITTED
.
Первый вариант исключает возможность грязного чтения. При втором же варианте возможно грязное чтение. Его уже нужно устанавливать явно с помощью поля TRANSACTIONS_READ_UNCOMMITED
.
Грязное чтение
Допустим есть транзакция А
и транзакция В
, которые выполняются в разных потоках параллельно при этом обе транзакции выполняются в режиме TRANSACTIONS_READ_UNCOMMITED,
то есть грязное чтение возможно.
К транзакции А
в потоке где она выполняется будет впоследствии применен rollback.
Значит данные в БД измененные транзакцией А
в конце концов окажутся недействительными, так как к ним будет применен rollback.
Но пока rollback не произошел к БД, которая была изменена транзакцией А
могут совершать запросы другие потоки, например, поток в котором выполняется транзакция В
.
Очевидно, что если транзакция В
обратиться к данным в БД, которые изменила транзакция А
и которые в результате окажутся недействительными из-за примененного к этим данным rollback, это может быть очень нежелательным. То есть транзакция В
будет работать с данными, которых в БД в результате не будет, так как к ним будет применен rollback.
Давайте уберем грязное чтение
Как уже говорилось, по умолчанию стоит T
RANSACTION_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
мин.
Изоляция транзакций. Фантомное чтение
14
мин.
Безопасные запросы с PreparedStatement
12
мин.