Django原子事务锁住数据库吗?

时间:2022-11-17 18:03:02

When you do:

当你做的事:

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

while the function is running, does it lock the database?

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

I'm asking regarding django's atomic transaction: https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details

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

1 个解决方案

#1


4  

(I'm assuming modern SQL databases in this answer.)

(我假设这里是现代SQL数据库。)

tl;dr

Transactions are not locks, but hold locks that are acquired automatically during operations. And django does not add any locking by default, so the answer is No, it does not lock the database.

事务不是锁,而是在操作过程中自动获取的锁。django默认不添加任何锁定,所以答案是No,它不会锁定数据库。

E.g. if you were do:

例如:if you were to do:

@transaction.atomic
def update_db():
    cursor.execute('UPDATE app_model SET model_name TO 'bob' WHERE model_id = 1;')
    # some other stuff...

You will have locked the app_model row with id 1 for the duration of "other stuff". But it is not locked until that query. So if you want to ensure consistency you should probably use locks explicitly.

您将在“其他内容”期间将app_model行与id 1锁在一起。但是它直到那个查询才被锁定。所以如果你想确保一致性,你应该明确地使用锁。

Transactions

As said, transactions are not locks because that would be awful for perfomance. In general they are lighter-weight mechanisms in the first instance for ensuring that if you make a load of changes that wouldn't make sense one at a time to other users of the database, those changes appear to happen all at once. I.e. are atomic. Transactions do not block other users from mutating the database, and indeed in general do not block other users from mutation the same rows you may be reading.

如前所述,事务不是锁,因为这对性能非常不利。通常,在第一个实例中,它们是轻量级的机制,用于确保如果您一次对数据库的其他用户进行大量的更改,而这些更改似乎是同时发生的。即是原子。事务不会阻止其他用户改变数据库,而且通常也不会阻止其他用户改变您可能正在读取的相同的行。

See this guide and your databases docs (e.g. postgres) for more details on how transactions are protected.

有关如何保护事务的详细信息,请参阅本指南和您的数据库文档(例如postgres)。

Django implementation of atomic.

Django itself does the following when you use the atomic decorator (referring to the code).

当您使用原子修饰符(引用代码)时,Django本身将执行以下操作。

Not already in an atomic block

  1. Disables autocommit. Autocommit is an application level feature which will always commit transactions immediately, so it looks to the application like there is never a transaction outstanding.

    禁用自动提交。Autocommit是一个应用程序级别的特性,它总是会立即提交事务,因此在应用程序看来,从来没有事务未处理过。

    This tells the database to start a new transaction.

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

    • At this point psycopg2 for postgres sets the isolation level of the transaction to READ COMMITTED, which means that any reads in the transaction will only return committed data, which means if another transaction writes, you won't see that change until it commits it. It does mean though that if that transaction commits during your transaction, you may read again and see that the value has changed during your transaction.

      此时,postgres的psycopg2设置了要读取提交的事务的隔离级别,这意味着事务中的任何读取都将只返回提交的数据,这意味着如果另一个事务写,在提交它之前,您不会看到该更改。这确实意味着,如果该事务在事务期间提交,您可以再次读取,并看到值在事务期间发生了更改。

      Obviously this means that the database is not locked.

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

  2. Runs your code. Any queries / mutations you make are not committed.

    运行您的代码。您所做的任何查询/突变都没有提交。

  3. Commits the transaction.
  4. 提交事务。
  5. Re-enables autocommit.
  6. 还可以重新开启自动提交。

In an earlier atomic block

Basically in this case we try to use savepoints so we can revert back to the if we "rollback" the "transaction", but as far as the database connection is concerned we are in the same transaction.

基本上,在这种情况下,我们尝试使用保存点,这样我们就可以返回到if我们“回滚”“事务”,但是就数据库连接而言,我们是在同一个事务中。

Automatic locking

As said, the database may give your transaction some automatic locks, as outlined in this doc. To demonstrate this, consider the following code that operates on a postgres database with one table and one row in it:

如前所述,数据库可能会为您的事务提供一些自动锁,如文档中所述。要演示这一点,请考虑以下代码,该代码在一个包含一个表和一行的postgres数据库上运行:

my_table
id | age
---+----
1  | 50

And then you run this code:

然后你运行这个代码:

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()

You get:

你会得到:

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)]

and the point is that the second process is started before the first command completes, but after it has called the update command, so the second process has to wait for the lock which is why we don't see sleep after update age to 100 until after the commit for age 99.

,重点是第二个过程开始之前,第一个命令完成后,但后更新命令,所以第二个进程必须等待锁,这就是为什么我们没有看到更新后睡眠时代到100年99岁之前提交。

If you put the sleep before the exec, you get:

如果你把睡眠放在执行前,你会得到:

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)]

Indicating the lock was not acquired by the time the second process gets to its update, which happens first but during the first process's transaction.

指示在第二个进程进行更新时没有获取锁,更新发生在第一个进程的事务期间。

#1


4  

(I'm assuming modern SQL databases in this answer.)

(我假设这里是现代SQL数据库。)

tl;dr

Transactions are not locks, but hold locks that are acquired automatically during operations. And django does not add any locking by default, so the answer is No, it does not lock the database.

事务不是锁,而是在操作过程中自动获取的锁。django默认不添加任何锁定,所以答案是No,它不会锁定数据库。

E.g. if you were do:

例如:if you were to do:

@transaction.atomic
def update_db():
    cursor.execute('UPDATE app_model SET model_name TO 'bob' WHERE model_id = 1;')
    # some other stuff...

You will have locked the app_model row with id 1 for the duration of "other stuff". But it is not locked until that query. So if you want to ensure consistency you should probably use locks explicitly.

您将在“其他内容”期间将app_model行与id 1锁在一起。但是它直到那个查询才被锁定。所以如果你想确保一致性,你应该明确地使用锁。

Transactions

As said, transactions are not locks because that would be awful for perfomance. In general they are lighter-weight mechanisms in the first instance for ensuring that if you make a load of changes that wouldn't make sense one at a time to other users of the database, those changes appear to happen all at once. I.e. are atomic. Transactions do not block other users from mutating the database, and indeed in general do not block other users from mutation the same rows you may be reading.

如前所述,事务不是锁,因为这对性能非常不利。通常,在第一个实例中,它们是轻量级的机制,用于确保如果您一次对数据库的其他用户进行大量的更改,而这些更改似乎是同时发生的。即是原子。事务不会阻止其他用户改变数据库,而且通常也不会阻止其他用户改变您可能正在读取的相同的行。

See this guide and your databases docs (e.g. postgres) for more details on how transactions are protected.

有关如何保护事务的详细信息,请参阅本指南和您的数据库文档(例如postgres)。

Django implementation of atomic.

Django itself does the following when you use the atomic decorator (referring to the code).

当您使用原子修饰符(引用代码)时,Django本身将执行以下操作。

Not already in an atomic block

  1. Disables autocommit. Autocommit is an application level feature which will always commit transactions immediately, so it looks to the application like there is never a transaction outstanding.

    禁用自动提交。Autocommit是一个应用程序级别的特性,它总是会立即提交事务,因此在应用程序看来,从来没有事务未处理过。

    This tells the database to start a new transaction.

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

    • At this point psycopg2 for postgres sets the isolation level of the transaction to READ COMMITTED, which means that any reads in the transaction will only return committed data, which means if another transaction writes, you won't see that change until it commits it. It does mean though that if that transaction commits during your transaction, you may read again and see that the value has changed during your transaction.

      此时,postgres的psycopg2设置了要读取提交的事务的隔离级别,这意味着事务中的任何读取都将只返回提交的数据,这意味着如果另一个事务写,在提交它之前,您不会看到该更改。这确实意味着,如果该事务在事务期间提交,您可以再次读取,并看到值在事务期间发生了更改。

      Obviously this means that the database is not locked.

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

  2. Runs your code. Any queries / mutations you make are not committed.

    运行您的代码。您所做的任何查询/突变都没有提交。

  3. Commits the transaction.
  4. 提交事务。
  5. Re-enables autocommit.
  6. 还可以重新开启自动提交。

In an earlier atomic block

Basically in this case we try to use savepoints so we can revert back to the if we "rollback" the "transaction", but as far as the database connection is concerned we are in the same transaction.

基本上,在这种情况下,我们尝试使用保存点,这样我们就可以返回到if我们“回滚”“事务”,但是就数据库连接而言,我们是在同一个事务中。

Automatic locking

As said, the database may give your transaction some automatic locks, as outlined in this doc. To demonstrate this, consider the following code that operates on a postgres database with one table and one row in it:

如前所述,数据库可能会为您的事务提供一些自动锁,如文档中所述。要演示这一点,请考虑以下代码,该代码在一个包含一个表和一行的postgres数据库上运行:

my_table
id | age
---+----
1  | 50

And then you run this code:

然后你运行这个代码:

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()

You get:

你会得到:

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)]

and the point is that the second process is started before the first command completes, but after it has called the update command, so the second process has to wait for the lock which is why we don't see sleep after update age to 100 until after the commit for age 99.

,重点是第二个过程开始之前,第一个命令完成后,但后更新命令,所以第二个进程必须等待锁,这就是为什么我们没有看到更新后睡眠时代到100年99岁之前提交。

If you put the sleep before the exec, you get:

如果你把睡眠放在执行前,你会得到:

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)]

Indicating the lock was not acquired by the time the second process gets to its update, which happens first but during the first process's transaction.

指示在第二个进程进行更新时没有获取锁,更新发生在第一个进程的事务期间。