Django Atomic Transaction 是否锁定数据库?
Does Django Atomic Transaction lock the database?
当你这样做时:
@transaction.atomic
def update_db():
do_bulk_update()
当函数是运行时,它是否锁定了数据库?
我问的是关于 django 的原子事务:
https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details
(我在这个答案中假设现代 SQL 数据库。)
tl;博士
事务不是锁,而是持有在操作过程中自动获取的锁。而django默认是不加锁的,所以答案是否定的,它不加锁数据库。
例如如果你这样做:
@transaction.atomic
def update_db():
cursor.execute('UPDATE app_model SET model_name TO 'bob' WHERE model_id = 1;')
# some other stuff...
您将在“其他内容”期间锁定 ID 为 1 的 app_model
行。但是直到那个查询它才被锁定。所以如果你想确保一致性,你应该显式地使用锁。
交易
如前所述,事务不是锁,因为那会影响性能。一般来说,它们首先是轻量级机制,以确保如果您进行大量更改,而这些更改对数据库的其他用户来说一次没有意义,那么这些更改似乎会同时发生。 IE。是原子的。事务不会阻止其他用户改变数据库,实际上通常不会阻止其他用户改变您可能正在阅读的相同行。
有关如何保护事务的更多详细信息,请参阅 this guide and your databases docs (e.g. postgres)。
原子的 Django 实现。
当你使用 atomic
装饰器(参考 the code)时,Django 本身会执行以下操作。
尚未在原子块中
禁用自动提交。自动提交是一个应用程序级别的功能,它总是会立即提交事务,所以它看起来对应用程序来说从来没有未完成的事务。
这告诉数据库开始一个新事务。
此时psycopg2
for postgres将事务的隔离级别设置为READ COMMITTED
,也就是说事务中的任何读取只会return提交数据,这意味着如果另一个事务写入,您将不会看到该更改,直到它提交为止。这确实意味着如果该事务在您的事务期间提交,您可能会再次阅读并看到该值在您的事务期间发生了变化。
显然这意味着数据库没有被锁定。
运行您的代码。您所做的任何查询/更改都不会提交。
提交事务。
重新启用自动提交。
在较早的原子块中
基本上在这种情况下,我们尝试使用保存点,这样如果我们“回滚”“事务”,我们就可以恢复到它们,但就数据库连接而言,我们处于同一事务中。
自动锁定
如前所述,数据库可能会为您的事务提供一些自动锁定,如 this doc 中所述。为了证明这一点,请考虑以下代码,该代码在一个 table 和其中一行的 postgres 数据库上运行:
my_table
id | age
---+----
1 | 50
然后你 运行 这个代码:
import psycopg2 as Database
from multiprocessing import Process
from time import sleep
from contextlib import contextmanager
@contextmanager
def connection():
conn = Database.connect(
user='daphtdazz', host='localhost', port=5432, database='db_test'
)
try:
yield conn
finally:
conn.close()
def connect_and_mutate_after_seconds(seconds, age):
with connection() as conn:
curs = conn.cursor()
print('execute update age to %d...' % (age,))
curs.execute('update my_table set age = %d where id = 1;' % (age,))
print('sleep after update age to %d...' % (age,))
sleep(seconds)
print('commit update age to %d...' % (age,))
conn.commit()
def dump_table():
with connection() as conn:
curs = conn.cursor()
curs.execute('select * from my_table;')
print('table: %s' % (curs.fetchall(),))
if __name__ == '__main__':
p1 = Process(target=connect_and_mutate_after_seconds, args=(2, 99))
p1.start()
sleep(0.6)
p2 = Process(target=connect_and_mutate_after_seconds, args=(1, 100))
p2.start()
p2.join()
dump_table()
p1.join()
dump_table()
你得到:
execute update age to 99...
sleep after update age to 99...
execute update age to 100...
commit update age to 99...
sleep after update age to 100...
commit update age to 100...
table: [(1, 100)]
table: [(1, 100)]
关键是第二个进程在第一个命令完成之前启动,但在它调用 update
命令之后,所以第二个进程必须等待锁定,这就是我们不这样做的原因直到 99 岁 commit
之后才看到 sleep after update age to 100
。
如果你把 sleep 放在 exec 之前,你会得到:
sleep before update age to 99...
sleep before update age to 100...
execute update age to 100...
commit update age to 100...
table: [(24, 3), (100, 2)]
execute update age to 99...
commit update age to 99...
table: [(24, 3), (99, 2)]
表示在第二个进程进行更新时未获取锁,这首先发生但在第一个进程的事务期间。
如@daphtdazz answer 所述,Django 在您打开事务时不会获取任何锁,但在您更新数据时,数据库可能会自动获取锁。锁的类型和范围取决于数据库,也可能取决于事务隔离级别。有关这些自动锁定的详细信息,请参阅您的数据库文档。
如果您想手动锁定,有几个选项。
主要且最简单的方法是执行 select_for_update()
查询。这将获取一个更新锁,该锁将阻止对与查询匹配的行的所有其他更新。这与更新事务中的行时自动获取的锁相同,但是 select_for_update()
允许您在实际进行更新之前获取更新锁,这通常很有用。
如果行锁定不适合您的情况,您可以在支持行锁定的数据库(例如 Postgres)中获取咨询锁。开箱即用,Django 不支持这一点,但是有第三方包为 Django 添加了对咨询锁的支持,或者您可以简单地发出适当的原始 SQL 查询。
当你这样做时:
@transaction.atomic
def update_db():
do_bulk_update()
当函数是运行时,它是否锁定了数据库?
我问的是关于 django 的原子事务: https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details
(我在这个答案中假设现代 SQL 数据库。)
tl;博士
事务不是锁,而是持有在操作过程中自动获取的锁。而django默认是不加锁的,所以答案是否定的,它不加锁数据库。
例如如果你这样做:
@transaction.atomic
def update_db():
cursor.execute('UPDATE app_model SET model_name TO 'bob' WHERE model_id = 1;')
# some other stuff...
您将在“其他内容”期间锁定 ID 为 1 的 app_model
行。但是直到那个查询它才被锁定。所以如果你想确保一致性,你应该显式地使用锁。
交易
如前所述,事务不是锁,因为那会影响性能。一般来说,它们首先是轻量级机制,以确保如果您进行大量更改,而这些更改对数据库的其他用户来说一次没有意义,那么这些更改似乎会同时发生。 IE。是原子的。事务不会阻止其他用户改变数据库,实际上通常不会阻止其他用户改变您可能正在阅读的相同行。
有关如何保护事务的更多详细信息,请参阅 this guide and your databases docs (e.g. postgres)。
原子的 Django 实现。
当你使用 atomic
装饰器(参考 the code)时,Django 本身会执行以下操作。
尚未在原子块中
禁用自动提交。自动提交是一个应用程序级别的功能,它总是会立即提交事务,所以它看起来对应用程序来说从来没有未完成的事务。
这告诉数据库开始一个新事务。
此时
psycopg2
for postgres将事务的隔离级别设置为READ COMMITTED
,也就是说事务中的任何读取只会return提交数据,这意味着如果另一个事务写入,您将不会看到该更改,直到它提交为止。这确实意味着如果该事务在您的事务期间提交,您可能会再次阅读并看到该值在您的事务期间发生了变化。显然这意味着数据库没有被锁定。
运行您的代码。您所做的任何查询/更改都不会提交。
提交事务。
重新启用自动提交。
在较早的原子块中
基本上在这种情况下,我们尝试使用保存点,这样如果我们“回滚”“事务”,我们就可以恢复到它们,但就数据库连接而言,我们处于同一事务中。
自动锁定
如前所述,数据库可能会为您的事务提供一些自动锁定,如 this doc 中所述。为了证明这一点,请考虑以下代码,该代码在一个 table 和其中一行的 postgres 数据库上运行:
my_table
id | age
---+----
1 | 50
然后你 运行 这个代码:
import psycopg2 as Database
from multiprocessing import Process
from time import sleep
from contextlib import contextmanager
@contextmanager
def connection():
conn = Database.connect(
user='daphtdazz', host='localhost', port=5432, database='db_test'
)
try:
yield conn
finally:
conn.close()
def connect_and_mutate_after_seconds(seconds, age):
with connection() as conn:
curs = conn.cursor()
print('execute update age to %d...' % (age,))
curs.execute('update my_table set age = %d where id = 1;' % (age,))
print('sleep after update age to %d...' % (age,))
sleep(seconds)
print('commit update age to %d...' % (age,))
conn.commit()
def dump_table():
with connection() as conn:
curs = conn.cursor()
curs.execute('select * from my_table;')
print('table: %s' % (curs.fetchall(),))
if __name__ == '__main__':
p1 = Process(target=connect_and_mutate_after_seconds, args=(2, 99))
p1.start()
sleep(0.6)
p2 = Process(target=connect_and_mutate_after_seconds, args=(1, 100))
p2.start()
p2.join()
dump_table()
p1.join()
dump_table()
你得到:
execute update age to 99...
sleep after update age to 99...
execute update age to 100...
commit update age to 99...
sleep after update age to 100...
commit update age to 100...
table: [(1, 100)]
table: [(1, 100)]
关键是第二个进程在第一个命令完成之前启动,但在它调用 update
命令之后,所以第二个进程必须等待锁定,这就是我们不这样做的原因直到 99 岁 commit
之后才看到 sleep after update age to 100
。
如果你把 sleep 放在 exec 之前,你会得到:
sleep before update age to 99...
sleep before update age to 100...
execute update age to 100...
commit update age to 100...
table: [(24, 3), (100, 2)]
execute update age to 99...
commit update age to 99...
table: [(24, 3), (99, 2)]
表示在第二个进程进行更新时未获取锁,这首先发生但在第一个进程的事务期间。
如@daphtdazz answer 所述,Django 在您打开事务时不会获取任何锁,但在您更新数据时,数据库可能会自动获取锁。锁的类型和范围取决于数据库,也可能取决于事务隔离级别。有关这些自动锁定的详细信息,请参阅您的数据库文档。
如果您想手动锁定,有几个选项。
主要且最简单的方法是执行 select_for_update()
查询。这将获取一个更新锁,该锁将阻止对与查询匹配的行的所有其他更新。这与更新事务中的行时自动获取的锁相同,但是 select_for_update()
允许您在实际进行更新之前获取更新锁,这通常很有用。
如果行锁定不适合您的情况,您可以在支持行锁定的数据库(例如 Postgres)中获取咨询锁。开箱即用,Django 不支持这一点,但是有第三方包为 Django 添加了对咨询锁的支持,或者您可以简单地发出适当的原始 SQL 查询。