Статьи

Руководство для начинающих читать и писать косые явления

Вступление

В моей статье о ACID и транзакциях базы данных я представил три явления, описанных стандартом SQL:

  • грязное чтение
  • неповторяемое чтение
  • фантомное чтение

Несмотря на то, что они хороши для различения четырех уровней изоляции (Чтение незафиксированного, Чтение зафиксированного, Повторяемое чтение и Сериализуемый), в действительности есть и другие явления, которые необходимо учитывать. В статье 1995 года (Критика уровней изоляции SQL ANSI) представлены другие явления, которые не включены в стандартную спецификацию.

В своей книге « Постоянство в Java» я решил настаивать на главе «Транзакции», поскольку она очень важна как для эффективности, так и для эффективности доступа к данным.

Доменная модель

Для следующих примеров я собираюсь использовать следующие две сущности:

readwriteskew

В нашем вымышленном приложении при изменении заголовка сообщения автор должен быть записан в соответствующей записи PostDetails . Если перекос чтения и записи не предотвращается, это ограничение модели домена может быть скомпрометировано, как вы увидите в следующих тестовых примерах.

Читать перекос

Следующий тест имитирует, как может произойти перекос чтения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
doInConnection(aliceConnection -> {
    prepareConnection(aliceConnection);
    String title = selectStringColumn(
        aliceConnection,
        selectPostTitleSql()
    );
    executeSync(() -> {
        doInConnection(bobConnection -> {
            prepareConnection(bobConnection);
            try {
                update(
                    bobConnection,
                    updatePostTitleParamSql(),
                    new Object[]{"Bob"}
                );
                update(
                    bobConnection,
                    updatePostDetailsAuthorParamSql(),
                    new Object[]{"Bob"}
                );
            } catch (Exception e) {
                LOGGER.info("Exception thrown", e);
                preventedByLocking.set(true);
            }
        });
    });
    String createdBy = selectStringColumn(
        aliceConnection,
        selectPostDetailsAuthorSql()
    );
});
  • Алиса выбирает заголовок сообщения
  • Боб пробирается и обновляет сущности Post и PostDetails
  • Поток Алисы возобновляется, и она выбирает запись PostDetails

Если перекос чтения разрешен, Алиса видит обновление Боба и может предположить, что предыдущая версия Post (которую она прочитала в начале своей транзакции) была выпущена Бобом (что может быть неточным).

Выполнение этого теста в четырех наиболее распространенных системах баз данных отношений дает следующие результаты:

Уровень изоляции базы данных Читать перекос
Oracle Read Committed да
Oracle Сериализуемый нет
Чтение SQL Server незафиксировано да
SQL Server Read Committed да
Изоляция моментального снимка в режиме чтения SQL Server да
Повторяемое чтение SQL Server нет
SQL Server Сериализуемый нет
Изоляция моментального снимка SQL Server нет
PostgreSQL Чтение Uncommitted да
PostgreSQL Read Committed да
Повторяемое чтение PostgreSQL нет
PostgreSQL Сериализуемый нет
MySQL Read Uncommitted да
MySQL Read Committed да
MySQL Repeatable Read нет
MySQL Сериализуемый нет

Написать перекос

Чтобы эмулировать перекос записи, необходимо выполнить следующий тестовый пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
doInConnection(aliceConnection -> {
    prepareConnection(aliceConnection);
    String title = selectStringColumn(
        aliceConnection,
        selectPostTitleSql()
    );
    String createdBy = selectStringColumn(
        aliceConnection,
        selectPostDetailsAuthorSql()
    );
    executeSync(() -> {
        doInConnection(bobConnection -> {
            prepareConnection(bobConnection);
            try {
                String bobTitle = selectStringColumn(
                    bobConnection,
                    selectPostTitleSql()
                );
                String bonCreatedBy = selectStringColumn(
                    bobConnection,
                    selectPostDetailsAuthorSql()
                );
                update(
                    bobConnection,
                    updatePostTitleParamSql(),
                    new Object[]{"Bob"}
                );
            } catch (Exception e) {
                LOGGER.info("Exception thrown", e);
                preventedByLocking.set(true);
            }
        });
    });
    update(
        aliceConnection,
        updatePostDetailsAuthorParamSql(),
        new Object[]{"Alice"}
    );
});
  • Алиса выбирает заголовок и автора сообщения из записи PostDetails.
  • Боб также выбирает заголовок сообщения и связанного с ним автора, но он решает обновить только заголовок
  • Алиса думает об обновлении записи PostDetails без изменения заголовка сообщения

Если допускается асимметрия записи, непересекающиеся записи Алисы и Боба будут выполняться без блокирования ограничением, управляющим обеими записями.

Выполнение этого теста в четырех наиболее распространенных системах баз данных отношений дает следующие результаты:

Уровень изоляции базы данных Написать перекос
Oracle Read Committed да
Oracle Сериализуемый да
Чтение SQL Server незафиксировано да
SQL Server Read Committed да
Изоляция моментального снимка в режиме чтения SQL Server да
Повторяемое чтение SQL Server нет
SQL Server Сериализуемый нет
Изоляция моментального снимка SQL Server да
PostgreSQL Чтение Uncommitted да
PostgreSQL Read Committed да
Повторяемое чтение PostgreSQL да
PostgreSQL Сериализуемый нет
MySQL Read Uncommitted да
MySQL Read Committed да
MySQL Repeatable Read да
MySQL Сериализуемый нет

  • Перекос записи преобладает среди механизмов управления несколькими версиями параллелизма, и Oracle не может предотвратить его, даже если заявляет, что использует Serializable, который на самом деле является просто уровнем изоляции моментальных снимков.
  • Уровни изоляции на основе блокировок SQL Server по умолчанию могут предотвратить перекос записи при использовании Repeatable Read и Serializable. Ни один из уровней изоляции моментальных снимков (на основе MVCC) не может предотвратить / обнаружить его.
  • PostgreSQL предотвращает его использование более продвинутого уровня Serializable Snapshot Isolation
  • MySQL использует разделяемые блокировки при использовании Serializable, поэтому перекос записи можно предотвратить, даже если InnoDB также основан на MVCC

Если вам интересна эта тема, то вам также может понравиться моя книга о высокопроизводительном Java Persistence , которую я пишу.