Бесплатный курс по Java: от основ до продвинутого уровня
Изоляция транзакций в Java. Грязное чтение
Две или более выполняющиеся параллельно транзакции часто должны быть изолированы друг от друга.
Есть три случая когда необходимо изолировать транзакции друг от друга:
грязное чтение,
неповторяющееся чтение
фантомное чтение.
Начнем с грязного чтения.
В прошлых уроках о транзакциях мы говорили, что выполнение группы запросов, и соответственно изменение БД происходит только после вызова commit.
Но транзакции могут вести себя и по другому.
Например, можно сделать так, чтобы группа запросов даже до коммит производила изменения в БД, но если коммит так и не произошел, эти изменения откатятся. То есть коммит в этом случае просто делает так, чтобы изменения произведенные в базе там остались.
По умолчанию, как уже можно было понять, стоит первый вариант из двух приведенных. Он неявно установлен с помощью поляTRANSACTION_READ_COMMITTED.
Первый вариант исключает возможность грязного чтения. При втором же варианте возможно грязное чтение. Его уже нужно устанавливать явно с помощью поля TRANSACTIONS_READ_UNCOMMITED.
Грязное чтение
Допустим есть транзакция А и транзакция В, которые выполняются в разных потокахпараллельно при этом обе транзакции выполняются в режиме TRANSACTIONS_READ_UNCOMMITED,то есть грязное чтение возможно.
К транзакции А в потоке где она выполняется будет впоследствии применен rollback.
Значит данные в БД измененные транзакцией Ав конце концов окажутся недействительными,так как к ним будет применен rollback.
Но пока rollback не произошел к БД, которая была изменена транзакцией Амогут совершать запросы другие потоки, например, поток в котором выполняется транзакция В.
Очевидно, что если транзакция В обратиться к данным в БД, которые изменила транзакция А и которые в результате окажутся недействительными из-за примененного к этим данным rollback, это может быть очень нежелательным. То есть транзакция В будет работать с данными, которых в БД в результате не будет, так как к ним будет применен rollback.
Давайте уберем грязное чтение
Как уже говорилось, по умолчанию стоит TRANSACTION_READ_COMMITTED, поэтому грязного чтения не будет даже если не устанавливать TRANSACTION_READ_COMMITTEDявно.
Пример программы:
import java.sql.*;
public class IsolationsDirty {
public static void main(String[] args)
throws ClassNotFoundException,
SQLException, InterruptedException {
//здесь идет транзакция А
Class.forName(“com.mysql.cj.jdbc.Driver”);
Connection connection =
DriverManager.getConnection(
“jdbc:mysql://localhost/storage”,
“root”, “07998MSD”);
Statement statement = connection.createStatement();
connection.setAutoCommit(false);
//установим режим транзакции при котором
//теперь невозможно грязное чтение.
//На самом деле TRANSACTION_READ_COMMITTED
//как уже говорилось установлен по умолчанию
//поэтому эту строчку кода можно не писать.
connection.setTransactionIsolation(
Connection.TRANSACTION_READ_COMMITTED);
//изменим имя второй книги в таблице
//Важно что теперь грязное чтение невозможно
//то есть запрос ниже выполнится только при следующем
//коммите. То есть транзакция В не будет читать
//данные, которые в итоге окажутся недействительными
statement.executeUpdate(“update books set name = ”
+”‘another name’ where id = 2″);
//Запускаем поток транзакции В
new TransactionB().start();
//при этом на две секунды останавливаем
//поток транзакции А, то есть текущий.
Thread.sleep(2000);
//После того как данные книг считаны
//транзакцией В, с помощью rollback делаем
//так чтобы изменения БД, которые должна сделать
//транзакция А при следующем коммите
//(то есть изменение второй книги) не случились.
connection.rollback();
}
static class TransactionB extends Thread {
@Override
public void run() {
//здесь идет транзакция В
try {
Connection connection =
DriverManager.getConnection(
“jdbc:mysql://localhost/storage”,
“root”, “07998MSD”);
Statement statement = connection.createStatement();
connection.setAutoCommit(false);
connection.setTransactionIsolation(
Connection.TRANSACTION_READ_COMMITTED);
//Пока транзакция А остановлена на 2 сек
//Извлекаем и выводим на консоль данные книг.
//В этом случае данные второй книги
//не изменены в БД транзакцией А.
//Так как чтобы они вступили в силу нужно
//чтобы случился коммит транзакции А.
ResultSet resultSet =
statement.executeQuery(“SELECT * FROM books”);
while (resultSet.next()) {
System.out.println(“name: ”
+ resultSet.getString(“name”));
}
} catch (SQLException e){}
}
}
}
Скомпилируем, запустим программу:
Как видим, грязного чтения не произошло. Update запрос, который хотела выполнить транзакция А не был закоммичен, поэтому транзакция Вне читала никаких таких данных, которые в итоге оказались бы недействительными.