diff --git a/.obsidian/plugins/home-tab/data.json b/.obsidian/plugins/home-tab/data.json index 9cddc6d4..f7ecaf94 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": 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": [], diff --git a/.obsidian/plugins/recent-files-obsidian/data.json b/.obsidian/plugins/recent-files-obsidian/data.json index 939ff0b9..7d56f7e7 100644 --- a/.obsidian/plugins/recent-files-obsidian/data.json +++ b/.obsidian/plugins/recent-files-obsidian/data.json @@ -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" diff --git a/_inbox/Read committed.md b/_inbox/Read committed.md index 2461564d..59fbfafc 100644 --- a/_inbox/Read committed.md +++ b/_inbox/Read committed.md @@ -12,3 +12,5 @@ linked: --- **Read committed (чтение фиксированных данных).** Большинство промышленных СУБД по умолчанию используют именно этот уровень. На этом уровне обеспечивается защита от «грязного» чтения, тем не менее, в процессе работы одной транзакции другая может быть успешно завершена и сделанные ею изменения зафиксированы. В итоге первая транзакция будет работать с другим набором данных. ^11df20 +Проблемы: +- [[непов]] \ No newline at end of file diff --git a/_inbox/Read uncommitted.md b/_inbox/Read uncommitted.md index dd82f25b..4cfa6b88 100644 --- a/_inbox/Read uncommitted.md +++ b/_inbox/Read uncommitted.md @@ -10,4 +10,10 @@ parents: - "[[Уровни изоляций транзакций БД]]" linked: --- -**Read uncommitted (чтение незафиксированных данных).** Если несколько параллельных транзакций пытаются изменять одну и ту же строку таблицы, то в окончательном варианте строка будет иметь значение, определенное всем набором успешно выполненных транзакций. ^52421e \ No newline at end of file +**Read uncommitted (чтение незафиксированных данных).** Если несколько параллельных транзакций пытаются изменять одну и ту же строку таблицы, то в окончательном варианте строка будет иметь значение, определенное всем набором успешно выполненных транзакций. ^52421e + +Проблемы: +- [Грязное чтение](Грязное%20чтение.md) +- [Неповторяющееся чтение](Неповторяющееся%20чтение.md) +- [Фантомное чтение](Фантомное%20чтение.md) +- [Потерянное обновление](Потерянное%20обновление.md) \ No newline at end of file diff --git a/_inbox/Неповторяющееся чтение.md b/_inbox/Неповторяющееся чтение.md index 4fa231b5..923a8b96 100644 --- a/_inbox/Неповторяющееся чтение.md +++ b/_inbox/Неповторяющееся чтение.md @@ -20,4 +20,87 @@ link: https://struchkov.dev/blog/ru/transactional-isolation-levels/#%D0%BD%D0%B5 Во второй транзакции устанавливаем пользователю баланс равный 100_000. После чего закрываем вторую транзакцию. Баланс успешно обновился в БД. -Первая транзакция продолжает выполнение. Снова запрашивает баланс пользователя из БД, на этот раз получает значение 100_000. Таким образом, вторая транзакция повлияла на выполнение первой. \ No newline at end of file +Первая транзакция продолжает выполнение. Снова запрашивает баланс пользователя из БД, на этот раз получает значение 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) \ No newline at end of file diff --git a/_inbox/Фантомное чтение.md b/_inbox/Фантомное чтение.md index 0a129e12..ac2e13b5 100644 --- a/_inbox/Фантомное чтение.md +++ b/_inbox/Фантомное чтение.md @@ -9,6 +9,72 @@ zero-link: - "[[00 Базы Данных]]" parents: - "[[Проблемы при параллельном выполнении нескольких транзакций]]" -linked: +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 \ No newline at end of file +**Фантомное чтение (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) \ No newline at end of file