django使用mysql事务处理_使用Django进行事务管理

原标题:使用Django进行事务管理

django使用mysql事务处理_使用Django进行事务管理_第1张图片

Python部落组织翻译,禁止转载,欢迎转发

如果你曾经花大量时间学习过Django数据库事务管理,你就会知道学习它是多么的令人迷惑。在过去,官方提供的辅助文档具有相当的深度,但是理解它只能通过建立数据库和做实验。

有过多的修饰词来说明这一点,像,commit_on_success, commit_manually,commit_unless_managed, rollback_unless_managed, enter_transaction_management,leave_transaction_management, 我在这里仅仅说明几个例子。幸运的是,通过使用Django 1.6,这些都不再成为问题。所以你现在真的需要只需要一些函数。并且我们可以很快就会学到这些。首先,我们将会重点讲解这些话题:

什么是事务管理?

在Django 1.6之前事务管理是怎么样的?

在深入之前:

在Django 1.6中关于事务管理什么是对的?

然后解决一些详细的例子:

条纹示例

事务

推荐的方式

使用装饰器

每种HTTP请求的事务

存盘点

嵌套事务

什么是事务?

根据SQL-92,“一个SQL事务(有时简称为事务)是一系列SQL语句的执行”。换句话讲,所有的SQL语句是在一块执行的。同样的,当需要回滚的时候,所有的语句会一起进行回滚执行。

例如:

# START note = Note(title="my first note", text="Yay!")

note = Note(title="my second note", text="Whee!")

address1.save()

address2.save()

# COMMIT

因此,事务就是数据库中的单个单元的工作。并且这个单个单元的工作是通过一个起始事务进行划分,然后进行或者回滚。

在Django 1.6之前事务管理是怎么样的?

为了充分地这个问题,我们必须解决事务在数据库、客户端和Django中是如何处理的。

数据库

数据库中的每一个语句必须在事务中运行,即使事务仅仅包括一个语句。

大多数的数据库都有AUTOCOMMIT设置,这个设置通常默认设置为真。这个AUTOCOMMIT覆盖了事务中的每一个语句,如果语句执行成功的话,就 会立即执行。当然了,你可以调用像START_TRANSACTION的语句,这个语句将会暂时停止AUTOCOMMIT,直到你调用 COMMIT_TRANSACTION或ROLLBACK。

但是,这里的缺点就是AUTOCOMMIT设置会在每一个语句之后应用一个隐含的执行。

客户端

有像sqlite3和mysqldb这样的客户端,可以将Python程序与数据库本身进行连接。这样的客户端具有一系列的用于获取和查询数据库的标准。 DB API 2.0标准在PEP 249中进行了描述。这个标准数据库可能阅读起来比较枯燥,一个重要的问题就是PEP 249表明数据库 AUTOCOMMIT应该是默认为OFF.

这就和数据中的一些情况发生了冲突:

SQL语句总是必须在事务中运行,数据库一般是通过AUTOCOMMIT对你开放。

然而,根据PEP 249,这个不应该发生。

客户端必须面对数据库中发生的情况,但是因为他们不允许将AUTOCOMMIT默认转换成on,他们就像在数据库中仅仅在事务中包括你的SQL语句。

好的。和我呆在一起久一点吧。

Django

进入到Django. Django和事务管理之间也有很多可以探讨的。在Django 1.5和之前的版本中,Django基本上是以开放事务运行,并且当你写数据到数据库时会自动执行相应的事务。所以每次当你调用函数 model.save()或者model.update()时,Django就会产生合适的SQL语句,然后执行对应的事务。

并且在Django 1.5和之前的版本中,推荐使用TransactionMiddleware来绑定事务到HTTP请求中。每一个请求都会给出一个事务。如果响应没有例外 的返回,Django将会执行这个事务,但是如果你的回调函数抛出了一个错误,ROLLBACK将会调用。这实际上会关掉AUTOCOMMIT。如果你想 要标准,数据库级的自动执行方式的事务管理,你必须管理这些事务本身——通常在你的回调函数中采用事务装饰器,比如 @transaction.commit_manually或@transaction.commit_on_success。

休息一下。

那这意味着什么呢?

是的,那里还有很多事情可以去做,并且它证明了大多数的开发者只是想要标准数据库级的自动执行,也就是说事务一直都在幕后,做着它们的事情,直到你需要人工去调整它们。

在Django 1.6中关于事务管理为什么是对的?

现在,欢迎来到Django 1.6。尽力忘记刚才我们探讨过的所有东西,仅仅记得在Django 1.6中,你可以使用数据库AUTOCOMMIT语句,并且当需要的时候可以手动管理事务。从本质上讲,我们拥有一个非常简单的模型,基本上首先是做数据库是如何设计的。

理论已经足够了。让我们开始编写代码吧。

条纹示例

这里我们采用这个例子来观察函数如何处理登记用户和采用Strip进行信用卡处理。

def register(request):

user = None

if request.method == 'POST':

form = UserForm(request.POST)

if form.is_valid():

customer = Customer.create("subion",

email = form.cleaned_data['email'],

deion = 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)

)

这个函数首先调用Customer.create,通常调用Stripe来处理信用卡处理。然后我们创建一个新的用户。如果我们从Strip得到一个响 应,我们就采用strip_id更新新创建的客户。如果我们不能得到一个客户回应(Strip是向下的),我们将通过新创建的客户电子邮件增加一个 UnpaidUsers表的入口。因此我们可以要求它们稍后重新更新信用卡详情。

思想就是即使Strip是向下的,用户仍然可以登记和开始使用我们的网站。我们只是在后期再次要求他们信用卡信息。

我知道这可能是一个有点不自然的例子,并且这不是我实现这些功能的方式,但是目的是为了说明事务。

继续。考虑一下事务,并且记住Django 1.6默认给予我们AUTOCOMMIT行为处理数据库。让我们看一下稍微长点的数据库相关的代码。

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:

你能发现任何问题吗?那么UnpaidUsers(email=cd['email']).save() 这一行执行失败会发生什么呢?

你将会有一个用户登记在系统中,并且系统认为已经验证过它的信用卡,但实际上他们并没有验证过信用卡。

我们只想用下面二个结果中的一个:

1. 用户被创建(在数据库中),具有一个stripe_id.

2. 用户被创建(在数据库中),没有strip_id,并且在UnpaidUsers表中相关行中具有相同的电子邮件地址被产生。

这就意味着我们想要二个分离的数据库语句,既能够执行也能够回滚。一个不大的事务的完美例子。

首先,让我们写 一些测试来验证事情会像我们想的那样进行。

@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)

在测试顶部的装饰器是一个模拟,当我们尝试保存这个UnpaidUsers表时,将会抛出一个Integrity错误。

这就回答这个问题“那么UnpaidUsers(email=cd['email']).save() 这一行执行失败会发生什么呢?”接下来的一小点代码只是创建一个模拟会话,通过我们需要的合适的信息用于我们的注册函数。然后,with mock.patch迫使系统相信Stripe是向下的....最后我们进入到测试。

Resp = resister(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)

所以当我们运行测试的时候会运行失败:

FAIL: test_registering_user_when_st......

Traceback (most recent call last):

File "/Users/j1z0/..., line 1201, in patched

return func(*args, **keywargs)

File "/Users/j1z0/.....", line 266,

in test_registering_us.....othing

self.assertEquals(len(users), 0)

Asserti: 1 != 0

很好,看起来非常有趣,并且这也是我们想要的。记住,这里我们采用的是TDD。错误信息告诉我们用户确实存储在数据库中,这也的确不是我们想要的,因为他们不会支付!

通过事务来解决这个问题......

事务

在Django中实际上有很多方式来创建事务。

让我们来看一些。

推荐的方式

根据Django 1.6文档,“Django提供了一个简单的API来控制数据库的事务。。。原子数是数据库事务定义的特性。原子性允许我们在数据库原子数保证的情况下编 码一块代码。”如果代码块成功执行了,数据库中将会进行相应的变化。如果有例外,变化将会回滚。

原子性可以用作装饰器或者作为上下文管理。所以如果我们使用它作为上下文管理,在我们的注册函数中的代码应该看起来像这样:

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')

注意with transaction.atomic()这一行。在这个块中的所有代码将会执行。所以如果重新运行我们的测试,他们所有都应该通过!记住一个事务是一个单一的工作单元,所以当UnpaidUsers调用失败时,在上下文管理中的一切都会一起进行回滚。

使用装饰器

我们也可以尝试正价原子性作为装饰器。

@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')

如果我们重新运行我们的测试,他们会遇到我们之前相同的错误。

那是为什么呢?为什么那些事务不能正确的回滚呢?原因是因为transactions.atomic会寻找一些异常,我们捕捉到那个错误(比如在我们的 try except语句块中的IntergrityError),所以transaction.atomic不会解决它,因此标准的AUTOCOMMIT功能会 接管它。

但是,当然去掉try except将会导致异常抛出,并且很有可能会在其他地方发生。所以我们不能这么做。

所以技巧就是把原子的上下文管理放在我们做的第一个解决方案的try except块里。再次看一下正确的代码:

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')

当UnpaidUsers遇到IntegrityError,transaction.atomic()上下文管理将会捕捉它并进行回滚。当我们的代码在处理异常时(比如,form.addErrot行),回滚将会完成,如果必要的话我们能够安全的进行数据库调用。

每个HTTP请求的事务

Django 1.6(和1.5一样)允许你以一种“每种请求的事务”的方式进行操作。在这种方式下,Django会自动在事务中包装你的回调函数。如果函数抛出了一个异常,Django将会回滚事务,否则它将会执行这个事务。

为了得到这个设置,你必须在每一个数据库设置中设置ATOMIC_REQUEST为真。所以在我们的“settting.py”中,我们修改如下:

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.sqlite3',

'NAME': os.path.join(SITE_ROOT, 'test.db'),

'ATOMIC_REQUEST': True,

}

}

实际上,将装饰器放在回调函数中,这只是使行为更加准确。所以这里并没有说明我们的目的。

存盘点

即使事务是原子的,他们也可以进一步分解为存盘点。考虑将存盘点作为部分事务。

所以如果你有一个事务,执行4条SQL语句,你可以在第二个村盘点之后创建一个存盘点。一旦存盘点被创建了,即使第三条或者第四条语句执行失败了,你也可以进行部分回滚。去掉第3条和第4条语句但是保留前2条。

所以基本上可以分离一个事务成更小的事务,允许你进行部分回滚或执行。

让我们看一下存盘点如何工作的例子。

@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)

这里整个函数是在一个事务中。在创建一个新用户之后,我们创建了一个存盘点,并得到了这个存盘点的引用。接下来的3条语句:

user.name = 'starting down the rabbit hole'

user.stripe_id = 4

user.save()

进一步,下面二个测试描述了存盘点是如何工作的:

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')

在我们执行或者回滚一个存盘点之后,我们继续在相同的事务中进行。那么那个工作不会受到前一个存盘点的后果的影响。

例如,如果这样更新我们的save_points函数:

@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')

不管是否savepoint_commit或者savepoint_rollback被一个处于成功创建的不定状态的用户调用。除非别的情况引起了整个事务进行回滚。

嵌套事务

为了手动指定存盘点,通过savepoint(),savepoint_commit和savepoint_rollback,创建一个嵌套事务将会自动地为我们创建一个存盘点,如果遇到错误就会回滚。

扩展我们的例子:

@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

这里我们可以看到,在我们解决了我们的存盘点之后, 我们使用transaction.atomic上下文管理来包装我们的不定状态用户的创建。当这个上下文管理被调用的时候,它实际上会创建一个存盘点,并且那个存盘点在退出上下文管理时将会执行或回滚。

因此,下面的两个测试描述了这种行为:

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)

所以实际上,你可以使用atomic或者savepoint在一个事务中创建存盘点。通过atomic你不会担心执行/回滚,然而savepoint你需要当它发生时进行控制。

结论

如果你之前有Django事务的经验,你可以看到事务模型是多么的简单。另外,默认采用AUTOCOMMIT是一个很好的例子,Django和Python都很骄傲进行表达。对于很多系统,你并不需要直接地解决事务,只需要让AUTOCOMMIT自己进行工作。但是,如果你这样做,希望这个帖子会给你在Django管理中需要的信息。返回搜狐,查看更多

责任编辑:

你可能感兴趣的:(django使用mysql事务处理_使用Django进行事务管理)