Does Django Atomic Transaction write a database?

When you do:

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

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

I ask for the django nuclear transaction: https://docs.djangoproject.com/en/1.10/topics/db/transactions/#autocommit-details

+6
source share
1 answer

(I accept modern SQL databases in this answer.)

TL; DR

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

eg. if you did:

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

You have locked the app_model line with identifier 1 for all of the "other stuff". But it is not blocked until this request. Therefore, if you want to ensure consistency, you must explicitly use locks explicitly.

Deals

As said, transactions are not locks because it would be horrible for performance. In general, they are primarily mechanisms with a lighter weight to ensure that if you make a load of changes that will not make sense one at a time for other database users, these changes appear to occur immediately. That is, they are atomic. Transactions do not block other users from mutating the database, and generally do not block other users from mutating the same lines that you can read.

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

Django atom implementation.

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

Not in the atomic block

  • Disables auto messaging. Autocommit is an application-level function that will always commit transactions immediately, so it accesses the application as if the transaction were not executing.

    This informs the database of the start of a new transaction.

    • At this point, psycopg2 for postgres sets the transaction isolation level to READ COMMITTED , which means that any read in the transaction will only return committed data, which means that if another transaction is written, you will not see this change until it completes it. This means that if the transaction is completed during the transaction, you can read it again and see that the value changed during the transaction.

      Obviously, this means that the database is not locked.

  • Runs your code. Any requests / mutations you make are not executed.

  • Performs a transaction.
  • Reenables auto message.

In an earlier atomic block

Basically, in this case we are trying to use savepoints so that we can return to if we "roll back" from the "transaction", but as for connecting to the database, we are in one transaction.

Automatic blocking

As said, the database may provide your transaction with some automatic locks, as indicated in this document . To demonstrate this, consider the following code that works in a postgres database with one table and one row in it:

 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 are getting:

 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 fact is that the second process starts before the completion of the first command, but after it called the update command, so the second process should wait for the lock, so we do not see sleep after update age to 100 until there is a commit in 99 years old.

If you put sleep before exec, you will 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)] 

The lock instruction was not received at the moment when the second process proceeds to update it, which happens first, but during the first transaction of the process.

+2
source

Source: https://habr.com/ru/post/1015354/


All Articles