paint-brush
Python: Подлабок поглед на тоа како функционираат трансакциите на Џангоод страна на@mta
134 читања Нова историја

Python: Подлабок поглед на тоа како функционираат трансакциите на Џанго

од страна на Michael T. Andemeskel9m2025/03/02
Read on Terminal Reader

Премногу долго; Да чита

Трансакцијата.atomic() на Django може да се користи за креирање трансакции за да се постигнат атомски операции - DB операции кои се загарантирани за сите да се извршат и да се зачуваат заедно или да не успеат. Django создава трансакции користејќи го основниот трансакциски механизам на DB, го извршува кодот во трансакцијата и ако не се појават грешки, тој ја обврзува трансакцијата на DB.
featured image - Python: Подлабок поглед на тоа како функционираат трансакциите на Џанго
Michael T. Andemeskel HackerNoon profile picture
0-item


Содржина

  • TLDR - Резиме на логиката на трансакциите на Џанго
  • Логички дијаграм
  • Трансакции и ATOMIC операции
  • Трансакции во Џанго
  • Django auto-commit mode — Исклучување на автоматски трансакции
  • Извори

TLDR - Резиме на логиката на трансакциите на Џанго

Ова е за апликации што користат Django со PSQL или други слични бази на податоци (DB) базирани на SQL (SQLite, MySQL, итн.); основните правила околу трансакциите може да не важат за други ДБ. Под капакот, кодот за управување со трансакциите на Џанго го прави следново (ова е копирано од документот на Џанго -скролувајте надолу- со мои сопствени точки за појаснување):


  • Отвора трансакција кога влегува во најоддалечениот атомски блок;
    • Добива или создава врска со DB и ја поставува како трансакција.


  • Бара и добива брави кога ќе се сретне операција на DB;
    • Сите операции на DB бараат брави - некои брави се лабави и ќе им овозможат на другите врски да извршуваат операции на истиот ресурс, но другите брави се построги; тие се ексклузивни и ќе ги блокираат операциите за читање и запишување. Создавањето, ажурирањето и бришењето записи, како и менувањето на табелите и колоните, ќе се здобијат со построги заклучувања што ќе ги блокираат другите врски од извршување операции. Овие брави се чуваат до КРАЈ на трансакцијата.


  • Создава зачувана точка при внесување внатрешен атомски блок;
    • Ова се прави автоматски кога, на пример, се вметнуваат записи во табела - некои операции на моделот автоматски ќе се завиткаат во атомски блокови.

    • Ова може да се направи рачно со повикување на функција која е обвиткана со atomic() или со користење atomic() во изјава со (како контекстуален менаџер).

    • Зачуваните точки ќе ја користат истата врска како и надворешната трансакција - ако го погледнете кодот на Django за зачуваните точки, тоа е само рекурзивен повик за да се создаде друга трансакција.

    • Зачуваните точки може да се вгнездуваат во зачуваните точки - магацинот ги следи зачуваните точки.


  • Се ослободува или се враќа на зачуваната точка при излегување од внатрешен блок;
    • По вметнувањето, зачуваната точка се отстранува или со извршување на промените и ставање достапни за остатокот од трансакцијата (но НЕ за другите врски) или со враќање на промените.


  • Кога ќе се сретне код што не ја допира DB, кодот се извршува како и обично;
    • Заклучувањата на DB сè уште се чуваат додека се извршува логиката што не е DB, на пр., ако испратиме барање HTTP во телото на трансакцијата, бравите ќе се задржат додека тоа барање не биде испратено и одговорот не се врати. Django не ги ослободува бравите кога ќе престанеме да ги извршуваме операциите на DB. Ги држи бравите ДОДЕКА не заврши надворешната трансакција.

    • Бравите стекнати во вгнездените трансакции исто така се чуваат додека не се заврши надворешната трансакција или додека вгнездената трансакција не се врати назад.


  • Кога ќе се појави Runtime Error или ќе се појави грешка надвор од DB (не е поврзана со операциите на DB);
    • Трансакцијата е стопирана, вратена назад и бравите се ослободуваат.

    • Ако грешката се појави во вгнездена трансакција, надворешната трансакција не е засегната. Враќањето се случува, но надворешната трансакција може да продолжи непрекинато (додека покачената грешка е фатена и обработена).

    • Трансакциите не откриваат грешки, но зависат од нив за да знаат кога да ги вратат промените. Затоа, не ги завиткувајте операциите на DB во обид/освен, наместо тоа, завиткајте ги во трансакција и потоа завиткајте ја трансакцијата во обид/освен. Со тоа се овозможува операцијата на DB да не успее и автоматски да се врати назад без да влијае на надворешната трансакција.


  • Ја обврзува или ја враќа трансакцијата кога излегува од најоддалечениот блок.
    • Конечно, целата трансакција е обврзана, со што промените се достапни за сите корисници/врски на DB. Или се враќа назад, оставајќи го DB во иста состојба како што беше кога се сретнуваше трансакцијата.


Во следниот блог, ќе пишувам за тоа што треба или што не треба да ставите во трансакцијата за да избегнете прекини предизвикани од гладување за заклучување.

Логички дијаграм

Логички дијаграм на трансакциите на Џанго — Целосно

Трансакции и ATOMIC операции

Трансакциите се механизми кои дозволуваат ACID DB (PSQL, SQL, најновиот MongoDB , итн.) да бидат ATOMIC (А во ACID). Ова значи дека сите записи во DB се прават како една операција - без разлика колку е сложено. Ако запишувањето успее, сите промени во DB опстојуваат и се достапни за сите врски истовремено (без делумно запишување). Ако запишувањето не успее, сите промени се враќаат назад - повторно, нема делумни промени.


Трансакциите ги гарантираат овие правила:

  • Податоците во 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 доколку се откријат какви било внатрешни трансакции.


Што треба да запомните за вгнездените трансакции:

  • Вгнездените трансакции НЕ ги ослободуваат бравите освен ако вгнездените трансакции не се вратат назад или надворешната трансакција не е извршена или вратена назад.
  • Вгнездените трансакции НЕ ги фаќаат грешките во DB; тие едноставно ги враќаат назад операциите во трансакцијата што ја предизвикала грешката. Сè уште треба да ја фатиме грешката во DB за надворешната трансакција да продолжи.
  • Промените направени во вгнездените трансакции се достапни за надворешната трансакција и последователните вгнездени трансакции.

Django Autocommit Mode — Исклучување на автоматски трансакции

 DATABASES = { 'default': { # True by default 'AUTOCOMMIT': False, # ...rest of configs } }

Стандардно, Django работи во режим на автоматско извршување , што значи дека секоја операција на DB е обвиткана во трансакција и веднаш се извршува (обврзана, оттука и автоматско извршување), освен ако операцијата е експлицитно завиткана во трансакција од страна на корисникот. Ова ја замаглува логиката на трансакцијата на основната DB - некои DB, како PSQL, автоматски ги обвиткуваат сите операции во трансакции додека други не. Ако го имаме Џанго да го прави ова за нас, не треба да се грижиме за основната логика на ДБ.


Можеме да го исклучиме режимот за автоматско извршување, но тоа ќе не остави да се потпреме на DB за управување со операциите и да се осигураме дека тие се ATOMIC, што е ризично и незгодно за програмерите. Не би било забавно ако, додека креиравме API, требаше да откриеме кои операции за пишување се во трансакциите и, според тоа, ќе бидат напишани целосно, а не делумно.


Сепак, можеби ќе сакаме да го исклучиме режимот за автоматско извршување ако го знаеме бројот на врски на DB и редоследот на операции што тие ги извршуваат. Ако сфатиме како да ги деконфликтираме овие операции, тогаш можеме да го исклучиме режимот за автоматско извршување и да добиеме некои ефикасност:

  • Отстрануваме две команди од секоја операција: BEGIN и COMMIT/ROLLBACK. Но, повеќето DB автоматски ја обвиткуваат секоја операција во трансакција, така што ова може да биде бесмислено.
  • Бравите нема да се држат премногу долго - штом операцијата заврши, бравите што ги држи се ослободуваат, во споредба со чекањето да заврши целата трансакција.
  • Помалку веројатни се ќор-сокаките - ќор-сокакот се јавува кога на две трансакции им треба заклучување што го држи другата трансакција. Ова ги остава двете трансакции да чекаат другата да заврши, но ниту една не може да заврши. Сепак, сè уште може да се појават ќор-сокак. Некои операции на DB ќе прават повеќе запишувања, на пр., ажурирањето на колона што има индекс на неа ќе ги ажурира колоната и индексот (барем две записи). Ова може да доведе до ќор-сокак ако друга врска се обиде да работи на погодените редови или колони.


Негативните страни се повисоките ризици од корупција на податоците. Не вреди, но можеби има случај на употреба што не ми текнува.

Извори


L O A D I N G
. . . comments & more!

About Author

Michael T. Andemeskel HackerNoon profile picture
Michael T. Andemeskel@mta
I write code and, occasionally, bad poetry. Thankfully, my code isn’t as bad as my poetry.

ВИСЕТЕ ТАГОВИ

ОВОЈ СТАТИЈА БЕШЕ ПРЕТСТАВЕН ВО...