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_LOCKfunction - 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
acquireandreleasemethods similar tothreading.Lock. These call the MySQL functionsGET_LOCK,RELEASE_LOCK, andIS_USED_LOCKto 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_LOCKdocumentation 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_formatset toMIXEDbecause 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
DATABASESto use. Defaults to Django’sDEFAULT_DB_ALIASto 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, orNoneif 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/finallyto 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_infoplugin is loaded. You can install this in a migration using theInstallSONameoperation, 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
READlevel. Any models using multi-table inheritance will also lock their parents.
- write¶
A list of models or raw table names to lock at the
WRITElevel. Any models using multi-table inheritance will also lock their parents.
- using=None
The connection alias from
DATABASESto use. Defaults to Django’sDEFAULT_DB_ALIASto 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/finallyto 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_locksis set to 1. This is the default, but may have been changed for your environment.