web-dev-qa-db-ja.com

Django Atomic Transactionはデータベースをロックしますか?

あなたがするとき:

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

関数の実行中にデータベースをロックしますか?

Djangoのアトミックトランザクションについて質問しています: https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details

18
kong

(この回答では最新のSQLデータベースを想定しています)

tl; dr

トランザクションはロックではありませんが、操作中に自動的に取得されるロックを保持します。また、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_行をロックします。ただし、そのクエリまでロックされません。したがって、一貫性を確保したい場合は、明示的にロックを使用する必要があります。

取引

前述のように、トランザクションはロックではありません。パフォーマンスが低下するためです。一般に、それらは最初のインスタンスでは軽量のメカニズムであり、データベースの他のユーザーに対して一度に意味のない変更のロードを行った場合、それらの変更は一度にすべて発生するように見えます。つまりアトミックです。トランザクションは、他のユーザーによるデータベースの変更をブロックしません。実際、一般に、他のユーザーが読み取る可能性のある同じ行の変更をブロックしません。

トランザクションの保護方法の詳細については、 このガイド とデータベースのドキュメント(例 postgres )を参照してください。

DjangoのAtomicの実装。

atomicデコレータを使用すると、Django自体が次のことを行います( コード を参照)。

まだアトミックブロックにありません

  1. 自動コミットを無効にします。自動コミットは、常にトランザクションをすぐにコミットするアプリケーションレベルの機能であるため、未処理のトランザクションがないようにアプリケーションに見えます。

    これは、データベースに新しいトランザクションを開始するように指示します。

    • この時点で、postgresの_psycopg2_は、トランザクションの分離レベルを _READ COMMITTED_ に設定します。これは、トランザクションでの読み取りは、コミットされたデータのみを返すことを意味します。書き込み、コミットするまでその変更は表示されません。ただし、そのトランザクションがトランザクション中にコミットした場合、再度読み取って、トランザクション中に値が変更されたことを確認できます。

      明らかに、これはデータベースがロックされていないことを意味します。

  2. コードを実行します。クエリ/ミューテーションはコミットされません。

  3. トランザクションをコミットします。
  4. 自動コミットを再度有効にします。

以前のアトミックブロック

基本的にこの場合、「トランザクション」を「ロールバック」した場合に戻ることができるようにセーブポイントを使用しようとしますが、データベース接続に関する限り、同じトランザクション内にいます。

自動ロック

前述のように、データベースは this doc で概説されているように、トランザクションにいくつかの自動ロックを与える場合があります。これを実証するために、1つのテーブルと1つの行を含む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)]
_

ポイントは、最初のコマンドが完了する前に2番目のプロセスが開始されることですが、それがupdateコマンドを呼び出した後、2番目のプロセスはロックを待機する必要があるため、_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)]
_

2番目のプロセスが更新に到達するまでにロックが取得されなかったことを示します。更新は最初に行われますが、最初のプロセスのトランザクション中に行われます。

28
daphtdazz