Всегда ли будет запрет параллельного чтения при чтении одной записи транзакциями из разных потоков?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Параллельное чтение в транзакциях из разных потоков
Этот вопрос касается уровней изоляции транзакций (ACID) и того, может ли одновременно несколько потоков читать одну и ту же запись. Ответ: НЕТ, не всегда будет запрет. Это зависит от уровня изоляции.
Уровни изоляции транзакций (ISO/IEC)
В SQL стандарте определены 4 уровня изоляции, от самого слабого к самому строгому:
1. READ UNCOMMITTED (самый низкий)
Означает: транзакция может читать незакомиченные изменения (Dirty Read).
// Поток 1
Connection conn1 = getConnection();
conn1.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
conn1.setAutoCommit(false);
Statement stmt1 = conn1.createStatement();
ResultSet rs1 = stmt1.executeQuery("SELECT balance FROM account WHERE id = 1");
rs1.next();
int balance1 = rs1.getInt("balance"); // Может быть незакомиченное значение!
conn1.commit();
// Поток 2 в это же время
Connection conn2 = getConnection();
conn2.setAutoCommit(false);
Statement stmt2 = conn2.createStatement();
stmt2.executeUpdate("UPDATE account SET balance = 5000 WHERE id = 1");
// НЕ коммитим! (Rollback может быть позже)
// Поток 1 видит новое значение (5000) хотя оно не закомичено
// Если Поток 2 откатится — Поток 1 видел "грязное" значение (Dirty Read)
Характеристика: ПАРАЛЛЕЛЬНОЕ ЧТЕНИЕ РАЗРЕШЕНО
2. READ COMMITTED (уровень по умолчанию в большинстве БД)
Означает: читаются только закомиченные данные, но возможны фантомные чтения.
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);
// Поток 1
Statement stmt1 = conn.createStatement();
int balance1 = 1000;
ResultSet rs1 = stmt1.executeQuery("SELECT balance FROM account WHERE id = 1");
rs1.next();
balance1 = rs1.getInt("balance"); // Может быть закомиченное изменение
// Пауза 2 секунды
rs1 = stmt1.executeQuery("SELECT balance FROM account WHERE id = 1");
rs1.next();
int balance2 = rs1.getInt("balance"); // Может быть ДРУГОЙ результат!
// Это Non-repeatable Read
conn.commit();
ПАРАЛЛЕЛЬНОЕ ЧТЕНИЕ РАЗРЕШЕНО, но видны только закомиченные изменения.
3. REPEATABLE READ
Означает: одна транзакция видит консистентный снимок данных на момент начала. Но возможны фантомные строки.
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
conn.setAutoCommit(false);
// Поток 1
Statement stmt = conn.createStatement();
int balance1 = 1000;
ResultSet rs = stmt.executeQuery("SELECT balance FROM account WHERE id = 1");
rs.next();
balance1 = rs.getInt("balance"); // Например: 1000
// В это время Поток 2 изменяет и коммитит новое значение
Thread.sleep(2000); // Пауза
// Но Поток 1 ВСЕГДА видит 1000 (снимок начала транзакции)
rs = stmt.executeQuery("SELECT balance FROM account WHERE id = 1");
rs.next();
int balance2 = rs.getInt("balance"); // Всё ещё 1000
conn.commit();
ПАРАЛЛЕЛЬНОЕ ЧТЕНИЕ РАЗРЕШЕНО, но каждая транзакция видит снимок.
4. SERIALIZABLE (самый высокий)
Означает: полная изоляция, как если бы транзакции выполнялись последовательно.
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);
// Поток 1
Statement stmt = conn.createStatement();
int balance = 1000;
ResultSet rs = stmt.executeQuery("SELECT balance FROM account WHERE id = 1");
rs.next();
balance = rs.getInt("balance");
// Поток 2 пытается это же прочитать — может быть заблокирован!
Connection conn2 = getConnection();
conn2.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn2.setAutoCommit(false);
Statement stmt2 = conn2.createStatement();
int balance2 = 1000;
// Может ждать, пока Поток 1 закончит транзакцию
ResultSet rs2 = stmt2.executeQuery("SELECT balance FROM account WHERE id = 1");
ПАРАЛЛЕЛЬНОЕ ЧТЕНИЕ МОЖЕТ БЫТЬ ЗАПРЕЩЕНО (или сериализовано).
Таблица: явления и уровни изоляции
| Уровень | Dirty Read | Non-repeatable Read | Phantom Read |
|---|---|---|---|
| READ_UNCOMMITTED | Да | Да | Да |
| READ_COMMITTED | Нет | Да | Да |
| REPEATABLE_READ | Нет | Нет | Да (или Нет в некоторых БД) |
| SERIALIZABLE | Нет | Нет | Нет |
Практический пример на PostgreSQL
public class TransactionIsolationExample {
public static void main(String[] args) throws Exception {
// Настройка БД: PostgreSQL
String url = "jdbc:postgresql://localhost:5432/mydb";
// Поток 1: READ COMMITTED
Thread t1 = new Thread(() -> {
try {
Connection conn = DriverManager.getConnection(url, "user", "pass");
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);
for (int i = 0; i < 3; i++) {
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT balance FROM account WHERE id = 1");
rs.next();
System.out.println("[Поток 1] Попытка " + i + ": " + rs.getInt("balance"));
Thread.sleep(1000);
}
conn.commit();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
});
// Поток 2: изменяет и коммитит
Thread t2 = new Thread(() -> {
try {
Thread.sleep(500); // Начинает позже
Connection conn = DriverManager.getConnection(url, "user", "pass");
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.executeUpdate("UPDATE account SET balance = 2000 WHERE id = 1");
conn.commit();
System.out.println("[Поток 2] Изменили и закомитили на 2000");
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
// Вывод:
// [Поток 1] Попытка 0: 1000
// [Поток 2] Изменили и закомитили на 2000
// [Поток 1] Попытка 1: 2000 <- Видит новое значение!
// [Поток 1] Попытка 2: 2000
Блокировки на уровне БД
Когда транзакция читает данные, БД может установить блокировки:
public class DatabaseLocks {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:mysql://...");
// SELECT ... FOR UPDATE (явная блокировка)
// Поток 1
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT balance FROM account WHERE id = 1 FOR UPDATE"
);
rs.next();
int balance = rs.getInt("balance");
// Поток 2 попытается читать эту же строку
// В зависимости от уровня изоляции:
// - Может прочитать (READ_COMMITTED)
// - Может ждать (SERIALIZABLE)
// - Может получить снимок (REPEATABLE_READ)
}
}
Реальная сценарий: банковская система
public class BankTransactionExample {
public static void transferMoney(int fromId, int toId, double amount) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://...");
try {
// SERIALIZABLE для критичных операций
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);
// 1. Читаем баланс отправителя
Statement stmt1 = conn.createStatement();
ResultSet rs1 = stmt1.executeQuery(
"SELECT balance FROM account WHERE id = " + fromId
);
rs1.next();
double fromBalance = rs1.getDouble("balance");
if (fromBalance < amount) {
throw new SQLException("Недостаточно средств");
}
Thread.sleep(100); // Имитация обработки
// 2. Читаем баланс получателя
Statement stmt2 = conn.createStatement();
ResultSet rs2 = stmt2.executeQuery(
"SELECT balance FROM account WHERE id = " + toId
);
rs2.next();
double toBalance = rs2.getDouble("balance");
// 3. Обновляем оба счёта
Statement stmt3 = conn.createStatement();
stmt3.executeUpdate(
"UPDATE account SET balance = balance - " + amount +
" WHERE id = " + fromId
);
Statement stmt4 = conn.createStatement();
stmt4.executeUpdate(
"UPDATE account SET balance = balance + " + amount +
" WHERE id = " + toId
);
conn.commit();
} catch (Exception e) {
conn.rollback();
throw new SQLException(e);
} finally {
conn.close();
}
}
}
Ответ на вопрос
НЕТ, не всегда будет запрет параллельного чтения.
Это зависит от уровня изоляции:
- READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ: параллельное чтение разрешено
- SERIALIZABLE: может быть запрещено или заблокировано
Дополнительно:
- По умолчанию: большинство БД используют READ COMMITTED
- Для критичных операций: используйте SERIALIZABLE
- Для оптимизации: помните, что повышение уровня изоляции снижает параллелизм
- В Java: используйте
setTransactionIsolation()для явного указания уровня
Выбор уровня изоляции — это балансировка между консистентностью данных и производительностью.