当你这样做的时候:
@transaction.atomic
def update_db():
do_bulk_update()
当函数运行时,它是否锁定数据库?
我是问关于Django 的原子交易: https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details个
当你这样做的时候:
@transaction.atomic
def update_db():
do_bulk_update()
当函数运行时,它是否锁定数据库?
我是问关于Django 的原子交易: https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details个
(I'm assuming modern SQL databases in this answer.)个
事务不是锁,而是持有在操作期间自动获取的锁.默认情况下,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
行.但是在该查询之前它不会被锁定.因此,如果您希望确保一致性,则可能应该显式使用锁.
如前所述,事务不是锁,因为这对性能很糟糕.一般来说,它们首先是更轻量级的机制,用于确保如果您每次对数据库的其他用户进行一个都不合理的更改,那么这些更改似乎会同时发生.也就是说,它们是原子的.事务不会阻止其他用户更改数据库,实际上,一般来说,也不会阻止其他用户更改您可能正在读取的相同行.
有关如何保护事务的更多详细信息,请参阅this guide和您的数据库文档(例如postgres).
当您使用atomic
decorator (指the code)时,Django本身执行以下操作.
禁用自动提交.自动提交是一种应用程序级功能,它总是会立即提交事务,因此在应用程序看来,似乎从来没有未完成的事务.
这会告诉数据库开始一个新事务.
此时,Postgres的psycopg2
将事务的隔离级别设置为READ COMMITTED
,这意味着事务中的任何读取都将只返回已提交的数据,这意味着如果另一个事务写入,您在提交之前不会看到该更改.这确实意味着,如果事务在事务期间提交,您可能会再次阅读并看到该值在事务期间已更改.
显然,这意味着数据库没有锁定.
运行你的代码.您提出的任何查询/变更均未提交.
提交交易记录.
重新启用自动提交.
基本上,在这种情况下,我们try 使用保存点,以便在"回滚""事务"时可以恢复到它们,但就数据库连接而言,我们处于同一事务中.
如前所述,数据库可能会给事务提供一些自动锁,如this doc中所述.为了演示这一点,请考虑在PASGRESS数据库上操作的以下代码,其中一个表和一行在其中:
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
命令之后,所以第二个进程必须等待锁定,这就是为什么我们在commit
之后才能看到sleep after update age to 100
,因为99岁.
如果你把睡眠放在高管之前,你会得到:
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)]
表示在第二个进程进行更新时未获得锁,更新首先发生,但在第一个进程的事务中发生.