Почему MVCC требует блокировки для операторов DML

В PostgreSQL механизм управления параллелизмом MVCC говорит, что:

Блокировки MVCC, полученные для запроса (чтения) данных, не конфликтуют с блокировками, полученными для записи данных, поэтому чтение никогда не блокирует запись, а запись никогда не блокирует чтение

Таким образом, даже для READ_COMMITTED инструкция UPDATE заблокирует текущие затронутые строки, поэтому никакая другая транзакция не сможет их изменить, пока текущая транзакция не завершится или не откатится.

Если параллельная транзакция выдает UPDATE для заблокированных строк, вторая транзакция будет блокироваться до тех пор, пока первая не освободит свои блокировки.

  1. Это поведение пытается предотвратить конфликты записи-записи?

  2. Потерянные обновления все еще могут происходить в READ_COMMITTED, так как после фиксации первой транзакции вторая перезапишет строку (даже если база данных изменилась между началом запроса UPDATE и концом запроса). Так что, если потерянные обновления все еще возможны, почему вторая транзакция должна ждать? Разве нельзя использовать моментальные снимки на уровне строк для хранения незафиксированных изменений транзакций, чтобы избежать необходимости в транзакциях, ожидающих снятия блокировок записи?

2 ответа

Решение

Ответ на первый вопрос - да. Ни одна СУБД не может поддерживать грязные записи; если две транзакции T1 и T2 выполняются одновременно, и T2 перезаписывает обновление от T1, то система не может обработать случай, когда T1 впоследствии выдает ROLLBACK, так как обновление T2 уже произошло.

Чтобы избежать "грязных" записей, исходное определение изоляции моментальных снимков было "выигрывает первый коммиттер", то есть конфликтующие записи будут разрешены, но только первая транзакция, которая выдаст COMMIT, сможет - все другие конфликтующие транзакции должны будут ROLLBACK. Но эта модель программирования несколько проблематична, если не расточительна, поскольку транзакция может обновить значительную часть базы данных, только если в конце ей будет отказано в возможности COMMIT. Таким образом, вместо "выигрышей первого коммиттера" большинство систем СУБД, поддерживающих MVCC, реализуют "выигрыши первого обновления", используя довольно традиционную двухфазную блокировку.

Итак, если потеря обновлений все еще возможна, почему вторая транзакция должна ждать? Нельзя ли использовать снимки уровня строк для хранения незафиксированных изменений транзакций, чтобы избежать необходимости ожидания транзакциями снятия блокировки записи?

Да, потеря обновлений по-прежнему возможна, но для воспроизведения ситуации потери обновлений (с точки зрения неповторяемого чтения) нам необходимо взаимодействие между читателями и записывающими устройствами из параллельных транзакций. Но что, если у нас есть две одновременные транзакции, и обе они выполняют такие запросы, как:

      UPDATE register SET total = total + 100 WHERE id = 1;

Итак, у нас есть атомарные операции, в которых у нас нет места для взаимодействия между читателем и записывающим устройством, и мы всегда ожидаем, что последняя транзакция попытается применить свой оператор к актуальным данным (и если данные изменились, но не зафиксированы, нам следует подождать ) даже в MVCC и независимо от уровня изоляции. И в результате мы получаем +200 с обеих транзакций на уровне READ COMMITTED.

Но без механизма блокировки мы получим +100 в результате обеих транзакций, и это будет похоже на потерянное обновление конфликта записи-записи, о котором вы упомянули. Поэтому писатели всегда блокируют авторов, чтобы избежать подобных проблем.

Другие вопросы по тегам