Database
 sql >> Baza danych >  >> RDS >> Database

Zarządzanie transakcjami z Django 1.6

Jeśli kiedykolwiek poświęciłeś dużo czasu na zarządzanie transakcjami w bazie danych Django, wiesz, jak bardzo może to być zagmatwane. W przeszłości dokumentacja zapewniała sporo głębi, ale zrozumienie można było uzyskać tylko poprzez budowanie i eksperymentowanie.

Było mnóstwo dekoratorów do pracy, takich jak commit_on_success , commit_manually , commit_unless_managed , rollback_unless_managed , enter_transaction_management , leave_transaction_management , żeby wymienić tylko kilka. Na szczęście z Django 1.6 wszystko wychodzi poza drzwi. Teraz naprawdę musisz wiedzieć tylko o kilku funkcjach. A do nich dojdziemy za chwilę. Najpierw zajmiemy się tymi tematami:

  • Co to jest zarządzanie transakcjami?
  • Co jest nie tak z zarządzaniem transakcjami przed Django 1.6?

Przed przejściem do:

  • Co jest dobrego w zarządzaniu transakcjami w Django 1.6?

A potem zajmijmy się szczegółowym przykładem:

  • Przykład paska
  • Transakcje
  • Zalecany sposób
  • Korzystanie z dekoratora
  • Transakcja na żądanie HTTP
  • Zapisz punkty
  • Zagnieżdżone transakcje

Co to jest transakcja?

Zgodnie z SQL-92 „Transakcja SQL (czasami nazywana po prostu „transakcją”) to sekwencja wykonań instrukcji SQL, która jest niepodzielna w odniesieniu do odzyskiwania”. Innymi słowy, wszystkie instrukcje SQL są wykonywane i zatwierdzane razem. Podobnie po wycofaniu wszystkie stwierdzenia są wycofywane razem.

Na przykład:

# START
note = Note(title="my first note", text="Yay!")
note = Note(title="my second note", text="Whee!")
address1.save()
address2.save()
# COMMIT

Tak więc transakcja to pojedyncza jednostka pracy w bazie danych. A ta pojedyncza jednostka pracy jest odgraniczona przez transakcję początkową, a następnie zatwierdzenie lub jawne wycofanie.



Co jest nie tak z zarządzaniem transakcjami przed Django 1.6?

Aby w pełni odpowiedzieć na to pytanie, musimy zająć się sposobem obsługi transakcji w bazie danych, bibliotekach klienta i w Django.


Bazy danych

Każdy wyciąg w bazie danych musi zostać uruchomiony w transakcji, nawet jeśli transakcja zawiera tylko jeden wyciąg.

Większość baz danych ma funkcję AUTOCOMMIT ustawienie, które jest zwykle ustawione na True jako domyślne. To AUTOCOMMIT otacza każdą instrukcję w transakcji, która jest natychmiast zatwierdzana, jeśli instrukcja się powiedzie. Oczywiście możesz ręcznie wywołać coś takiego jak START_TRANSACTION co tymczasowo zawiesi AUTOCOMMIT dopóki nie zadzwonisz do COMMIT_TRANSACTION lub ROLLBACK .

Jednakże wniosek jest taki, że AUTOCOMMIT ustawienie stosuje niejawne zatwierdzenie po każdej instrukcji .



Biblioteki klienta

Są też biblioteki klienckie Pythona jak sqlite3 i mysqldb, które umożliwiają programom Pythona łączenie się z samymi bazami danych. Takie biblioteki przestrzegają zestawu standardów dotyczących dostępu do baz danych i wykonywania zapytań. Ten standard, DB API 2.0, jest opisany w PEP 249. Chociaż może to sprawić, że czytanie może być nieco suche, ważnym wnioskiem jest to, że PEP 249 stwierdza, że ​​baza danych AUTOCOMMIT powinna być WYŁĄCZONA domyślnie.

To wyraźnie koliduje z tym, co dzieje się w bazie danych:

  • Wyrażenia SQL zawsze muszą być uruchamiane w transakcji, którą baza danych zazwyczaj otwiera dla Ciebie za pomocą funkcji AUTOCOMMIT .
  • Jednak według PEP 249 nie powinno to mieć miejsca.
  • Biblioteki klienta muszą odzwierciedlać to, co dzieje się w bazie danych, ale ponieważ nie mogą włączać funkcji AUTOCOMMIT domyślnie, po prostu zawijają twoje instrukcje SQL w transakcję, tak jak baza danych.

Dobra. Zostań ze mną trochę dłużej.



Django

Wejdź do Django. Django ma też coś do powiedzenia na temat zarządzania transakcjami. W Django 1.5 i wcześniejszych, Django zasadniczo działał z otwartą transakcją i automatycznie zatwierdzał tę transakcję, gdy zapisywałeś dane do bazy danych. Więc za każdym razem, gdy wywołałeś coś takiego jak model.save() lub model.update() , Django wygenerowało odpowiednie instrukcje SQL i zatwierdziło transakcję.

Również w Django 1.5 i wcześniejszych zalecano użycie TransactionMiddleware powiązania transakcji z żądaniami HTTP. Każde żądanie otrzymało transakcję. Jeśli odpowiedź zostanie zwrócona bez wyjątków, Django zatwierdzi transakcję, ale jeśli twoja funkcja widoku zwróci błąd, ROLLBACK zostanie nazwany. W efekcie wyłączono funkcję AUTOCOMMIT . Jeśli chciałeś standardowego zarządzania transakcjami w stylu automatycznego zatwierdzania na poziomie bazy danych, musiałeś samodzielnie zarządzać transakcjami - zwykle za pomocą dekoratora transakcji w funkcji widoku, takiego jak @transaction.commit_manually lub @transaction.commit_on_success .

Wziąć oddech. Albo dwa.



Co to oznacza?

Tak, dużo się tam dzieje i okazuje się, że większość programistów chce tylko standardowych automatycznych zatwierdzeń na poziomie bazy danych – co oznacza, że ​​transakcje pozostają w ukryciu, robiąc swoje, dopóki nie będziesz musiał ich ręcznie dostosować.




Co jest dobrego w zarządzaniu transakcjami w Django 1.6?

Witamy w Django 1.6. Postaraj się zapomnieć o wszystkim, o czym właśnie rozmawialiśmy, i po prostu pamiętaj, że w Django 1.6 używasz bazy danych AUTOCOMMIT i w razie potrzeby zarządzaj transakcjami ręcznie. Zasadniczo mamy znacznie prostszy model, który zasadniczo robi to, do czego baza danych została zaprojektowana w pierwszej kolejności.

Dość teorii. Zróbmy kod.



Przykład paska

Tutaj mamy tę przykładową funkcję widoku, która obsługuje rejestrację użytkownika i wywołanie Stripe w celu przetworzenia karty kredytowej.

def register(request):
    user = None
    if request.method == 'POST':
        form = UserForm(request.POST)
        if form.is_valid():

            customer = Customer.create("subscription",
              email = form.cleaned_data['email'],
              description = form.cleaned_data['name'],
              card = form.cleaned_data['stripe_token'],
              plan="gold",
            )

            cd = form.cleaned_data
            try:
                user = User.create(cd['name'], cd['email'], cd['password'],
                   cd['last_4_digits'])

                if customer:
                    user.stripe_id = customer.id
                    user.save()
                else:
                    UnpaidUsers(email=cd['email']).save()

            except IntegrityError:
                form.addError(cd['email'] + ' is already a member')
            else:
                request.session['user'] = user.pk
                return HttpResponseRedirect('/')

    else:
      form = UserForm()

    return render_to_response(
        'register.html',
        {
          'form': form,
          'months': range(1, 12),
          'publishable': settings.STRIPE_PUBLISHABLE,
          'soon': soon(),
          'user': user,
          'years': range(2011, 2036),
        },
        context_instance=RequestContext(request)
    )

Ten widok najpierw wywołuje Customer.create który faktycznie wywołuje Stripe do obsługi przetwarzania kart kredytowych. Następnie tworzymy nowego użytkownika. Jeśli otrzymaliśmy odpowiedź od Stripe, aktualizujemy nowo utworzonego klienta za pomocą stripe_id . Jeśli nie odzyskamy klienta (Stripe nie działa), dodamy wpis do UnpaidUsers tabela z nowo utworzonym adresem e-mail klienta, dzięki czemu możemy poprosić ich o ponowne ponowną próbę danych karty kredytowej później.

Chodzi o to, że nawet jeśli Stripe nie działa, użytkownik nadal może się zarejestrować i zacząć korzystać z naszej strony. Po prostu poprosimy ich ponownie w późniejszym terminie o dane karty kredytowej.

Rozumiem, że może to być trochę wymyślony przykład i nie jest to sposób, w jaki zaimplementowałbym taką funkcjonalność, gdybym musiał, ale celem jest zademonstrowanie transakcji.

Naprzód. Myśląc o transakcjach i pamiętając, że domyślnie Django 1.6 daje nam AUTOCOMMIT zachowanie dla naszej bazy danych, spójrzmy trochę dłużej na kod związany z bazą danych.

cd = form.cleaned_data
try:
    user = User.create(
        cd['name'], cd['email'], 
        cd['password'], cd['last_4_digits'])

    if customer:
        user.stripe_id = customer.id
        user.save()
    else:
        UnpaidUsers(email=cd['email']).save()

except IntegrityError:
    # ...

Czy widzisz jakieś problemy? Co się stanie, jeśli UnpaidUsers(email=cd['email']).save() linia nie działa?

Będziesz mieć użytkownika zarejestrowanego w systemie, który według systemu zweryfikował swoją kartę kredytową, ale w rzeczywistości nie zweryfikował karty.

Chcemy tylko jednego z dwóch wyników:

  1. Użytkownik jest tworzony (w bazie danych) i ma stripe_id .
  2. Użytkownik został utworzony (w bazie danych) i nie ma stripe_id ORAZ powiązany wiersz w UnpaidUsers generowana jest tabela z tym samym adresem e-mail.

Co oznacza, że ​​chcemy, aby dwie oddzielne instrukcje bazy danych zarówno zatwierdzały, jak i wycofywały. Idealny przypadek na skromną transakcję.

Najpierw napiszmy kilka testów, aby sprawdzić, czy rzeczy zachowują się tak, jak chcemy.

@mock.patch('payments.models.UnpaidUsers.save', side_effect = IntegrityError)
def test_registering_user_when_strip_is_down_all_or_nothing(self, save_mock):

    #create the request used to test the view
    self.request.session = {}
    self.request.method='POST'
    self.request.POST = {'email' : '[email protected]',
                         'name' : 'pyRock',
                         'stripe_token' : '...',
                         'last_4_digits' : '4242',
                         'password' : 'bad_password',
                         'ver_password' : 'bad_password',
                        }

    #mock out stripe  and ask it to throw a connection error
    with mock.patch('stripe.Customer.create', side_effect =
                    socket.error("can't connect to stripe")) as stripe_mock:

        #run the test
        resp = register(self.request)

        #assert there is no record in the database without stripe id.
        users = User.objects.filter(email="[email protected]")
        self.assertEquals(len(users), 0)

        #check the associated table also didn't get updated
        unpaid = UnpaidUsers.objects.filter(email="[email protected]")
        self.assertEquals(len(unpaid), 0)

Dekorator na górze testu to próba, która wyrzuci „IntegrityError”, gdy spróbujemy zapisać do UnpaidUsers tabela.

Ma to na celu odpowiedź na pytanie:„Co się stanie, jeśli UnpaidUsers(email=cd['email']).save() linia nie działa?” Następny fragment kodu po prostu tworzy symulowaną sesję z odpowiednimi informacjami, których potrzebujemy do naszej funkcji rejestracji. A potem with mock.patch zmusza system do przekonania, że ​​Stripe nie działa… w końcu przechodzimy do testu.

resp = register(self.request)

Powyższa linia po prostu wywołuje naszą funkcję widoku rejestru przekazującą w zaklętym żądaniu. Następnie po prostu sprawdzamy, czy tabele nie są aktualizowane:

#assert there is no record in the database without stripe_id.
users = User.objects.filter(email="[email protected]")
self.assertEquals(len(users), 0)

#check the associated table also didn't get updated
unpaid = UnpaidUsers.objects.filter(email="[email protected]")
self.assertEquals(len(unpaid), 0)

Więc powinno się nie udać, jeśli uruchomimy test:

======================================================================
FAIL: test_registering_user_when_strip_is_down_all_or_nothing (tests.payments.testViews.RegisterPageTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/j1z0/.virtualenvs/django_1.6/lib/python2.7/site-packages/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "/Users/j1z0/Code/RealPython/mvp_for_Adv_Python_Web_Book/tests/payments/testViews.py", line 266, in test_registering_user_when_strip_is_down_all_or_nothing
    self.assertEquals(len(users), 0)
AssertionError: 1 != 0

----------------------------------------------------------------------

Ładny. Wydaje się śmieszne, ale to jest dokładnie to, czego chcieliśmy. Pamiętaj:ćwiczymy tutaj TDD. Komunikat o błędzie informuje nas, że użytkownik rzeczywiście jest przechowywany w bazie danych - a dokładnie tego nie chcemy, ponieważ nie zapłacił!

Transakcje na ratunek…



Transakcje

W Django 1.6 istnieje kilka sposobów tworzenia transakcji.

Omówmy kilka.


Zalecany sposób

Zgodnie z dokumentacją Django 1.6:

„Django zapewnia pojedyncze API do kontroli transakcji bazodanowych. […] Atomowość jest definiującą właściwością transakcji bazodanowych. atomic pozwala nam stworzyć blok kodu, w ramach którego gwarantowana jest niepodzielność bazy danych. Jeśli blok kodu zostanie pomyślnie zakończony, zmiany są zatwierdzane w bazie danych. Jeśli jest wyjątek, zmiany są cofane”.

Atomic może być używany zarówno jako dekorator, jak i jako menedżer_kontekstu. Więc jeśli użyjemy go jako menedżera kontekstu, kod w naszej funkcji rejestru wygląda tak:

from django.db import transaction

try:
    with transaction.atomic():
        user = User.create(
            cd['name'], cd['email'], 
            cd['password'], cd['last_4_digits'])

        if customer:
            user.stripe_id = customer.id
            user.save()
        else:
            UnpaidUsers(email=cd['email']).save()

except IntegrityError:
    form.addError(cd['email'] + ' is already a member')

Zwróć uwagę na wiersz with transaction.atomic() . Cały kod wewnątrz tego bloku zostanie wykonany w ramach transakcji. Jeśli więc ponownie uruchomimy nasze testy, wszystkie powinny zaliczyć! Pamiętaj, że transakcja to pojedyncza jednostka pracy, więc wszystko w menedżerze kontekstu zostaje wycofane razem, gdy UnpaidUsers połączenie nie powiodło się.



Korzystanie z dekoratora

Możemy również spróbować dodać atomic jako dekorator.

@transaction.atomic():
def register(request):
    # ...snip....

    try:
        user = User.create(
            cd['name'], cd['email'], 
            cd['password'], cd['last_4_digits'])

        if customer:
            user.stripe_id = customer.id
            user.save()
        else:
                UnpaidUsers(email=cd['email']).save()

    except IntegrityError:
        form.addError(cd['email'] + ' is already a member')

Jeśli ponownie uruchomimy nasze testy, zakończą się niepowodzeniem z tym samym błędem, który mieliśmy wcześniej.

Dlaczego? Dlaczego transakcja nie została cofnięta poprawnie? Powodem jest to, że transaction.atomic szuka jakiegoś wyjątku i cóż, wykryliśmy ten błąd (tj. IntegrityError w naszej próbie z wyjątkiem bloku), więc transaction.atomic nigdy tego nie widziałem, a zatem standardowe AUTOCOMMIT funkcjonalność przejęła.

Ale oczywiście usunięcie try z wyjątkiem spowoduje, że wyjątek zostanie po prostu wyrzucony w górę łańcucha wywołań i najprawdopodobniej wybuchnie gdzie indziej. Więc my też nie możemy tego zrobić.

Sztuczka polega więc na umieszczeniu atomowego menedżera kontekstu w try z wyjątkiem bloku, który zrobiliśmy w naszym pierwszym rozwiązaniu. Patrząc ponownie na poprawny kod:

from django.db import transaction

try:
    with transaction.atomic():
        user = User.create(
            cd['name'], cd['email'], 
            cd['password'], cd['last_4_digits'])

        if customer:
            user.stripe_id = customer.id
            user.save()
        else:
            UnpaidUsers(email=cd['email']).save()

except IntegrityError:
    form.addError(cd['email'] + ' is already a member')

Kiedy UnpaidUsers uruchamia IntegrityError transaction.atomic() menedżer kontekstu przechwyci go i wykona wycofanie. Zanim nasz kod zostanie wykonany w procedurze obsługi wyjątków (tj. form.addError line) wycofanie zostanie wykonane i w razie potrzeby będziemy mogli bezpiecznie wykonać wywołania bazy danych. Zwróć także uwagę na wszelkie wywołania bazy danych przed lub po transaction.atomic() menedżer kontekstu nie będzie miał wpływu, niezależnie od końcowego wyniku menedżera_kontekstu.



Transakcja na żądanie HTTP

Django 1.6 (podobnie jak 1.5) pozwala również na działanie w trybie „Transakcja na żądanie”. W tym trybie Django automatycznie zapakuje Twoją funkcję widoku w transakcję. Jeśli funkcja zgłosi wyjątek, Django wycofa transakcję, w przeciwnym razie zatwierdzi transakcję.

Aby uzyskać konfigurację, musisz ustawić ATOMIC_REQUEST na True w konfiguracji bazy danych dla każdej bazy danych, która ma mieć to zachowanie. Tak więc w naszym „settings.py” wprowadzamy taką zmianę:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(SITE_ROOT, 'test.db'),
        'ATOMIC_REQUEST': True,
    }
}

W praktyce zachowuje się to dokładnie tak, jakbyś umieścił dekorator w naszej funkcji widoku. Więc to nie służy naszym celom.

Warto jednak zauważyć, że w obu przypadkach ATOMIC_REQUESTS i @transaction.atomic dekoratorem nadal można wyłapywać/obsługiwać te błędy po ich wyrzuceniu z widoku. Aby wyłapać te błędy, musiałbyś zaimplementować niestandardowe oprogramowanie pośredniczące lub możesz nadpisać urls.hadler500 lub utworzyć szablon 500.html.




Zapisz punkty

Mimo że transakcje są atomowe, można je dalej podzielić na punkty zapisu. Pomyśl o punktach zapisu jako o transakcjach częściowych.

Jeśli więc masz transakcję, której wykonanie wymaga czterech instrukcji SQL, możesz utworzyć punkt zapisu po drugiej instrukcji. Po utworzeniu tego punktu zapisu, nawet jeśli trzecia lub czwarta instrukcja nie powiedzie się, możesz wykonać częściowe wycofanie, pozbywając się trzeciej i czwartej instrukcji, ale zachowując dwie pierwsze.

Więc to jest w zasadzie jak dzielenie transakcji na mniejsze, lekkie transakcje, co pozwala na częściowe wycofanie lub zatwierdzenie.

Pamiętaj jednak, że główna transakcja ma zostać wycofana (być może z powodu IntegrityError który został podniesiony i nie został złapany, wszystkie savepointy również zostaną wycofane).

Spójrzmy na przykład, jak działają punkty zapisu.

@transaction.atomic()
def save_points(self,save=True):

    user = User.create('jj','inception','jj','1234')
    sp1 = transaction.savepoint()

    user.name = 'starting down the rabbit hole'
    user.stripe_id = 4
    user.save()

    if save:
        transaction.savepoint_commit(sp1)
    else:
        transaction.savepoint_rollback(sp1)

Tutaj cała funkcja jest w transakcji. Po utworzeniu nowego użytkownika tworzymy punkt zapisu i otrzymujemy odniesienie do punktu zapisu. Kolejne trzy stwierdzenia-

user.name = 'starting down the rabbit hole'
user.stripe_id = 4
user.save()

-nie są częścią istniejącego punktu zapisu, więc mają szansę być częścią następnego savepoint_rollback lub savepoint_commit . W przypadku savepoint_rollback , wiersz user = User.create('jj','inception','jj','1234') nadal będą zapisywane w bazie danych, mimo że pozostałe aktualizacje nie.

Innymi słowy, poniższe dwa testy opisują działanie punktów zapisu:

def test_savepoint_rollbacks(self):

    self.save_points(False)

    #verify that everything was stored
    users = User.objects.filter(email="inception")
    self.assertEquals(len(users), 1)

    #note the values here are from the original create call
    self.assertEquals(users[0].stripe_id, '')
    self.assertEquals(users[0].name, 'jj')


def test_savepoint_commit(self):
    self.save_points(True)

    #verify that everything was stored
    users = User.objects.filter(email="inception")
    self.assertEquals(len(users), 1)

    #note the values here are from the update calls
    self.assertEquals(users[0].stripe_id, '4')
    self.assertEquals(users[0].name, 'starting down the rabbit hole')

Również po zatwierdzeniu lub wycofaniu punktu zapisu możemy kontynuować pracę w tej samej transakcji. A na tę pracę nie będzie miał wpływu wynik poprzedniego punktu zapisu.

Na przykład, jeśli zaktualizujemy nasze save_points funkcja jako taka:

@transaction.atomic()
def save_points(self,save=True):

    user = User.create('jj','inception','jj','1234')
    sp1 = transaction.savepoint()

    user.name = 'starting down the rabbit hole'
    user.save()

    user.stripe_id = 4
    user.save()

    if save:
        transaction.savepoint_commit(sp1)
    else:
        transaction.savepoint_rollback(sp1)

    user.create('limbo','illbehere@forever','mind blown',
           '1111')

Niezależnie od tego, czy savepoint_commit lub savepoint_rollback został nazwany „limbo”, użytkownik nadal zostanie pomyślnie utworzony. Chyba że coś innego spowoduje wycofanie całej transakcji.



Zagnieżdżone transakcje

Oprócz ręcznego określania punktów zapisu, za pomocą savepoint() , savepoint_commit i savepoint_rollback , utworzenie zagnieżdżonej Transakcji automatycznie utworzy dla nas punkt zapisu i wycofa go, jeśli wystąpi błąd.

Rozszerzając nasz przykład nieco dalej, otrzymujemy:

@transaction.atomic()
def save_points(self,save=True):

    user = User.create('jj','inception','jj','1234')
    sp1 = transaction.savepoint()

    user.name = 'starting down the rabbit hole'
    user.save()

    user.stripe_id = 4
    user.save()

    if save:
        transaction.savepoint_commit(sp1)
    else:
        transaction.savepoint_rollback(sp1)

    try:
        with transaction.atomic():
            user.create('limbo','illbehere@forever','mind blown',
                   '1111')
            if not save: raise DatabaseError
    except DatabaseError:
        pass

Tutaj widzimy, że po zajęciu się naszymi punktami zapisu używamy transaction.atomic menedżer kontekstu, aby objąć nasze stworzenie użytkownika „limbo”. Gdy ten menedżer kontekstu jest wywoływany, w efekcie tworzy punkt zapisu (ponieważ jesteśmy już w transakcji) i ten punkt zapisu zostanie zatwierdzony lub wycofany po wyjściu z menedżera kontekstu.

Dlatego następujące dwa testy opisują ich zachowanie:

 def test_savepoint_rollbacks(self):

    self.save_points(False)

    #verify that everything was stored
    users = User.objects.filter(email="inception")
    self.assertEquals(len(users), 1)

    #savepoint was rolled back so we should have original values
    self.assertEquals(users[0].stripe_id, '')
    self.assertEquals(users[0].name, 'jj')

    #this save point was rolled back because of DatabaseError
    limbo = User.objects.filter(email="illbehere@forever")
    self.assertEquals(len(limbo),0)

def test_savepoint_commit(self):
    self.save_points(True)

    #verify that everything was stored
    users = User.objects.filter(email="inception")
    self.assertEquals(len(users), 1)

    #savepoint was committed
    self.assertEquals(users[0].stripe_id, '4')
    self.assertEquals(users[0].name, 'starting down the rabbit hole')

    #save point was committed by exiting the context_manager without an exception
    limbo = User.objects.filter(email="illbehere@forever")
    self.assertEquals(len(limbo),1)

Tak więc w rzeczywistości możesz użyć atomic lub savepoint do tworzenia punktów zapisu wewnątrz transakcji. Z atomic nie musisz martwić się wyraźnie o zatwierdzenie / wycofanie, gdzie tak jak w przypadku savepoint masz pełną kontrolę nad tym, kiedy to się dzieje.



Wniosek

Jeśli miałeś jakieś doświadczenia z wcześniejszymi wersjami transakcji Django, możesz zobaczyć, o ile prostszy jest model transakcji. Posiada również funkcję AUTOCOMMIT on domyślnie jest doskonałym przykładem „rozsądnych” wartości domyślnych, z których dostarczania szczycą się zarówno Django, jak i Python. W przypadku wielu systemów nie musisz zajmować się bezpośrednio transakcjami, po prostu pozwól AUTOCOMMIT wykonać swoją pracę. Ale jeśli tak, mam nadzieję, że ten post dostarczy ci informacji potrzebnych do zarządzania transakcjami w Django jak profesjonalista.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak zmienić tekst na małe litery w SQL?

  2. SQL INSERT dla początkujących

  3. Język manipulacji danymi SQL

  4. Zamówienie warunkowe według

  5. Routing tylko do odczytu dla zawsze włączonych