Вступление
В моей статье о ACID и транзакциях базы данных я представил три явления, описанных стандартом SQL:
- грязное чтение
- неповторяемое чтение
- фантомное чтение
Несмотря на то, что они хороши для различения четырех уровней изоляции (Чтение незафиксированного, Чтение зафиксированного, Повторяемое чтение и Сериализуемый), в действительности есть и другие явления, которые необходимо учитывать. В статье 1995 года (Критика уровней изоляции SQL ANSI) представлены другие явления, которые не включены в стандартную спецификацию.
В своей книге « Постоянство в Java» я решил настаивать на главе «Транзакции», поскольку она очень важна как для эффективности, так и для эффективности доступа к данным.
Доменная модель
Для следующих примеров я собираюсь использовать следующие две сущности:
В нашем вымышленном приложении при изменении заголовка сообщения автор должен быть записан в соответствующей записи 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 , которую я пишу.
Ссылка: | Руководство для начинающих по чтению и написанию искаженных явлений от нашего партнера по JCG Влада Михалча в блоге Влада Михалча . |