Locks¶
The following can be imported from django_mysql.locks
.
- class django_mysql.locks.Lock(name, acquire_timeout=10.0, using=None)[source]¶
MySQL can act as a locking server for arbitrary named locks (created on the fly) via its
GET_LOCK
function - sometimes called ‘User Locks’ since they are user-specific, and don’t lock tables or rows. They can be useful for your code to limit its access to some shared resource.This class implements a user lock and acts as either a context manager (recommended), or a plain object with
acquire
andrelease
methods similar tothreading.Lock
. These call the MySQL functionsGET_LOCK
,RELEASE_LOCK
, andIS_USED_LOCK
to manage it.The lock is only re-entrant (acquirable multiple times) on MariaDB.
Basic usage:
from django_mysql.exceptions import TimeoutError from django_mysql.locks import Lock try: with Lock("my_unique_name", acquire_timeout=2.0): mutually_exclusive_process() except TimeoutError: print("Could not get the lock")
For more information on user locks refer to the
GET_LOCK
documentation on MySQL or MariaDB.Warning
As the documentation warns, user locks are unsafe to use if you have replication running and your replication format (
binlog_format
) is set toSTATEMENT
. Most environments havebinlog_format
set toMIXED
because it can be more performant, but do check.- name¶
This is a required argument.
Specifies the name of the lock. Since user locks share a global namespace on the MySQL server, it will automatically be prefixed with the name of the database you use in your connection from DATABASES and a full stop, in case multiple apps are using different databases on the same server.
MySQL enforces a maximum length on the total name (including the DB prefix that Django-MySQL adds) of 64 characters. MariaDB doesn’t enforce any limit. The practical limit on MariaDB is maybe 1 million characters or more, so most sane uses should be fine.
- acquire_timeout=10.0
The time in seconds to wait to acquire the lock, as will be passed to
GET_LOCK()
. Defaults to 10 seconds.
- using=None
The connection alias from
DATABASES
to use. Defaults to Django’sDEFAULT_DB_ALIAS
to use your main database connection.
- is_held()[source]¶
Returns True iff a query to
IS_USED_LOCK()
reveals that this lock is currently held.
- holding_connection_id()[source]¶
Returns the MySQL
CONNECTION_ID()
of the holder of the lock, orNone
if it is not currently held.
- acquire()[source]¶
For using the lock as a plain object rather than a context manager, similar to
threading.Lock.acquire
. Note you should normally usetry
/finally
to ensure unlocking occurs.Example usage:
from django_mysql.locks import Lock lock = Lock("my_unique_name") lock.acquire() try: mutually_exclusive_process() finally: lock.release()
- release()[source]¶
Also for using the lock as a plain object rather than a context manager, similar to
threading.Lock.release
. For example, see above.
- classmethod held_with_prefix(prefix, using=DEFAULT_DB_ALIAS)[source]¶
Queries the held locks that match the given prefix, for the given database connection. Returns a dict of lock names to the
CONNECTION_ID()
that holds the given lock.Example usage:
>>> Lock.held_with_prefix("Author") {'Author.1': 451, 'Author.2': 457}
Note
Works with MariaDB 10.0.7+ only, when the
metadata_lock_info
plugin is loaded. You can install this in a migration using theInstallSOName
operation, like so:from django.db import migrations from django_mysql.operations import InstallSOName class Migration(migrations.Migration): dependencies = [] operations = [ # Install https://mariadb.com/kb/en/mariadb/metadata_lock_info/ InstallSOName("metadata_lock_info") ]
- class django_mysql.locks.TableLock(write=None, read=none, using=None)[source]¶
MySQL allows you to gain a table lock to prevent modifications to the data during reads or writes. Most applications don’t need to do this since transaction isolation should provide enough separation between operations, but occasionally this can be useful, especially in data migrations or if you are using a non-transactional storage such as MyISAM.
This class implements table locking and acts as either a context manager (recommended), or a plain object with
acquire()
andrelease()
methods similar tothreading.Lock
. It uses the transactional pattern from the MySQL manual to ensure all the necessary steps are taken to lock tables properly. Note that locking has no timeout and blocks until held.Basic usage:
from django_mysql.locks import TableLock with TableLock(read=[MyModel1], write=[MyModel2]): fix_bad_instances_of_my_model2_using_my_model1_data()
- read¶
A list of models or raw table names to lock at the
READ
level. Any models using multi-table inheritance will also lock their parents.
- write¶
A list of models or raw table names to lock at the
WRITE
level. Any models using multi-table inheritance will also lock their parents.
- using=None
The connection alias from
DATABASES
to use. Defaults to Django’sDEFAULT_DB_ALIAS
to use your main database connection.
- acquire()[source]¶
For using the lock as a plain object rather than a context manager, similar to
threading.Lock.acquire
. Note you should normally usetry
/finally
to ensure unlocking occurs.Example usage:
from django_mysql.locks import TableLock table_lock = TableLock(read=[MyModel1], write=[MyModel2]) table_lock.acquire() try: fix_bad_instances_of_my_model2_using_my_model1_data() finally: table_lock.release()
- release()[source]¶
Also for using the lock as a plain object rather than a context manager, similar to
threading.Lock.release
. For example, see above.
Note
Transactions are not allowed around table locks, and an error will be raised if you try and use one inside of a transaction. A transaction is created to hold the locks in order to cooperate with InnoDB. There are a number of things you can’t do whilst holding a table lock, for example accessing tables other than those you have locked - see the MySQL/MariaDB documentation for more details.
Note
Table locking works on InnoDB tables only if the
innodb_table_locks
is set to 1. This is the default, but may have been changed for your environment.