diff --git a/.obsidian/plugins/home-tab/data.json b/.obsidian/plugins/home-tab/data.json index b809d705..e26197a8 100644 --- a/.obsidian/plugins/home-tab/data.json +++ b/.obsidian/plugins/home-tab/data.json @@ -24,24 +24,24 @@ "unresolvedLinks": false, "recentFilesStore": [ { - "filepath": "_inbox/Безмастерная репликация.md", - "timestamp": 1718816632068 + "filepath": "knowledge/dev/database/Уровни изоляций транзакций БД.md", + "timestamp": 1718816928649 + }, + { + "filepath": "_inbox/Транзакция БД.md", + "timestamp": 1718816827388 }, { "filepath": "knowledge/dev/database/Проблемы при параллельном выполнении нескольких транзакций.md", - "timestamp": 1718816630837 + "timestamp": 1718816823586 }, { "filepath": "_inbox/Потерянное обновление.md", - "timestamp": 1718816620297 + "timestamp": 1718816800840 }, { - "filepath": "_inbox/Неповторяющееся чтение.md", - "timestamp": 1718816570534 - }, - { - "filepath": "_inbox/Фантомное чтение.md", - "timestamp": 1718816475988 + "filepath": "_inbox/Race condition.md", + "timestamp": 1718816771915 } ], "bookmarkedFileStore": [], diff --git a/.obsidian/plugins/recent-files-obsidian/data.json b/.obsidian/plugins/recent-files-obsidian/data.json index 3d4d6d6e..424f350f 100644 --- a/.obsidian/plugins/recent-files-obsidian/data.json +++ b/.obsidian/plugins/recent-files-obsidian/data.json @@ -1,8 +1,12 @@ { "recentFiles": [ { - "basename": "Безмастерная репликация", - "path": "_inbox/Безмастерная репликация.md" + "basename": "Уровни изоляций транзакций БД", + "path": "knowledge/dev/database/Уровни изоляций транзакций БД.md" + }, + { + "basename": "Транзакция БД", + "path": "_inbox/Транзакция БД.md" }, { "basename": "Проблемы при параллельном выполнении нескольких транзакций", @@ -12,6 +16,14 @@ "basename": "Потерянное обновление", "path": "_inbox/Потерянное обновление.md" }, + { + "basename": "Race condition", + "path": "_inbox/Race condition.md" + }, + { + "basename": "Безмастерная репликация", + "path": "_inbox/Безмастерная репликация.md" + }, { "basename": "Неповторяющееся чтение", "path": "_inbox/Неповторяющееся чтение.md" @@ -24,10 +36,6 @@ "basename": "Полу-синхронная репликация", "path": "_inbox/Полу-синхронная репликация.md" }, - { - "basename": "Уровни изоляций транзакций БД", - "path": "knowledge/dev/database/Уровни изоляций транзакций БД.md" - }, { "basename": "Грязное чтение", "path": "_inbox/Грязное чтение.md" @@ -40,10 +48,6 @@ "basename": "Read uncommitted", "path": "_inbox/Read uncommitted.md" }, - { - "basename": "Транзакция БД", - "path": "_inbox/Транзакция БД.md" - }, { "basename": "Memcached", "path": "_inbox/Memcached.md" @@ -68,10 +72,6 @@ "basename": "Reliability", "path": "_inbox/Reliability.md" }, - { - "basename": "Race condition", - "path": "_inbox/Race condition.md" - }, { "basename": "Home", "path": "Home.md" diff --git a/_inbox/Race condition.md b/_inbox/Race condition.md index d5081276..b89bace2 100644 --- a/_inbox/Race condition.md +++ b/_inbox/Race condition.md @@ -10,3 +10,4 @@ zero-link: parents: linked: --- +Пример race condition в БД: ![](Pasted%20image%2020240619200549.png) \ No newline at end of file diff --git a/_inbox/Безмастерная репликация.md b/_inbox/Безмастерная репликация.md index 683ebcb0..21086e7b 100644 --- a/_inbox/Безмастерная репликация.md +++ b/_inbox/Безмастерная репликация.md @@ -40,5 +40,5 @@ linked: - [Нестрогий кворум](Нестрогий%20кворум.md). Возможно чтение старых данных при W+R < N - Нет отката транзакций. - Как в таком случае работает обновление при чтении или противодействие энтропии, ведь эти данные становятся новыми. -- Конфликт записей и [потерянное обновление](Проблемы%20при%20параллельном%20выполнении%20нескольких%20транзакций.md) +- Конфликт записей и [Потерянное обновление](Потерянное%20обновление.md). - Проблемы с линеаризуемостью. \ No newline at end of file diff --git a/_inbox/Потерянное обновление.md b/_inbox/Потерянное обновление.md index d2f88a95..99709ce6 100644 --- a/_inbox/Потерянное обновление.md +++ b/_inbox/Потерянное обновление.md @@ -11,4 +11,81 @@ parents: - "[[Проблемы при параллельном выполнении нескольких транзакций]]" linked: --- -**Потерянное обновление (lost update).** Две параллельные транзакции меняют одни и те же данные, при этом итоговый результат обновления предсказать невозможно. ^23d01d \ No newline at end of file +**Потерянное обновление (lost update).** Две параллельные транзакции меняют одни и те же данные, при этом итоговый результат обновления предсказать невозможно. ^23d01d + +Рассмотрим эту проблему на примере: + +```java +public class LostUpdateExample { + + public static final String READ = "SELECT person.balance FROM person WHERE id = ?"; + public static final String UPDATE = "UPDATE person SET balance = ? WHERE id = ?"; + + @SneakyThrows + public static void main(String[] args) { + + // Начинаем две транзакции. + final Connection connectionOne = getNewConnection(); + final Connection connectionTwo = getNewConnection(); + + // Первая и вторая транзакция запрашивают баланс пользователя. + // balance = 1000 + final long balanceOne = getBalance(connectionOne); + final long balanceTwo = getBalance(connectionTwo); + + // Первая транзакция готовится обновить баланс пользователю. + final PreparedStatement updateOne = connectionOne.prepareStatement(UPDATE); + updateOne.setLong(1, balanceOne + 10); + updateOne.setLong(2, 1); + updateOne.execute(); + + // Первая транзакция фиксирует изменения и завершается. + // Значение balance в базе в этот момент = 1010. + connectionOne.commit(); + connectionOne.close(); + + // Но вторая транзакция ничего не знает про изменения в БД. + // Значение balanceTwo все еще равно 1000, к этому значению мы добавляем 5. + final PreparedStatement updateTwo = connectionTwo.prepareStatement(UPDATE); + updateTwo.setLong(1, balanceTwo + 5); + updateTwo.setLong(2, 1); + updateTwo.execute(); + + // Вторая транзакция фиксирует свои изменения и завершается. + // В итоге в БД остается значение 1005, а не 1015, как хотелось бы нам. + connectionTwo.commit(); + connectionTwo.close(); + } + + private static Connection getNewConnection() throws SQLException { + final Connection connection = Repository.getConnection(); + connection.setAutoCommit(false); + connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); + return connection; + } + + private static long getBalance(Connection connectionOne) throws SQLException { + final PreparedStatement preparedStatement = connectionOne.prepareStatement(READ); + preparedStatement.setLong(1, 1); + final ResultSet resultSet = preparedStatement.executeQuery(); + resultSet.next(); + final long balanceOne = resultSet.getLong(1); + return balanceOne; + } + +} +``` + +На строках 10, 11 мы получаем 2 независимых соединения с БД, в которых отключён auto-commit и установлен уровень изоляции [Read committed](Read%20committed.md). + +На строках 15 и 16 обе транзакции получают баланс первого пользователя. Напомню, что баланс равен 1000. + +На строках 19-22 выполняется обновление баланса пользователя первой транзакцией. Для этого к полученному ранее балансу прибавляется 10. Но сейчас эти изменения не были отправлены в БД. Это произойдёт только при закрытии транзакции. + +Закрываем первую транзакцию (26, 27). Баланс пользователя в БД равен 1010. Но вторая транзакция идёт параллельно, и ничего не знает об изменении баланса пользователя. Ведь баланс мы уже считали из БД, и там было 1000. + +Вторая транзакция прибавляет к балансу пользователя 5 (31-34). После чего вторая транзакция также закрывается (38, 39). Баланс пользователя в БД равен 1005. Мы потеряли обновления, которые выполнила первая транзакция. Такое поведение называют [Race condition](Race%20condition.md). + +Изменим уровень транзакций на более изолированный. В примере у нас используется `READ_COMMITTED`, установим  в строке 45. + +В таком случае при выполнении нашего кода получаем исключение `PSQLException`. \ No newline at end of file diff --git a/_inbox/Транзакция БД.md b/_inbox/Транзакция БД.md index 611c2ff5..db2bed35 100644 --- a/_inbox/Транзакция БД.md +++ b/_inbox/Транзакция БД.md @@ -15,12 +15,18 @@ linked: Для реализации обычно используется [Журнал БД](Журнал%20БД.md). -- [Свойства транзакции БД (ACID)](Свойства%20транзакции%20БД%20(ACID).md) -- [Проблемы при параллельном выполнении нескольких транзакций](Проблемы%20при%20параллельном%20выполнении%20нескольких%20транзакций.md) -- [Уровни изоляций транзакций БД](Уровни%20изоляций%20транзакций%20БД.md) - **Как происходит commit транзакции:** - Подготовка транзакции в движке БД - Запись транзакции в лог - Завершение транзакции в движке БД -- Возврат результата клиенту \ No newline at end of file +- Возврат результата клиенту + +**Свойства транзакций БД:** +![Свойства транзакции БД (ACID)](Свойства%20транзакции%20БД%20(ACID).md) + +**Проблемы при параллельном выполнении нескольких транзакций:** +![Проблемы при параллельном выполнении нескольких транзакций](Проблемы%20при%20параллельном%20выполнении%20нескольких%20транзакций.md) + +**Уровни изоляций транзакций БД:** +![Уровни изоляций транзакций БД](Уровни%20изоляций%20транзакций%20БД.md) + diff --git a/knowledge/dev/database/Проблемы при параллельном выполнении нескольких транзакций.md b/knowledge/dev/database/Проблемы при параллельном выполнении нескольких транзакций.md index f9995253..c4a6fe10 100644 --- a/knowledge/dev/database/Проблемы при параллельном выполнении нескольких транзакций.md +++ b/knowledge/dev/database/Проблемы при параллельном выполнении нескольких транзакций.md @@ -1,7 +1,5 @@ --- -aliases: - - потерянное обновление - - lost update +aliases: [] tags: - зрелость/🌱 date: diff --git a/meta/files/Pasted image 20240619200549.png b/meta/files/Pasted image 20240619200549.png new file mode 100644 index 00000000..a4d6a2cf Binary files /dev/null and b/meta/files/Pasted image 20240619200549.png differ