Nếu bạn đã từng dành nhiều thời gian cho việc quản lý giao dịch cơ sở dữ liệu Django, bạn sẽ biết nó có thể trở nên khó hiểu như thế nào. Trước đây, tài liệu cung cấp khá sâu, nhưng sự hiểu biết chỉ đến khi xây dựng và thử nghiệm.
Có rất nhiều trình trang trí để làm việc, như commit_on_success
, commit_manually
, commit_unless_managed
, rollback_unless_managed
, enter_transaction_management
, leave_transaction_management
, chỉ để nêu tên một vài. May mắn thay, với Django 1.6, tất cả điều đó sẽ xảy ra. Bạn thực sự chỉ cần biết về một vài chức năng bây giờ. Và chúng tôi sẽ nhận được những điều đó chỉ trong một giây. Trước tiên, chúng tôi sẽ giải quyết các chủ đề sau:
- Quản lý giao dịch là gì?
- Có vấn đề gì với quản lý giao dịch trước Django 1.6?
Trước khi tham gia:
- Điều gì đúng về quản lý giao dịch trong Django 1.6?
Và sau đó xử lý một ví dụ chi tiết:
- Ví dụ về sọc
- Giao dịch
- Cách được đề xuất
- Sử dụng trình trang trí
- Giao dịch trên mỗi Yêu cầu HTTP
- SavePoints
- Giao dịch lồng nhau
Giao dịch là gì?
Theo SQL-92, “Giao dịch SQL (đôi khi được gọi đơn giản là“ giao dịch ”) là một chuỗi thực thi các câu lệnh SQL là nguyên tử liên quan đến khôi phục”. Nói cách khác, tất cả các câu lệnh SQL được thực thi và cam kết cùng nhau. Tương tự như vậy, khi được cuộn lại, tất cả các câu lệnh sẽ được cuộn lại cùng nhau.
Ví dụ:
# START
note = Note(title="my first note", text="Yay!")
note = Note(title="my second note", text="Whee!")
address1.save()
address2.save()
# COMMIT
Vì vậy, một giao dịch là một đơn vị công việc duy nhất trong cơ sở dữ liệu. Và đơn vị công việc đó được phân định bằng một giao dịch bắt đầu và sau đó là một cam kết hoặc một lần hoàn trả rõ ràng.
Có vấn đề gì với quản lý giao dịch trước Django 1.6?
Để trả lời đầy đủ câu hỏi này, chúng ta phải giải quyết cách xử lý các giao dịch trong cơ sở dữ liệu, thư viện máy khách và trong Django.
Cơ sở dữ liệu
Mọi câu lệnh trong cơ sở dữ liệu phải chạy trong một giao dịch, ngay cả khi giao dịch chỉ bao gồm một câu lệnh.
Hầu hết các cơ sở dữ liệu đều có AUTOCOMMIT
, thường được đặt thành True làm mặc định. AUTOCOMMIT
này kết thúc mọi tuyên bố trong một giao dịch được cam kết ngay lập tức nếu tuyên bố thành công. Tất nhiên, bạn có thể gọi một cái gì đó theo cách thủ công như START_TRANSACTION
sẽ tạm thời đình chỉ AUTOCOMMIT
cho đến khi bạn gọi COMMIT_TRANSACTION
hoặc ROLLBACK
.
Tuy nhiên, lợi ích ở đây là AUTOCOMMIT
cài đặt áp dụng một cam kết ngầm sau mỗi câu lệnh .
Thư viện khách hàng
Sau đó là thư viện ứng dụng khách Python như sqlite3 và mysqldb, cho phép các chương trình Python tự giao tiếp với cơ sở dữ liệu. Các thư viện như vậy tuân theo một bộ tiêu chuẩn về cách truy cập và truy vấn cơ sở dữ liệu. Tiêu chuẩn đó, DB API 2.0, được mô tả trong PEP 249. Mặc dù nó có thể khiến việc đọc hơi khô khan, nhưng một điểm quan trọng cần lưu ý là PEP 249 nêu rõ rằng cơ sở dữ liệu AUTOCOMMIT
nên TẮT theo mặc định.
Điều này rõ ràng xung đột với những gì đang xảy ra trong cơ sở dữ liệu:
- Các câu lệnh SQL luôn phải chạy trong một giao dịch mà cơ sở dữ liệu thường mở cho bạn thông qua
AUTOCOMMIT
. - Tuy nhiên, theo PEP 249, điều này sẽ không xảy ra.
- Thư viện ứng dụng khách phải phản ánh những gì xảy ra trong cơ sở dữ liệu, nhưng vì chúng không được phép bật
AUTOCOMMIT
theo mặc định, chúng chỉ cần gói các câu lệnh SQL của bạn trong một giao dịch, giống như cơ sở dữ liệu.
Được chứ. Ở lại với tôi lâu hơn một chút.
Django
Nhập Django. Django cũng có một cái gì đó để nói về quản lý giao dịch. Trong Django 1.5 và trước đó, Django về cơ bản chạy với một giao dịch mở và tự động cam kết giao dịch đó khi bạn ghi dữ liệu vào cơ sở dữ liệu. Vì vậy, mỗi khi bạn gọi một cái gì đó như model.save()
hoặc model.update()
, Django đã tạo các câu lệnh SQL thích hợp và thực hiện giao dịch.
Cũng trong Django 1.5 trở về trước, bạn nên sử dụng TransactionMiddleware
để ràng buộc các giao dịch với các yêu cầu HTTP. Mỗi yêu cầu đã được đưa ra một giao dịch. Nếu phản hồi trả về không có ngoại lệ, Django sẽ thực hiện giao dịch nhưng nếu chức năng xem của bạn gặp lỗi, hãy ROLLBACK
sẽ được gọi. Điều này có hiệu lực, đã tắt AUTOCOMMIT
. Nếu bạn muốn quản lý giao dịch theo kiểu tự động gửi tiêu chuẩn, cấp cơ sở dữ liệu, bạn phải tự quản lý các giao dịch - thường bằng cách sử dụng trình trang trí giao dịch trên chức năng xem của bạn, chẳng hạn như @transaction.commit_manually
hoặc @transaction.commit_on_success
.
Hít thở. Hoặc hai.
Điều này có nghĩa là gì?
Vâng, có rất nhiều thứ đang diễn ra ở đó, và hóa ra hầu hết các nhà phát triển chỉ muốn tự động chuyển đổi cấp độ cơ sở dữ liệu tiêu chuẩn - nghĩa là các giao dịch ở lại hậu trường, thực hiện công việc của chúng, cho đến khi bạn cần điều chỉnh chúng theo cách thủ công.
Điều gì đúng về quản lý giao dịch trong Django 1.6?
Bây giờ, chào mừng bạn đến với Django 1.6. Cố gắng hết sức để quên mọi thứ chúng ta vừa nói và chỉ cần nhớ rằng trong Django 1.6, bạn sử dụng cơ sở dữ liệu AUTOCOMMIT
và quản lý các giao dịch theo cách thủ công khi cần thiết. Về cơ bản, chúng tôi có một mô hình đơn giản hơn nhiều, về cơ bản thực hiện những gì cơ sở dữ liệu được thiết kế để làm ngay từ đầu.
Lý thuyết đủ rồi. Hãy viết mã.
Ví dụ về sọc
Ở đây chúng tôi có chức năng xem ví dụ này xử lý việc đăng ký người dùng và gọi Stripe để xử lý thẻ tín dụng.
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)
)
Chế độ xem này đầu tiên gọi Customer.create
mà thực sự gọi Stripe để xử lý việc xử lý thẻ tín dụng. Sau đó, chúng tôi tạo một người dùng mới. Nếu chúng tôi nhận được phản hồi từ Stripe, chúng tôi sẽ cập nhật cho khách hàng mới tạo bằng stripe_id
. Nếu chúng tôi không nhận được khách hàng trở lại (Stripe đang ngừng hoạt động), chúng tôi sẽ thêm một mục vào UnpaidUsers
bảng với email khách hàng mới được tạo, vì vậy chúng tôi có thể yêu cầu họ thử lại chi tiết thẻ tín dụng của mình sau.
Ý tưởng là ngay cả khi Stripe ngừng hoạt động, người dùng vẫn có thể đăng ký và bắt đầu sử dụng trang web của chúng tôi. Chúng tôi sẽ chỉ hỏi lại họ vào một ngày sau để biết thông tin thẻ tín dụng.
Tôi hiểu rằng đây có thể là một ví dụ giả tạo và đó không phải là cách tôi sẽ triển khai chức năng như vậy nếu phải làm, nhưng mục đích là để chứng minh các giao dịch.
Trở đi. Suy nghĩ về các giao dịch và lưu ý rằng theo mặc định, Django 1.6 cung cấp cho chúng ta AUTOCOMMIT
hành vi của cơ sở dữ liệu của chúng ta, hãy xem mã liên quan đến cơ sở dữ liệu lâu hơn một chút.
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:
# ...
Bạn có thể phát hiện ra bất kỳ vấn đề nào không? Điều gì sẽ xảy ra nếu UnpaidUsers(email=cd['email']).save()
dòng không thành công?
Bạn sẽ có một người dùng, đã đăng ký trong hệ thống, mà hệ thống cho rằng đã xác minh thẻ tín dụng của họ, nhưng trên thực tế, họ chưa xác minh thẻ.
Chúng tôi chỉ muốn một trong hai kết quả:
- Người dùng được tạo (trong cơ sở dữ liệu) và có
stripe_id
. - Người dùng được tạo (trong cơ sở dữ liệu) và không có
stripe_id
VÀ một hàng được liên kết trongUnpaidUsers
bảng có cùng địa chỉ email được tạo.
Điều đó có nghĩa là chúng tôi muốn hai câu lệnh cơ sở dữ liệu riêng biệt có thể cam kết hoặc cả hai lần khôi phục. Một trường hợp hoàn hảo cho giao dịch khiêm tốn.
Trước tiên, hãy viết một số bài kiểm tra để xác minh mọi thứ hoạt động theo cách chúng ta muốn.
@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)
Trình trang trí ở đầu bài kiểm tra là một mô hình sẽ tạo ra 'IntegrityError' khi chúng tôi cố gắng lưu vào UnpaidUsers
bảng.
Đây là câu trả lời cho câu hỏi, “Điều gì sẽ xảy ra nếu UnpaidUsers(email=cd['email']).save()
đường dây không thành công? ” Đoạn mã tiếp theo chỉ tạo ra một phiên chế nhạo, với thông tin thích hợp mà chúng tôi cần cho chức năng đăng ký của mình. Và sau đó là with mock.patch
buộc hệ thống tin rằng Stripe đang gặp sự cố… cuối cùng thì chúng tôi cũng đi đến bài kiểm tra.
resp = register(self.request)
Dòng trên chỉ gọi chức năng xem đăng ký của chúng tôi truyền trong yêu cầu giả mạo. Sau đó, chúng tôi chỉ kiểm tra để đảm bảo các bảng không được cập nhật:
#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)
Vì vậy, nó sẽ không thành công nếu chúng tôi chạy thử nghiệm:
======================================================================
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
----------------------------------------------------------------------
Tốt đẹp. Nói thì có vẻ buồn cười nhưng đó chính xác là những gì chúng tôi muốn. Hãy nhớ:chúng tôi đang luyện tập TDD ở đây. Thông báo lỗi cho chúng tôi biết rằng Người dùng thực sự đang được lưu trữ trong cơ sở dữ liệu - đó chính xác là điều chúng tôi không muốn vì họ không trả tiền!
Các giao dịch để giải cứu…
Giao dịch
Thực tế có một số cách để tạo giao dịch trong Django 1.6.
Hãy xem qua một vài điều.
Cách được đề xuất
Theo tài liệu Django 1.6:
“Django cung cấp một API duy nhất để kiểm soát các giao dịch cơ sở dữ liệu. […] Tính nguyên tử là thuộc tính xác định của các giao dịch cơ sở dữ liệu. nguyên tử cho phép chúng ta tạo một khối mã trong đó tính nguyên tử trên cơ sở dữ liệu được đảm bảo. Nếu khối mã được hoàn thành thành công, các thay đổi được cam kết với cơ sở dữ liệu. Nếu có ngoại lệ, các thay đổi sẽ được khôi phục. ”
Atomic có thể được sử dụng như một trình trang trí hoặc một trình quản lý ngữ cảnh. Vì vậy, nếu chúng tôi sử dụng nó như một trình quản lý ngữ cảnh, mã trong hàm đăng ký của chúng tôi sẽ giống như sau:
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')
Lưu ý dòng with transaction.atomic()
. Tất cả mã bên trong khối đó sẽ được thực thi bên trong một giao dịch. Vì vậy, nếu chúng tôi chạy lại các bài kiểm tra của mình, tất cả chúng đều sẽ vượt qua! Hãy nhớ rằng một giao dịch là một đơn vị công việc duy nhất, vì vậy mọi thứ bên trong trình quản lý ngữ cảnh được khôi phục lại với nhau khi UnpaidUsers
cuộc gọi không thành công.
Sử dụng trình trang trí
Chúng tôi cũng có thể thử thêm nguyên tử làm trình trang trí.
@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')
Nếu chúng tôi chạy lại các bài kiểm tra của mình, chúng sẽ không thành công với cùng một lỗi mà chúng tôi đã mắc phải trước đó.
Tại sao vậy? Tại sao giao dịch không quay trở lại chính xác? Lý do là vì transaction.atomic
đang tìm kiếm một số loại Ngoại lệ và tốt, chúng tôi đã gặp lỗi đó (tức là IntegrityError
trong thử khối ngoại trừ của chúng tôi), vì vậy transaction.atomic
không bao giờ nhìn thấy nó và do đó, AUTOCOMMIT
tiêu chuẩn đã tiếp quản chức năng.
Nhưng tất nhiên, việc loại bỏ thử ngoại trừ sẽ khiến ngoại lệ chỉ được ném lên chuỗi cuộc gọi và rất có thể sẽ nổ tung ở một nơi khác. Vì vậy, chúng tôi cũng không thể làm điều đó.
Vì vậy, mẹo là đặt trình quản lý ngữ cảnh nguyên tử bên trong khối try ngoại trừ, đó là những gì chúng tôi đã làm trong giải pháp đầu tiên của mình. Nhìn lại mã chính xác:
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')
Khi UnpaidUsers
kích hoạt IntegrityError
transaction.atomic()
trình quản lý ngữ cảnh sẽ nắm bắt nó và thực hiện khôi phục. Vào thời điểm mã của chúng tôi thực thi trong trình xử lý ngoại lệ, (tức là form.addError
dòng) quá trình khôi phục sẽ được thực hiện và chúng tôi có thể thực hiện các lệnh gọi cơ sở dữ liệu một cách an toàn nếu cần thiết. Cũng lưu ý bất kỳ lệnh gọi cơ sở dữ liệu nào trước hoặc sau transaction.atomic()
trình quản lý ngữ cảnh sẽ không bị ảnh hưởng bất kể kết quả cuối cùng của context_manager.
Giao dịch cho mỗi Yêu cầu HTTP
Django 1.6 (như 1.5) cũng cho phép bạn hoạt động ở chế độ "Giao dịch theo yêu cầu". Trong chế độ này, Django sẽ tự động bao hàm chức năng xem của bạn trong một giao dịch. Nếu hàm ném một ngoại lệ, Django sẽ khôi phục giao dịch, nếu không, nó sẽ thực hiện giao dịch.
Để thiết lập nó, bạn phải đặt ATOMIC_REQUEST
thành True trong cấu hình cơ sở dữ liệu cho mỗi cơ sở dữ liệu mà bạn muốn có hành vi này. Vì vậy, trong “settings.py” của chúng tôi, chúng tôi thực hiện thay đổi như sau:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'test.db'),
'ATOMIC_REQUEST': True,
}
}
Trong thực tế, điều này hoạt động chính xác như thể bạn đặt trình trang trí vào chức năng xem của chúng tôi. Vì vậy, nó không phục vụ mục đích của chúng tôi ở đây.
Tuy nhiên, cần lưu ý rằng với cả ATOMIC_REQUESTS
và @transaction.atomic
decorator vẫn có thể bắt / xử lý những lỗi đó sau khi chúng được ném khỏi chế độ xem. Để khắc phục những lỗi đó, bạn sẽ phải triển khai một số phần mềm trung gian tùy chỉnh hoặc bạn có thể ghi đè urls.hadler500 hoặc bằng cách tạo một mẫu 500.html.
SavePoints
Mặc dù các giao dịch là nguyên tử, chúng có thể được chia nhỏ hơn nữa thành các điểm lưu. Hãy coi các điểm lưu trữ là các giao dịch từng phần.
Vì vậy, nếu bạn có một giao dịch cần bốn câu lệnh SQL để hoàn thành, bạn có thể tạo một điểm lưu sau câu lệnh thứ hai. Khi điểm lưu đó được tạo, ngay cả khi câu lệnh thứ 3 hoặc thứ 4 không thành công, bạn có thể thực hiện khôi phục một phần, loại bỏ câu lệnh thứ 3 và thứ 4 nhưng giữ lại hai câu lệnh đầu tiên.
Vì vậy, về cơ bản nó giống như việc chia nhỏ một giao dịch thành các giao dịch nhẹ hơn cho phép bạn thực hiện các cam kết hoặc hoàn vốn một phần.
Nhưng hãy lưu ý nếu giao dịch chính sẽ được khôi phục (có thể do
IntegrityError
đã được nâng lên và không bị bắt, thì tất cả các điểm lưu cũng sẽ được khôi phục).
Hãy xem ví dụ về cách hoạt động của các điểm lưu.
@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)
Ở đây toàn bộ chức năng nằm trong một giao dịch. Sau khi tạo người dùng mới, chúng tôi tạo một điểm lưu và lấy tham chiếu đến điểm lưu. Ba câu tiếp theo-
user.name = 'starting down the rabbit hole'
user.stripe_id = 4
user.save()
-không phải là một phần của điểm lưu hiện có, vì vậy họ có cơ hội trở thành một phần của savepoint_rollback
tiếp theo hoặc savepoint_commit
. Trong trường hợp của một savepoint_rollback
, dòng user = User.create('jj','inception','jj','1234')
sẽ vẫn được cam kết với cơ sở dữ liệu mặc dù phần còn lại của các bản cập nhật sẽ không.
Nói cách khác, hai bài kiểm tra sau đây mô tả cách hoạt động của các điểm lưu:
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')
Ngoài ra, sau khi chúng tôi cam kết hoặc khôi phục một điểm lưu, chúng tôi có thể tiếp tục thực hiện công việc trong cùng một giao dịch. Và công việc đó sẽ không bị ảnh hưởng bởi kết quả của điểm lưu trước đó.
Ví dụ:nếu chúng tôi cập nhật save_points
của mình chức năng như vậy:
@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')
Bất kể savepoint_commit
hoặc savepoint_rollback
được gọi là người dùng ‘lấp lửng’ sẽ vẫn được tạo thành công. Trừ khi có điều gì khác khiến toàn bộ giao dịch được khôi phục.
Giao dịch lồng nhau
Ngoài việc chỉ định các điểm lưu theo cách thủ công, với savepoint()
, savepoint_commit
và savepoint_rollback
, việc tạo một Giao dịch lồng nhau sẽ tự động tạo một điểm lưu cho chúng tôi và khôi phục nó nếu chúng tôi gặp lỗi.
Mở rộng ví dụ của chúng tôi xa hơn một chút, chúng tôi nhận được:
@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
Ở đây, chúng ta có thể thấy rằng sau khi xử lý các điểm lưu của mình, chúng ta đang sử dụng transaction.atomic
trình quản lý ngữ cảnh để bao gồm việc tạo ra người dùng 'lấp lửng' của chúng tôi. Khi trình quản lý ngữ cảnh đó được gọi, nó thực sự tạo ra một điểm lưu (vì chúng ta đã ở trong một giao dịch) và điểm lưu đó sẽ được cam kết hoặc khôi phục khi thoát khỏi trình quản lý ngữ cảnh.
Do đó, hai bài kiểm tra sau đây mô tả hành vi của chúng:
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)
Vì vậy, trong thực tế, bạn có thể sử dụng atomic
hoặc savepoint
để tạo các điểm lưu bên trong một giao dịch. Với atomic
bạn không phải lo lắng rõ ràng về cam kết / khôi phục, giống như với savepoint
bạn có toàn quyền kiểm soát khi điều đó xảy ra.
Kết luận
Nếu bạn đã có bất kỳ kinh nghiệm nào trước đó với các phiên bản trước đó của giao dịch Django, bạn có thể thấy mô hình giao dịch đơn giản hơn nhiều như thế nào. Cũng có AUTOCOMMIT
theo mặc định là một ví dụ tuyệt vời về mặc định “lành mạnh” mà Django và Python đều tự hào về việc cung cấp. Đối với nhiều hệ thống, bạn sẽ không cần phải xử lý trực tiếp các giao dịch, chỉ cần để AUTOCOMMIT
làm công việc của nó. Nhưng nếu bạn làm vậy, hy vọng bài đăng này sẽ cung cấp cho bạn thông tin cần thiết để quản lý các giao dịch trong Django như một người chuyên nghiệp.