Django Atomic Transaction会锁定数据库吗?

时间:2017-02-28 23:59:05

标签: django database locking atomic

当你这样做时:

@transaction.atomic
def update_db():
    do_bulk_update()

在函数运行时,是否锁定数据库?

我问django的原子交易: https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details

2 个答案:

答案 0 :(得分:16)

(我在这个答案中假设现代SQL数据库。)

TL;博士

事务不是锁,而是保持在操作期间自动获取的锁。 django默认不添加任何锁定,所以答案是否,它不会锁定数据库。

E.g。如果你这样做:

@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实现原子。

当您使用atomic装饰器(指the code)时,Django会执行以下操作。

尚未存在于原子块中

  1. 禁用自动提交。 Autocommit是一个应用程序级别的功能,它将始终立即提交事务,因此它会向应用程序查找,因为从未有过未完成的事务。

    这告诉数据库启动一个新事务。

    • 此时,postgres的psycopg2将事务的隔离级别设置为READ COMMITTED,这意味着事务中的任何读取都只返回已提交的数据,这意味着如果另一个事务写入,在提交之前,你不会看到这种变化。这确实意味着,如果该交易在您的交易过程中提交,您可以再次阅读,并在交易过程中看到该值已发生变化。

      显然这意味着数据库未被锁定。

  2. 运行您的代码。您提出的任何查询/突变都不会被提交。

  3. 提交交易。
  4. 重新启用自动提交。
  5. 在早期的原子块中

    基本上在这种情况下我们尝试使用保存点,因此我们可以恢复到“如果我们”回滚“事务”,但就数据库连接而言,我们处于同一事务中。

    自动锁定

    如上所述,数据库可能会为您的事务提供一些自动锁定,如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命令之后,所以第二个进程必须等待锁定,这就是为什么我们看不到sleep after update age to 100直到99岁commit之后。

    如果你把睡眠放在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)]
    

    表示在第二个进程更新时未获取锁定,这种情况首先发生,但在第一个进程的事务处理期间发生。

答案 1 :(得分:0)

如@daphtdazz答案所述,当您打开事务时,Django不会获得任何锁,但是在更新数据时,数据库可能会获得自动锁。锁的类型和范围取决于数据库,并且还可能取决于事务隔离级别。有关这些自动锁的详细信息,请参阅数据库文档。

如果要手动锁定,有几种选择。

最主要和最简单的方法是进行select_for_update()查询。这将获取一个更新锁,该锁将阻止对与查询匹配的行的所有其他更新。这与在更新事务中的一行时自动获取的锁相同,但是select_for_update()允许您在实际进行更新之前获取更新锁,这通常很有用。

如果行锁不适合您的情况,则可以在支持行锁的数据库中获取咨询锁(例如Postgres)。开箱即用,Django不支持此功能,但是有第三方软件包为Django添加了对咨询锁的支持,或者您可以简单地发出适当的原始SQL查询。