Феномены чтения глазами разработчика

В современных приложениях Java работа с базами данных является одним из ключевых аспектов разработки. Взаимодействие с базами данных может происходить с использованием различных технологий, таких как JDBC и ORM (Object-Relational Mapping) фреймворки (например, Hibernate).

Однако при работе с базами данных разработчикам нужно учитывать различные феномены чтения, которые могут возникнуть в процессе исполнения транзакций. В данной статье мы рассмотрим три основных феномена чтения:

Кроме того, мы обсудим методы предотвращения этих явлений в контексте разработки с использованием JDBС, как наиболее базового метода.

Грязное чтение

Феномен грязного чтения происходит, когда транзакция получает доступ к данным, измененным, но еще не зафиксированным в рамках другой параллельной транзакции. Вследствие этого, транзакция, осуществляющая чтение, может работать с неправильными данными, в случае если вторая транзакция, вносящая изменения, в конечном итоге будет отменена (ROLLBACK).

Предположим, что у нас есть таблица products с двумя столбцами: id и price. Имеется следующая запись в таблице:

id | price
---------
1  | 100

Теперь предположим, что у нас есть две параллельные транзакции: Т1 и Т2. Т1 изменяет цену продукта, а Т2 читает данные.

BEGIN;
UPDATE products SET price = 120 WHERE id = 1;
-- Тут Т2 читает данные
ROLLBACK;

Транзакция Т2:

BEGIN;
-- Тут Т1 изменяет цену продукта
SELECT * FROM products WHERE id = 1;
COMMIT;

Пошаговое описание:

  1. Транзакция Т1 начинается с изменения цены продукта с идентификатором 1 (с 100 на 120). Изменения пока не фиксированы.
  2. Транзакция Т2 начинается и читает данные продукта с идентификатором 1. Из-за того, что изменения Т1 еще не зафиксированы, Т2 читает грязные данные (цена 120, хотя она еще не была фиксирована).
  3. Транзакция Т1 откатывается (ROLLBACK), возвращая цену продукта к исходному значению (100). Однако Т2 уже получила некорректные данные (120) и будет работать с ними дальше.
  4. Транзакция Т2 завершается.

Чтобы избежать грязного чтения при работе с JDBC, вы должны установить уровень изоляции транзакции, который предотвращает чтение незафиксированных данных. Уровень изоляции READ_COMMITTED является одним из наиболее распространенных выборов для этой цели. Вот как вы можете установить уровень изоляции транзакции в JDBC:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JDBCTransactionExample {
    public static void main(String[] args) {
        String url = "jdbc:your_database_url";
        String username = "your_username";
        String password = "your_password";

        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            // Установка уровня изоляции транзакции на READ_COMMITTED
            connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

            // Начало транзакции
            connection.setAutoCommit(false);

            // Выполнение SQL-запросов

            // Фиксация транзакции
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

В этом примере кода, уровень изоляции транзакции устанавливается на Connection.TRANSACTION_READ_COMMITTED, который гарантирует, что ваша транзакция будет читать только зафиксированные данные, предотвращая грязное чтение. Обратите внимание, что уровни изоляции транзакций могут варьироваться в зависимости от вашей СУБД, и некоторые СУБД могут иметь дополнительные уровни изоляции для лучшего контроля над параллельными транзакциями.

Неповторяемое чтение

Неповторяемое чтение – это феномен, который возникает, когда одна транзакция неоднократно читает одни и те же данные, и в то время как другая параллельная транзакция изменяет эти данные и фиксирует изменения. В результате, читающая транзакция получает разные значения при повторном чтении.

Предположим, что у нас есть таблица accounts с двумя столбцами: id и balance. Имеется следующая запись в таблице:

id | balance
---------
1  | 5000

Теперь предположим, что у нас есть две параллельные транзакции: Т1 и Т2. Т1 изменяет баланс, а Т2 читает данные дважды.

Транзакция Т1:

BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
-- Тут Т2 делает второе чтение
COMMIT;

Транзакция Т2:

BEGIN;
SELECT * FROM accounts WHERE id = 1;
-- Тут Т1 изменяет баланс и фиксирует транзакцию
SELECT * FROM accounts WHERE id = 1;
COMMIT;

Пошаговое описание:

  1. Транзакция Т2 начинается и читает данные аккаунта с идентификатором 1. Баланс равен 5000.
  2. Транзакция Т1 начинается и изменяет баланс аккаунта с идентификатором 1, вычитая 1000. Изменения пока не фиксированы.
  3. Транзакция Т1 фиксирует изменения, устанавливая баланс аккаунта с идентификатором 1 равным 4000.
  4. Транзакция Т2 делает повторное чтение данных аккаунта с идентификатором 1 и получает новый баланс, равный 4000, вместо исходных 5000.
  5. Транзакция Т2 завершается.

В данном примере, Т2 получила разные значения при повторном чтении данных из-за того, что Т1 изменила и зафиксировала данные между двумя чтениями Т2. Это явление называется неповторяемым чтением.

Чтобы избежать неповторяемого чтения при работе с JDBC, вам следует установить уровень изоляции транзакции на REPEATABLE_READ или выше. Уровень изоляции REPEATABLE_READ гарантирует, что ваша транзакция будет видеть одну и ту же версию данных в течение всей транзакции, даже если другие транзакции вносят изменения и фиксируют их. Вот как установить уровень изоляции транзакции в JDBC:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JDBCTransactionExample {
    public static void main(String[] args) {
        String url = "jdbc:your_database_url";
        String username = "your_username";
        String password = "your_password";

        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            // Установка уровня изоляции транзакции на REPEATABLE_READ
            connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

            // Начало транзакции
            connection.setAutoCommit(false);

            // Выполнение SQL-запросов

            // Фиксация транзакции
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

В этом примере кода, уровень изоляции транзакции устанавливается на Connection.TRANSACTION_REPEATABLE_READ, который гарантирует, что ваша транзакция будет видеть одну и ту же версию данных в течение всей транзакции, предотвращая неповторяемое чтение. Обратите внимание, что уровни изоляции транзакций могут варьироваться в зависимости от вашей СУБД, и некоторые СУБД могут иметь дополнительные уровни изоляции для лучшего контроля над параллельными транзакциями.

Фантомное чтение

Фантомное чтение – это феномен, который возникает, когда одна транзакция неоднократно читает набор данных, соответствующий определенному условию, и в то время как другая параллельная транзакция вставляет или удаляет строки, удовлетворяющие этому условию, и фиксирует изменения. В результате, читающая транзакция получает разное количество строк при повторном чтении.

Пример с SQL-запросами:

Предположим, что у нас есть таблица orders с двумя столбцами: id и status. Имеются следующие записи в таблице:

id | status
---------
1  | new
2  | new

Теперь предположим, что у нас есть две параллельные транзакции: Т1 и Т2. Т1 добавляет новый заказ, а Т2 читает заказы со статусом “new” дважды.

Транзакция Т1:

BEGIN;
INSERT INTO orders (id, status) VALUES (3, 'new');
-- Тут Т2 делает второе чтение
COMMIT;

Транзакция Т2:

BEGIN;
SELECT * FROM orders WHERE status = 'new';
-- Тут Т1 добавляет новый заказ и фиксирует транзакцию
SELECT * FROM orders WHERE status = 'new';
COMMIT;

Пошаговое описание:

  1. Транзакция Т2 начинается и читает заказы со статусом “new”. В результате выборки получены заказы с идентификаторами 1 и 2.
  2. Транзакция Т1 начинается и добавляет новый заказ с идентификатором 3 и статусом “new”. Изменения пока не фиксированы.
  3. Транзакция Т1 фиксирует изменения, сохраняя новый заказ в таблице.
  4. Транзакция Т2 делает повторное чтение заказов со статусом “new” и обнаруживает новую строку с идентификатором 3, которая не была видна при первом чтении.
  5. Транзакция Т2 завершается.

В данном примере, Т2 обнаружила “фантомную” строку при повторном чтении данных из-за того, что Т1 добавила новую строку и зафиксировала изменения между двумя чтениями Т2. Это явление называется фантомным чтением.

Чтобы избежать фантомного чтения при работе с JDBC, вам следует установить уровень изоляции транзакции на SERIALIZABLE. Уровень изоляции SERIALIZABLE предоставляет самый строгий контроль над параллельными транзакциями и гарантирует, что ваша транзакция не будет видеть никаких новых строк, добавленных другими транзакциями, во время своего выполнения. Вот как установить уровень изоляции транзакции в JDBC:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JDBCTransactionExample {
    public static void main(String[] args) {
        String url = "jdbc:your_database_url";
        String username = "your_username";
        String password = "your_password";

        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            // Установка уровня изоляции транзакции на SERIALIZABLE
            connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

            // Начало транзакции
            connection.setAutoCommit(false);

            // Выполнение SQL-запросов

            // Фиксация транзакции
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

В этом примере кода, уровень изоляции транзакции устанавливается на Connection.TRANSACTION_SERIALIZABLE, который гарантирует, что ваша транзакция будет изолирована от изменений, сделанных другими параллельными транзакциями, предотвращая фантомное чтение. Обратите внимание, что уровни изоляции транзакций могут варьироваться в зависимости от вашей СУБД, и некоторые СУБД могут иметь дополнительные уровни изоляции для лучшего контроля над параллельными транзакциями.

В данной статье мы рассмотрели феномены чтения в контексте разработки ПО.

Leave a Reply

Your email address will not be published. Required fields are marked *