Не вложенная версия @atomic() в Django?
Из документов атомарного()
атомные блоки могут быть вложенными
Этот звук похож на отличную функцию, но в моем случае я хочу обратное: я хочу, чтобы транзакция была долговечной, как только блок, украшенный с помощью @atomic()
, будет успешно удален.
Есть ли способ обеспечить долговечность обработки транзакций django?
Фон
Транзакция - это ACID. "D" означает долговечность. Поэтому я считаю, что транзакции не могут быть вложены без потери функции "D".
Пример. Если внутренняя транзакция выполнена успешно, но внешняя транзакция не выполняется, внешняя и внутренняя транзакция возвращаются. Результат: внутренняя транзакция не была долговечной.
Я использую PostgreSQL, но AFAIK это не имеет большого значения.
Ответы
Ответ 1
Вы не можете сделать это через любой API.
Транзакции не могут быть вложены, сохраняя все свойства ACID, а не все базы данных поддерживают вложенные транзакции.
Только внешний атомный блок создает транзакцию. Внутренние атомные блоки создают точку сохранения внутри транзакции и освобождают или откатывают точку сохранения при выходе из внутреннего блока. Как таковые, внутренние атомные блоки обеспечивают атомарность, но, как вы отметили, например, нет. долговечность.
Так как внешний атомный блок создает транзакцию, он должен обеспечивать атомарность, и вы не можете зафиксировать вложенные атомные блоки в базу данных, если содержащая транзакция не будет выполнена.
Единственный способ убедиться, что внутренний блок зафиксирован, заключается в том, чтобы убедиться, что код в транзакции завершает выполнение без каких-либо ошибок.
Ответ 2
Я согласен с knbk ответом, что это невозможно: долговечность присутствует только на уровне транзакции, а атомный - это. Он не обеспечивает его на уровне точек сохранения. В зависимости от варианта использования могут быть обходные пути.
Я предполагаю, что ваш пример использования:
@atomic # possibly implicit if ATOMIC_REQUESTS is enabled
def my_view():
run_some_code() # It fine if this gets rolled back.
charge_a_credit_card() # It not OK if this gets rolled back.
run_some_more_code() # This shouldn't roll back the credit card.
Я думаю, вам нужно что-то вроде:
@transaction.non_atomic_requests
def my_view():
with atomic():
run_some_code()
with atomic():
charge_a_credit_card()
with atomic():
run_some_more_code()
Если ваш вариант использования предназначен для кредитных карт (например, когда у меня была эта проблема несколько лет назад), мой коллега обнаружил, что кредитные карты действительно предоставляют механизмы для обработки этого. Подобный механизм может работать для вашего случая использования, в зависимости от структуры проблемы:
@atomic
def my_view():
run_some_code()
result = charge_a_credit_card(capture=False)
if result.successful:
transaction.on_commit(lambda: result.capture())
run_some_more_code()
Другим вариантом будет использование механизма транзакций без транзакций для записи того, что вас интересует, например, базы данных журнала или очереди повторных попыток записи.
Ответ 3
Этот тип прочности невозможно из-за ACID, с одним соединением. (то есть, когда вложенный блок остается зафиксированным во время откат внешнего блока). Это следствие ACID, а не проблема Django. Представьте себе супер-базу данных и случай, когда таблица B
имеет внешний ключ в таблице A
.
CREATE TABLE A (id serial primary key);
CREATE TABLE B (id serial primary key, b_id integer references A (id));
-- transaction
INSERT INTO A DEFAULT VALUES RETURNING id AS new_a_id
-- like it would be possible to create an inner transaction
INSERT INTO B (a_id) VALUES (new_a_id)
-- commit
-- rollback (= integrity problem)
Если внутренняя "транзакция" должна быть долговечной, тогда как (внешняя) транзакция будет откат, целостность будет нарушена. Операция отката должна быть всегда реализована так, чтобы она никогда не прерывалась, поэтому никакая база данных не выполняла бы вложенную независимую транзакцию. Это было бы против принципа причинности, и целостность не может быть гарантирована после такого выборочного отката. Это также против атомарности.
Сделка связана с подключением к базе данных. Если вы создаете два соединения, тогда создаются две независимые транзакции. В одном соединении не отображаются незафиксированные строки других транзакций (можно установить этот уровень изоляции, но он зависит от бэкэнд базы данных), и никакие внешние ключи к ним не могут быть созданы, а целостность сохраняется после отката по проекту базы данных.
Django поддерживает несколько баз данных, поэтому несколько подключений.
# no ATOMIC_REQUESTS should be set for "other_db" in DATABASES
@transaction.atomic # atomic for the database "default"
def my_view():
with atomic(): # or set atomic() here, for the database "default"
some_code()
with atomic("other_db"):
row = OtherModel.objects.using("other_db").create(**kwargs)
raise DatabaseError
Данные в "other_db" остаются зафиксированными.
Возможно, в Django можно создать трюк с двумя соединениями с той же базой данных, что и две базы данных с некоторыми базами данных, но я уверен, что он не проверен, он будет подвержен ошибкам, проблемы с миграцией, большую нагрузку на бэкэнд базы данных, которые должны создавать реальные параллельные транзакции при каждом запросе и не могут быть оптимизированы. Лучше использовать две реальные базы данных или реорганизовать код.
Настройка DATABASE_ROUTERS очень полезна, но я не уверен, если вас интересует несколько соединений.