Содржина
Ова е за апликации што користат Django со PSQL или други слични бази на податоци (DB) базирани на SQL (SQLite, MySQL, итн.); основните правила околу трансакциите може да не важат за други ДБ. Под капакот, кодот за управување со трансакциите на Џанго го прави следново (ова е копирано од документот на Џанго -скролувајте надолу- со мои сопствени точки за појаснување):
Добива или создава врска со DB и ја поставува како трансакција.
Сите операции на DB бараат брави - некои брави се лабави и ќе им овозможат на другите врски да извршуваат операции на истиот ресурс, но другите брави се построги; тие се ексклузивни и ќе ги блокираат операциите за читање и запишување. Создавањето, ажурирањето и бришењето записи, како и менувањето на табелите и колоните, ќе се здобијат со построги заклучувања што ќе ги блокираат другите врски од извршување операции. Овие брави се чуваат до КРАЈ на трансакцијата.
Ова се прави автоматски кога, на пример, се вметнуваат записи во табела - некои операции на моделот автоматски ќе се завиткаат во атомски блокови.
Ова може да се направи рачно со повикување на функција која е обвиткана со atomic() или со користење atomic() во изјава со (како контекстуален менаџер).
Зачуваните точки ќе ја користат истата врска како и надворешната трансакција - ако го погледнете кодот на Django за зачуваните точки, тоа е само рекурзивен повик за да се создаде друга трансакција.
Зачуваните точки може да се вгнездуваат во зачуваните точки - магацинот ги следи зачуваните точки.
По вметнувањето, зачуваната точка се отстранува или со извршување на промените и ставање достапни за остатокот од трансакцијата (но НЕ за другите врски) или со враќање на промените.
Заклучувањата на DB сè уште се чуваат додека се извршува логиката што не е DB, на пр., ако испратиме барање HTTP во телото на трансакцијата, бравите ќе се задржат додека тоа барање не биде испратено и одговорот не се врати. Django не ги ослободува бравите кога ќе престанеме да ги извршуваме операциите на DB. Ги држи бравите ДОДЕКА не заврши надворешната трансакција.
Бравите стекнати во вгнездените трансакции исто така се чуваат додека не се заврши надворешната трансакција или додека вгнездената трансакција не се врати назад.
Трансакцијата е стопирана, вратена назад и бравите се ослободуваат.
Ако грешката се појави во вгнездена трансакција, надворешната трансакција не е засегната. Враќањето се случува, но надворешната трансакција може да продолжи непрекинато (додека покачената грешка е фатена и обработена).
Трансакциите не откриваат грешки, но зависат од нив за да знаат кога да ги вратат промените. Затоа, не ги завиткувајте операциите на DB во обид/освен, наместо тоа, завиткајте ги во трансакција и потоа завиткајте ја трансакцијата во обид/освен. Со тоа се овозможува операцијата на DB да не успее и автоматски да се врати назад без да влијае на надворешната трансакција.
Конечно, целата трансакција е обврзана, со што промените се достапни за сите корисници/врски на DB. Или се враќа назад, оставајќи го DB во иста состојба како што беше кога се сретнуваше трансакцијата.
Во следниот блог, ќе пишувам за тоа што треба или што не треба да ставите во трансакцијата за да избегнете прекини предизвикани од гладување за заклучување.
Трансакциите се механизми кои дозволуваат ACID DB (PSQL, SQL, најновиот MongoDB , итн.) да бидат ATOMIC (А во ACID). Ова значи дека сите записи во DB се прават како една операција - без разлика колку е сложено. Ако запишувањето успее, сите промени во DB опстојуваат и се достапни за сите врски истовремено (без делумно запишување). Ако запишувањето не успее, сите промени се враќаат назад - повторно, нема делумни промени.
Трансакциите ги гарантираат овие правила:
Овие правила му дозволуваат на корисникот на DB да ги здружува сложените операции и да ги изврши како една операција. На пример, извршување на трансфер од една банкарска сметка на друга; ако немавме трансакции, тогаш во средината на овие две операции за запишување, може да дојде до повлекување или дури и операција на затворање на сметката што го прави трансферот неважечки. Трансакциите му овозможуваат на корисникот некаква форма на контрола на сообраќајот. Можеме да ги блокираме сите други конфликтни операции додека трансакцијата е во тек.
Операциите се блокирани преку низа брави на табели и редови. Во PSQL и други SQL варијанти, трансакциите се креираат со BEGIN; тогаш бравите се добиваат кога се извршува операција како изберете/вметнете/бришете/измени и трансакциите завршуваат со COMMIT или ROLLBACK. Бравите се ослободуваат кога се извршува COMMIT или ROLLBACK. За среќа, Џанго ни дозволува да креираме трансакции без да мора да ги користиме овие три изјави (но сепак треба да се грижиме за бравите; повеќе за тоа во следниот пост).
-- Start a new transaction BEGIN; SELECT … INSERT INTO … UPDATE … DELETE FROM … -- Save the changes COMMIT;
from django.db import transaction from app.core.models import Accounts, Fee, NotificationJob def do_migration(): overdrawn_accounts = Accounts.objects.filter(type='overdrawn') for acount in overdrawn_accounts: create_notification(acount) @transaction.atomic def create_notification(acount: Accounts): # $5 fee for overdraft - 500 because we never store money as float!!! recall = Fee.objects.create(acount=acount, description='Fee for overdraft', amount=500) NotificationJob.objects.create(recall=recall, notification_type='all') acount.status = 'awaiting_payment' acount.save() def do_migration2(): overdawn_account = Accounts.objects.filter(type='overdrawn') for account in overdawn_account: with transaction.atomic(): recall = Fee.objects.create(acount=account, description='Fee for overdraft', amount=500) NotificationJob.objects.create(recall=recall, notification_type='all') account.status = 'awaiting_payment' account.save()
Django автоматски ја завиткува секоја операција на DB во трансакција за нас кога работи во режим на автоматско извршување (повеќе за тоа подолу). Експлицитните трансакции може да се креираат со помош на декораторот atomic() или управувачот со контекст (со atomic()) - кога се користи atomic(), поединечните операции на DB НЕ се завиткани во трансакција или се обврзуваат веднаш на DB (тие се извршуваат, но промените во DB не се видливи за другите корисници). Наместо тоа, целата функција е завиткана во блок трансакција и е посветена на крајот (ако не се покренат грешки) - COMMIT се извршува.
Ако има грешки, промените на DB се враќаат назад - ROLLBACK се извршува. Ова е причината зошто трансакциите се задржуваат на бравите до самиот крај; не сакаме да отпуштиме брава на маса, да имаме друга врска да ја смениме табелата или да ја прочитаме, а потоа да бидеме принудени да го вратиме она што штотуку е сменето или прочитано.
ANGRY_CUSTOMER_THRESHOLD = 3 @transaction.atomic def create_notification(acount: Accounts): recall = Fee.objects.create(acount=acount, description='Fee for overdraft', amount=500) NotificationJob.objects.create(recall=recall, notification_type='all') try: # when this completes successfully, the changes will be available to the outer transaction with transaction.atomic(): owner = acount.owner fees = Fee.objects.filter(owner=owner).count() if fees >= ANGRY_CUSTOMER_THRESHOLD: for fee in fees: fee.amount = 0 fee.save() owner.status = 'angry' owner.save() # as long as we catch the error, the outer transaction will not be rolled back except Exception as e: logger.error(f'Error while removings fees for account {acount.id}: {e}') acount.status = 'awaiting_payment' acount.save()
Можеме да вгнездуваме трансакции со повикување друга функција која е обвиткана со atomic() или користејќи го контекстуалниот менаџер во трансакцијата. Ова ни овозможува да креираме точки за зачувување каде што можеме да се обидеме со ризични операции без да влијаеме на остатокот од трансакцијата - кога ќе се открие внатрешна трансакција, се создава точка за зачувување, се извршува внатрешната трансакција и ако внатрешната трансакција не успее, податоците се враќаат назад во точката за зачувување и надворешната трансакција продолжува. Ако надворешната трансакција не успее, сите внатрешни трансакции се враќаат назад заедно со надворешната трансакција. Вгнездувањето на трансакциите може да се спречи со поставување durable=True во atomic() - ова ќе предизвика трансакцијата да предизвика Runtime Error доколку се откријат какви било внатрешни трансакции.
Што треба да запомните за вгнездените трансакции:
DATABASES = { 'default': { # True by default 'AUTOCOMMIT': False, # ...rest of configs } }
Стандардно, Django работи во режим на автоматско извршување , што значи дека секоја операција на DB е обвиткана во трансакција и веднаш се извршува (обврзана, оттука и автоматско извршување), освен ако операцијата е експлицитно завиткана во трансакција од страна на корисникот. Ова ја замаглува логиката на трансакцијата на основната DB - некои DB, како PSQL, автоматски ги обвиткуваат сите операции во трансакции додека други не. Ако го имаме Џанго да го прави ова за нас, не треба да се грижиме за основната логика на ДБ.
Можеме да го исклучиме режимот за автоматско извршување, но тоа ќе не остави да се потпреме на DB за управување со операциите и да се осигураме дека тие се ATOMIC, што е ризично и незгодно за програмерите. Не би било забавно ако, додека креиравме API, требаше да откриеме кои операции за пишување се во трансакциите и, според тоа, ќе бидат напишани целосно, а не делумно.
Сепак, можеби ќе сакаме да го исклучиме режимот за автоматско извршување ако го знаеме бројот на врски на DB и редоследот на операции што тие ги извршуваат. Ако сфатиме како да ги деконфликтираме овие операции, тогаш можеме да го исклучиме режимот за автоматско извршување и да добиеме некои ефикасност:
Негативните страни се повисоките ризици од корупција на податоците. Не вреди, но можеби има случај на употреба што не ми текнува.