vault backup: 2024-06-19 20:18:52
This commit is contained in:
parent
b0f3b8e995
commit
3a3f080c0c
18
.obsidian/plugins/home-tab/data.json
vendored
18
.obsidian/plugins/home-tab/data.json
vendored
@ -24,24 +24,24 @@
|
||||
"unresolvedLinks": false,
|
||||
"recentFilesStore": [
|
||||
{
|
||||
"filepath": "_inbox/Неповторяющееся чтение.md",
|
||||
"timestamp": 1718817191076
|
||||
"filepath": "_inbox/Read committed.md",
|
||||
"timestamp": 1718817519939
|
||||
},
|
||||
{
|
||||
"filepath": "knowledge/dev/database/Уровни изоляций транзакций БД.md",
|
||||
"timestamp": 1718817183091
|
||||
"timestamp": 1718817518532
|
||||
},
|
||||
{
|
||||
"filepath": "_inbox/Потерянное обновление.md",
|
||||
"timestamp": 1718817154181
|
||||
"filepath": "_inbox/Транзакция БД.md",
|
||||
"timestamp": 1718817515846
|
||||
},
|
||||
{
|
||||
"filepath": "_inbox/Repeatable read.md",
|
||||
"timestamp": 1718817107184
|
||||
"filepath": "_inbox/Фантомное чтение.md",
|
||||
"timestamp": 1718817514775
|
||||
},
|
||||
{
|
||||
"filepath": "_inbox/Serializable.md",
|
||||
"timestamp": 1718817019589
|
||||
"filepath": "_inbox/Read uncommitted.md",
|
||||
"timestamp": 1718817512090
|
||||
}
|
||||
],
|
||||
"bookmarkedFileStore": [],
|
||||
|
@ -1,13 +1,25 @@
|
||||
{
|
||||
"recentFiles": [
|
||||
{
|
||||
"basename": "Неповторяющееся чтение",
|
||||
"path": "_inbox/Неповторяющееся чтение.md"
|
||||
"basename": "Read committed",
|
||||
"path": "_inbox/Read committed.md"
|
||||
},
|
||||
{
|
||||
"basename": "Уровни изоляций транзакций БД",
|
||||
"path": "knowledge/dev/database/Уровни изоляций транзакций БД.md"
|
||||
},
|
||||
{
|
||||
"basename": "Транзакция БД",
|
||||
"path": "_inbox/Транзакция БД.md"
|
||||
},
|
||||
{
|
||||
"basename": "Фантомное чтение",
|
||||
"path": "_inbox/Фантомное чтение.md"
|
||||
},
|
||||
{
|
||||
"basename": "Read uncommitted",
|
||||
"path": "_inbox/Read uncommitted.md"
|
||||
},
|
||||
{
|
||||
"basename": "Потерянное обновление",
|
||||
"path": "_inbox/Потерянное обновление.md"
|
||||
@ -17,12 +29,12 @@
|
||||
"path": "_inbox/Repeatable read.md"
|
||||
},
|
||||
{
|
||||
"basename": "Serializable",
|
||||
"path": "_inbox/Serializable.md"
|
||||
"basename": "Неповторяющееся чтение",
|
||||
"path": "_inbox/Неповторяющееся чтение.md"
|
||||
},
|
||||
{
|
||||
"basename": "Транзакция БД",
|
||||
"path": "_inbox/Транзакция БД.md"
|
||||
"basename": "Serializable",
|
||||
"path": "_inbox/Serializable.md"
|
||||
},
|
||||
{
|
||||
"basename": "Проблемы при параллельном выполнении нескольких транзакций",
|
||||
@ -36,10 +48,6 @@
|
||||
"basename": "Безмастерная репликация",
|
||||
"path": "_inbox/Безмастерная репликация.md"
|
||||
},
|
||||
{
|
||||
"basename": "Фантомное чтение",
|
||||
"path": "_inbox/Фантомное чтение.md"
|
||||
},
|
||||
{
|
||||
"basename": "Полу-синхронная репликация",
|
||||
"path": "_inbox/Полу-синхронная репликация.md"
|
||||
@ -48,14 +56,6 @@
|
||||
"basename": "Грязное чтение",
|
||||
"path": "_inbox/Грязное чтение.md"
|
||||
},
|
||||
{
|
||||
"basename": "Read committed",
|
||||
"path": "_inbox/Read committed.md"
|
||||
},
|
||||
{
|
||||
"basename": "Read uncommitted",
|
||||
"path": "_inbox/Read uncommitted.md"
|
||||
},
|
||||
{
|
||||
"basename": "Memcached",
|
||||
"path": "_inbox/Memcached.md"
|
||||
|
@ -12,3 +12,5 @@ linked:
|
||||
---
|
||||
**Read committed (чтение фиксированных данных).** Большинство промышленных СУБД по умолчанию используют именно этот уровень. На этом уровне обеспечивается защита от «грязного» чтения, тем не менее, в процессе работы одной транзакции другая может быть успешно завершена и сделанные ею изменения зафиксированы. В итоге первая транзакция будет работать с другим набором данных. ^11df20
|
||||
|
||||
Проблемы:
|
||||
- [[непов]]
|
@ -11,3 +11,9 @@ parents:
|
||||
linked:
|
||||
---
|
||||
**Read uncommitted (чтение незафиксированных данных).** Если несколько параллельных транзакций пытаются изменять одну и ту же строку таблицы, то в окончательном варианте строка будет иметь значение, определенное всем набором успешно выполненных транзакций. ^52421e
|
||||
|
||||
Проблемы:
|
||||
- [Грязное чтение](Грязное%20чтение.md)
|
||||
- [Неповторяющееся чтение](Неповторяющееся%20чтение.md)
|
||||
- [Фантомное чтение](Фантомное%20чтение.md)
|
||||
- [Потерянное обновление](Потерянное%20обновление.md)
|
@ -21,3 +21,86 @@ link: https://struchkov.dev/blog/ru/transactional-isolation-levels/#%D0%BD%D0%B5
|
||||
Во второй транзакции устанавливаем пользователю баланс равный 100_000. После чего закрываем вторую транзакцию. Баланс успешно обновился в БД.
|
||||
|
||||
Первая транзакция продолжает выполнение. Снова запрашивает баланс пользователя из БД, на этот раз получает значение 100_000. Таким образом, вторая транзакция повлияла на выполнение первой.
|
||||
|
||||
```java
|
||||
public class NonRepeatableRead {
|
||||
|
||||
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try (
|
||||
final Connection connection = Repository.getConnection();
|
||||
final Statement statement = connection.createStatement()
|
||||
) {
|
||||
connection.setAutoCommit(false);
|
||||
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||
|
||||
final ResultSet resultSetOne = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
||||
while (resultSetOne.next()) {
|
||||
final String balance = resultSetOne.getString("balance");
|
||||
System.out.println("[one] Balance: " + balance);
|
||||
}
|
||||
|
||||
new OtherTransaction().start();
|
||||
Thread.sleep(2000);
|
||||
|
||||
final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
||||
while (resultSetTwo.next()) {
|
||||
final String balance = resultSetTwo.getString("balance");
|
||||
System.out.println("[one] Balance: " + balance);
|
||||
}
|
||||
|
||||
} catch (SQLException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static class OtherTransaction extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try (
|
||||
final Connection connection = Repository.getConnection();
|
||||
final Statement statement = connection.createStatement()
|
||||
) {
|
||||
connection.setAutoCommit(false);
|
||||
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||
|
||||
statement.executeUpdate("UPDATE person SET balance = 100000 WHERE id = 1");
|
||||
connection.commit();
|
||||
|
||||
final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1");
|
||||
while (resultSetTwo.next()) {
|
||||
final String balance = resultSetTwo.getString("balance");
|
||||
System.out.println("[two] Balance: " + balance);
|
||||
}
|
||||
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Результат работы примера:
|
||||
|
||||
```log
|
||||
[one] Balance: 1000
|
||||
[two] Balance: 100000
|
||||
[one] Balance: 100000
|
||||
```
|
||||
|
||||
Для устранения этой проблемы воспользуемся известным нам уровнем изоляции [Repeatable read](Repeatable%20read.md).
|
||||
|
||||
Тогда при выполнении примера первая транзакция дважды получит значение баланса равным 1000, несмотря на то, что в БД будет уже 100_000.
|
||||
|
||||
Результат работы примера с `REPEATABLE_READ`:
|
||||
```log
|
||||
[one] Balance: 1000
|
||||
[two] Balance: 100000
|
||||
[one] Balance: 1000
|
||||
```
|
||||
## Дополнительные материалы
|
||||
- [Пример на Java](https://github.com/Example-uPagge/transactional/blob/master/jdbc-transaction/src/main/java/dev/struchkov/example/transaction/problems/NonRepeatableRead.java)
|
@ -10,5 +10,71 @@ zero-link:
|
||||
parents:
|
||||
- "[[Проблемы при параллельном выполнении нескольких транзакций]]"
|
||||
linked:
|
||||
link: https://struchkov.dev/blog/ru/transactional-isolation-levels/#%D1%84%D0%B0%D0%BD%D1%82%D0%BE%D0%BC%D0%BD%D0%BE%D0%B5-%D1%87%D1%82%D0%B5%D0%BD%D0%B8%D0%B5
|
||||
---
|
||||
**Фантомное чтение (phantom reads).** В результатах повторяющегося запроса появляются и исчезают строки, которые в данный момент модифицирует параллельная транзакция. ^ebb2ec
|
||||
|
||||
Эта проблема присутствует на уровне изоляции [Repeatable read](Repeatable%20read.md), [Read committed](Read%20committed.md) и [Read uncommitted](Read%20uncommitted.md).
|
||||
|
||||
Рассмотрим пример. Открываем первую транзакцию. Запрашиваем количество строк в таблице пользователей, получаем ответ 2. Далее стартуем вторую транзакцию, а поток с первой транзакцией засыпает. Вторая транзакция добавляет новую запись в таблицу пользователей и коммитит изменения. После этого первая транзакция продолжается. Снова запрашиваем количество строк, получаем на этот раз ответ 3.
|
||||
|
||||
```java
|
||||
public class PhantomRead {
|
||||
|
||||
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try(
|
||||
final Connection connection = Repository.getConnection();
|
||||
final Statement statement = connection.createStatement()
|
||||
) {
|
||||
connection.setAutoCommit(false);
|
||||
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||
|
||||
final ResultSet resultSet = statement.executeQuery("SELECT count(*) FROM person");
|
||||
while (resultSet.next()) {
|
||||
final int count = resultSet.getInt(1);
|
||||
System.out.println("Count: " + count);
|
||||
}
|
||||
|
||||
new OtherTransaction().start();
|
||||
Thread.sleep(2000);
|
||||
|
||||
final ResultSet resultSetTwo = statement.executeQuery("SELECT count(*) FROM person");
|
||||
while (resultSetTwo.next()) {
|
||||
final int count = resultSetTwo.getInt(1);
|
||||
System.out.println("Count: " + count);
|
||||
}
|
||||
|
||||
} catch (SQLException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static class OtherTransaction extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try (
|
||||
final Connection connection = Repository.getConnection();
|
||||
final Statement statement = connection.createStatement()
|
||||
) {
|
||||
connection.setAutoCommit(false);
|
||||
connection.setTransactionIsolation(ISOLATION_LEVEL);
|
||||
|
||||
statement.executeUpdate("INSERT INTO person(id, balance) values (3, 1000)");
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Чтобы устранить эту проблему, используем самый изолированный уровень [Serializable](Serializable.md). Однако, для PostgreSQL будет достаточно и `REPEATABLE_READ`.
|
||||
|
||||
Но мы всё равно установим `SERIALIZABLE`. Теперь в первой транзакции нам дважды возвращается ответ 2, несмотря на то, что в таблице появились новые записи.
|
||||
|
||||
## Дополнительные материалы
|
||||
- [Пример на Java](https://github.com/Example-uPagge/transactional/blob/master/jdbc-transaction/src/main/java/dev/struchkov/example/transaction/problems/PhantomRead.java)
|
Loading…
Reference in New Issue
Block a user