Skip to content

Instantly share code, notes, and snippets.

@yv84
Last active January 9, 2019 09:05
Show Gist options
  • Save yv84/93035c8c2524fd7dc5cf5cab57935254 to your computer and use it in GitHub Desktop.
Save yv84/93035c8c2524fd7dc5cf5cab57935254 to your computer and use it in GitHub Desktop.
Какие могут быть проблемы при параллельной работе транзакций:
1) Потерянное обновление - когда две транзакции читают и обновляют одну и ту же ячейку (поле в строке) параллельно. Например на счет начисляют проценты параллельно несколько транзакций. Одна пытается увеличить на 0,3%, вторая на 0,2%. Так как обе прочитали начальное значение одновременно, то в результате счет будет увеличен один раз.
2) Грязное чтение - когда одна транзакция меняет данные, а другая их читает, после чего первая откатывается. Например идут две транзакции, пытающиеся снять деньги со счета. На счету 100 рублей, первая пытается снять 30, но откатывается. Вторая пытается снять еще 50, но успевает прочитать значение счета после измененияи до коммита, то есть 70 рублей. В итоге на счету будет 20 рублей.
3) Неповторяемое чтение - когда одна транзакция читает данные несколько раз, а вторая успевает их обновить.
Две транзакции работают параллельно, она начисляет процент, вторая снимает сумму со скидкой в зависимости от состояния счета. Начисление процента происходит по нетривиальной формуле. Первая транзакция читает счет и уходит на вычисления процента, вторая быстро успевает обновить счет, пока первая думает, первая транзакция начисляет процент по неактуальной сумме.
4) Фантомное чтение - когда одна транзакция читает строки по предикату, а вторая меняет их, так что повторное чтение в первой транзакции возвращает разный набор строк. Все предыдущие проблемы проявлялись на одной строке, фантомное чтение проявлятся когда строк более чем одна. Классический пример - баланс. Одна транзакция считает сумму средств на счетах, а вторая переводит с одного счета на другой (обновляет две записи по очереди). Первая транзакция может посчитать баланс между двумя обновлениями второй транзакции, получив неверный баланс.
Вы запустили запрос в момент времен и Tl, а затем выполните его повторно в момент времени Т2, то в базе данных могут появиться дополнительные строки , что повлияет на полученные результаты. Отличие фантомного чтения от невоспроизводимого чтения состоит в том , что уже прочитанные данные не изменяются , но критерию запроса удовлетворяет больший объем данных, чем было до того.
Эти 4 проблемы приводят к тому, что результат нескольких параллельных транзакций не соответствует какому-либо порядку их последовательного выполнения.
Есть теорема о сериализуемости транзакций, которая говорит, что сериализуемость можно сделать используя двухфазный протокол блокировки. Когда в первой фазе навешиваются блокировки на строки, а во второй фазе снимаются. Причем для любого набора транзакций можно добиться их сериализумости если вторая фаза (снятие блокировок) происходит после коммита.
Увы в реальности такая строгая сериализуемость убивает параллельность (и скорость работы) и повышает количество дедлоков, поэтому придумано два способа оптимизации:
1) Разные типы и гранулярности блокировок
2) Разные уровни изоляции (по сути разные правила навешивания блокировок)
Само по себе название "уровень изоляции" плохо описывает происходящее. Ибо честная изоляция с точки зрения ACID, возможна только на уровне Serializable. Тогда сама база данных гарантирует, что любое количество транзакций, выполняющихся параллельно, имеют результат как при некотором последовательном выполнении. Все остальные уровни изоляции, кроме serializable, перекладывают на разработчика ответственность за сохранение изолированности транзакций.
Сам уровни изоляции привязаны как раз к потенциальным проблемам, которые они допускают:
1) Serializable - обеспечивает честную изолированность транзакций.
2) Repeatable Read - допускает фантомные чтения, сам разработчик должен следить чтобы целостность данных не была нарушена из-за фантомных чтений.
3) Read Commited - допускает фантомные чтения и неповторяемые чтения. Соответственно сам разработчик должен следить за отсутствием проблем из-за неповторяемых чтений.
4) Read Uncommited - допускает фантомные чтения, неповторяемые чтения и даже грязные чтения.
чтобы избежать Неповторяемое чтение нужно просто блочить те строки, с которыми работает транзакция, чтобы избежать Фантомное чтение нужно блочить все таблицы, с которыми работает транзакция.
Propagation Behaviors Supported by Spring
Propagation Description
REQUIRED If there’s an existing transaction in progress, the current method should run within this
transaction. Otherwise, it should start a new transaction and run within its own transaction.
REQUIRES_NEW The current method must start a new transaction and run within its own transaction.
If there’s an existing transaction in progress, it should be suspended.
SUPPORTS If there’s an existing transaction in progress, the current method can run within this
transaction. Otherwise, it is not necessary to run within a transaction.
NOT_SUPPORTED The current method should not run within a transaction. If there’s an existing transaction in
progress, it should be suspended.
MANDATORY The current method must run within a transaction. If there’s no existing transaction in
progress, an exception will be thrown.
NEVER The current method should not run within a transaction. If there’s an existing transaction in
progress, an exception will be thrown.
NESTED If there’s an existing transaction in progress, the current method should run within the
nested transaction (supported by the JDBC 3.0 save point feature) of this transaction.
Otherwise, it should start a new transaction and run within its own transaction. This feature
is unique to Spring (whereas the previous propagation behaviors have analogs in Java EE
transaction propagation). The behavior is useful for situations such as batch processing, in
which you’ve got a long running process (imagine processing 1 million records) and you
want to chunk the commits on the batch. So you commit every 10,000 records. If something
goes wrong, you roll back the nested transaction and you’ve lost only 10,000 records’ worth of
work (as opposed to the entire 1 million).
The problems caused by concurrent transactions can be categorized into four types:
• Dirty read: For two transactions T1 and T2, T1 reads a field that has been updated by T2 but
not yet committed. Later, if T2 rolls back, the field read by T1 will be temporary and invalid.
• Nonrepeatable read: For two transactions T1 and T2, T1 reads a field and then T2 updates the
field. Later, if T1 reads the same field again, the value will be different.
• Phantom read: For two transactions T1 and T2, T1 reads some rows from a table and then
T2 inserts new rows into the table. Later, if T1 reads the same table again, there will be
additional rows.
• Lost updates: For two transactions T1 and T2, they both select a row for update, and based on
the state of that row, make an update to it. Thus, one overwrites the other when the second
transaction to commit should have waited until the first one committed before performing its
selection.
Isolation Levels Supported by Spring
Isolation Description
DEFAULT Uses the default isolation level of the underlying database. For most databases, the default
isolation level is READ_COMMITTED.
READ_UNCOMMITTED Allows a transaction to read uncommitted changes by other transactions. The dirty read,
nonrepeatable read, and phantom read problems may occur.
READ_COMMITTED Allows a transaction to read only those changes that have been committed by other
transactions. The dirty read problem can be avoided, but the nonrepeatable read and
phantom read problems may still occur.
REPEATABLE_READ Ensures that a transaction can read identical values from a field multiple times. For the
duration of this transaction, updates made by other transactions to this field are prohibited.
The dirty read and nonrepeatable read problems can be avoided, but the phantom read
problem may still occur.
SERIALIZABLE Ensures that a transaction can read identical rows from a table multiple times. For the
duration of this transaction, inserts, updates, and deletes made by other transactions to this
table are prohibited. All the concurrency problems can be avoided, but the performance
will be low.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment