(我在这个答案中假设现代 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 和您的数据库文档(例如 postgres)。
原子的Django实现。
当您使用atomic 装饰器时,Django 本身会执行以下操作(参考the code)。
尚未在原子块中
-
禁用自动提交。自动提交是一个应用程序级别的功能,它总是会立即提交事务,所以它看起来就像没有一个未完成的事务一样。
这告诉数据库开始一个新事务。
-
运行您的代码。您所做的任何查询/突变都不会提交。
-
提交事务。
-
重新启用自动提交。
在较早的原子块中
基本上在这种情况下,我们尝试使用保存点,以便在“回滚”“事务”时可以恢复到它们,但就数据库连接而言,我们处于同一个事务中。
自动锁定
如前所述,数据库可能会为您的事务提供一些自动锁定,如this doc 中所述。为了证明这一点,请考虑以下代码,它在一个包含一个表和一个行的 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 命令之后,所以第二个进程必须等待锁定,这就是为什么我们看不到@987654335 @ 直到 99 岁的 commit 之后。
如果你把 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)]
指示在第二个进程进行更新时未获得锁,更新发生在第一个进程的事务期间。