Locking

Shared and Exclusive Locks

The X DevAPI supports locking matching rows, for the mysqlx.Collection.find() and mysqlx.Table.select() methods, which allows safe and transactional document/row updates on collections or tables.

There are two types of locks:

Examples

Setup

Assuming the existence of test_schema.test_collection collection.

[{
    "_id": "1",
    "name": "Fred",
    "age": 21
 },{
    "_id": "2",
    "name": "Sakila",
    "age": 23
 },{
    "_id": "3",
    "name": "Mike",
    "age": 42
}]

Get the session and collection objects.

# client 1
session_1 = mysqlx.get_session("root:@localhost:33060")
schema_1 = session_1.get_schema("test_schema")
collection_1 = schema_1.get_collection("test_collection")

# client 2
session_2 = mysqlx.get_session("root:@localhost:33060")
schema_2 = session_2.get_schema("test_schema")
collection_2 = schema_2.get_collection("test_collection")

Shared lock

# client 1
session_1.start_transaction()
collection_1.find("_id = '1'").lock_shared().execute()

# client 2
session_2.start_transaction()
collection_2.find("_id = '2'").lock_shared().execute()  # should return immediately
collection_2.find("_id = '1'").lock_shared().execute()  # should return immediately

# client 1
session_1.rollback()

# client 2
session_2.rollback()

Exclusive Lock

# client 1
session_1.start_transaction()
collection_1.find("_id = '1'").lock_exclusive().execute()

# client 2
session_2.start_transaction()
collection_2.find("_id = '2'").lock_exclusive().execute()  # should return immediately
collection_2.find("_id = '1'").lock_exclusive().execute()  # session_2 should block

# client 1
session_1.rollback()  # session_2 should unblock now

# client 2
session_2.rollback()

Shared Lock after Exclusive

# client 1
session_1.start_transaction()
collection_1.find("_id = '1'").lock_exclusive().execute()

# client 2
session_2.start_transaction()
collection_2.find("_id = '2'").lock_shared().execute()  # should return immediately
collection_2.find("_id = '1'").lock_shared().execute()  # session_2 blocks

# client 1
session_1.rollback()  # session_2 should unblock now

# client 2
session_2.rollback()

Exclusive Lock after Shared

# client 1
session_1.start_transaction()
collection_1.find("_id in ('1', '3')").lock_shared().execute()

# client 2
session_2.start_transaction()
collection_2.find("_id = '2'").lock_exclusive().execute()  # should return immediately
collection_2.find("_id = '3'").lock_shared().execute()     # should return immediately
collection_2.find("_id = '1'").lock_exclusive().execute()  # session_2 should block

# client 1
session_1.rollback()  # session_2 should unblock now

# client 2
session_2.rollback()

Locking with NOWAIT and SKIP_LOCKED

If a row is locked by a transaction, a transaction that requests the same locked row must wait until the blocking transaction releases the row lock. However, waiting for a row lock to be released is not necessary if you want the query to return immediately when a requested row is locked, or if excluding locked rows from the result set is acceptable.

To avoid waiting for other transactions to release row locks, mysqlx.LockContention.NOWAIT and mysqlx.LockContention.SKIP_LOCKED lock contentions options may be used.

NOWAIT

A locking read that uses mysqlx.LockContention.NOWAIT never waits to acquire a row lock. The query executes immediately, failing with an error if a requested row is locked.

Example of reading a share locked document using mysqlx.ReadStatement.lock_shared():

# client 1
session_1.start_transaction()
collection_1.find("_id = :id").lock_shared().bind("id", "1").execute()

# client 2
session_2.start_transaction()
collection_2.find("_id = :id").lock_shared(mysqlx.LockContention.NOWAIT) \
            .bind("id", "1").execute()
# The execution should return immediately, no block and no error is thrown

collection_2.modify("_id = '1'").set("age", 43).execute()
# The transaction should be blocked

# client 1
session_1.commit()
# session_2 should unblock now

# client 2
session_2.rollback()

SKIP_LOCKED

A locking read that uses mysqlx.LockContention.SKIP_LOCKED never waits to acquire a row lock. The query executes immediately, removing locked rows from the result set.

Example of reading a share locked document using mysqlx.ReadStatement.lock_exclusive():

# client 1
session_1.start_transaction()
collection_1.find("_id = :id").lock_shared().bind("id", "1").execute()

# client 2
session_2.start_transaction()
collection_2.find("_id = :id").lock_exclusive(mysqlx.LockContention.SKIP_LOCKED) \
            .bind("id", "1").execute()
# The execution should return immediately, no error is thrown

# client 1
session_1.commit()

# client 2
collection_2.find("_id = :id").lock_exclusive(mysqlx.LockContention.SKIP_LOCKED) \
            .bind("id", 1).execute()
# Since commit is done in 'client 1' then the read must be possible now and
# no error is thrown
session_2.rollback()