当你这样做的时候:

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

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行.但是在该查询之前它不会被锁定.因此,如果您希望确保一致性,则可能应该显式使用锁.

交易

如前所述,事务不是锁,因为这对性能很糟糕.一般来说,它们首先是更轻量级的机制,用于确保如果您每次对数据库的其他用户进行一个都不合理的更改,那么这些更改似乎会同时发生.也就是说,它们是原子的.事务不会阻止其他用户更改数据库,实际上,一般来说,也不会阻止其他用户更改您可能正在读取的相同行.

有关如何保护事务的更多详细信息,请参阅this guide和您的数据库文档(例如postgres).

Django实现了原子.

当您使用atomicdecorator (指the code)时,Django本身执行以下操作.

还没有在原子块中

  1. 禁用自动提交.自动提交是一种应用程序级功能,它总是会立即提交事务,因此在应用程序看来,似乎从来没有未完成的事务.

    这会告诉数据库开始一个新事务.

    • 此时,Postgres的psycopg2将事务的隔离级别设置为READ COMMITTED,这意味着事务中的任何读取都将只返回已提交的数据,这意味着如果另一个事务写入,您在提交之前不会看到该更改.这确实意味着,如果事务在事务期间提交,您可能会再次阅读并看到该值在事务期间已更改.

      显然,这意味着数据库没有锁定.

  2. 运行你的代码.您提出的任何查询/变更均未提交.

  3. 提交交易记录.

  4. 重新启用自动提交.

在更早的原子挡路中

基本上,在这种情况下,我们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)]

表示在第二个进程进行更新时未获得锁,更新首先发生,但在第一个进程的事务中发生.

Database相关问答推荐

使用 prisma ORM 在我的迁移中手动添加触发器

聚合以过滤 MongoDB 中的引用

如何将表字段的默认值设置为 0.00?

数据库 struct 建议

Oracle SQL 开发人员中的 DB2 数据库

如何禁用 Django 查询缓存?

当可伸缩性无关紧要时,NoSQL 与 SQL

一个 5MB 的 SQL 数据库可以存储多少数据?

无法在 MYSQL 5.5 w/MYSQL Workbench 中更改模式名称

如何关闭 Rails 中的updated_at列?

Oracle在哪些情况下会自动创建索引?

在 iphone 上本地存储数据

在 MySQL 存储过程中使用if和else

如何将 SQL Server 数据库图表迁移到另一个数据库?

从 CSV 文件填充 Android 数据库?

MySQL 整数 0 与 NULL

多个和单个索引

SQL 截断数据库?如何截断所有表格

如何修复 Postgres 上过时的 postmaster.pid 文件?

Web 应用程序的文件存储:文件系统、数据库和 NoSQL 引擎