Chapter 4. Transactions and Locks

The SQL-99 specification defines transaction in this way:

"An SQL-transaction (transaction) is a sequence of executions of SQL-statements that is atomic with respect to recovery. That is to say: either the execution result is completely successful, or it has no effect on any SQL-schemas or SQL-data.

The SQL Standard"

So, a transaction is an important feature that guarantees the integrity of data. It is useful to prevent the data from remaining in an inconsistent state when an operation fails. Suppose, for example, we are administering a database for an e-commerce website. We have several tables; one of them stores the orders, and the others store the available products and their quantity. When a customer buys a product, a new order must be written in the appropriate table and the quantity of that product must decrease. These operations must occur within the same transaction. In this way, if a server crashes after the order has been written but before the product's quantity decreases, the whole transaction never becomes effective. Only, if all the operations succeed, will the transaction be finalized.

But, transactions have another important property: they are isolated from each other. Many isolation levels exist, and each one implements different types of isolation. Anyway, the idea is that the operations performed by one transaction should never interfere with concurrent transactions. Let's again consider the example of the e-commerce website. When a customer buys a product, the application checks whether the desired product is available and then decreases its quantity. This happens within a transaction and avoids malfunctioning if two customers try to buy the same product at the same time. One of the customers will begin a transaction and lock the table row corresponding to the product he/she wants to buy. The second customer will have to wait until the first transaction ends. In this way, if the first customer buys the last stocked product, the second customer will not buy a "phantom" product. In MariaDB, transactions may only be used on storage engines that support them, such as InnoDB, TokuDB, and SPIDER.

The basic mechanisms that allow storage engines to guarantee data consistency are locks. We will analyze them in this chapter.

We will also discuss some aspects of the ALTER TABLE statement, which can lock big tables for a long period of time.

The InnoDB locks

A lock is a data structure that is acquired by a user and associated to a resource. Until the lock is held, other users will not be able to modify that resource or, depending on the lock type, they would not be able to read it. Typically, concurrent operations will be queued. InnoDB can lock rows and entire tables to prevent the concurrent operations from colliding.

In order to understand how the InnoDB locks work, it is necessary to understand how the concurrent transactions work. Also, this allows us to diagnose and fix problems, such as the transactions that have to wait for too long or the frequent deadlocks between the transactions.

When a transaction needs to access a row that is locked, it is put on hold until the transaction that holds the lock commits or rolls back. The wait has a limit, which is determined by the innodb_lock_wait_timeout server variable, expressed in seconds. The default value is 50 and can usually be decreased. If this timeout exceeds, the transaction terminates and an error is produced, shown as follows:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

The lock modes

InnoDB has two main lock modes: shared and exclusive. A shared lock prevents other connections from writing to a row, but allows them to read it. An exclusive lock prevents other connections from reading a row; the isolation level (explained in the Transactions isolation levels section) determines whether other transactions can write in the row. Shared locks are acquired before reading a row, and exclusive locks are acquired before writing a row. Before a transaction can acquire a shared or exclusive lock, it needs to acquire an intention shared lock or an intention exclusive lock. In other words, an intention lock indicates that a transaction is waiting to acquire a shared or exclusive lock, and prevents two connections from acquiring a lock on the same rows.

Shared and exclusive locks are commonly abbreviated as S and X. Intention shared locks and intention exclusive locks are abbreviated as IS and IX.

A lock on a record can only be acquired if an incompatible lock does not already involve the same record. If such a lock exists, the connection is put on hold. The following table shows which lock modes block the other lock modes:

Lock mode

Lock modes that will be blocked

X

X, S, IX, IS

S

X, IS

IX

X, S

IS

X

Lock types

InnoDB supports both table-level locks and record-level locks.

Note

Table-level locks lock a whole table. They can be acquired explicitly with LOCK TABLES, but this is not good practice with InnoDB and it is normally disabled. To enable this locking mode with InnoDB, the innodb_table_locks server variable can be set to ON. The LOCK TABLES command is designed to guarantee the consistency of non-transactional tables, such as Aria and MyISAM. Its performance is poor, because it locks a whole table, not just the rows involved in a transaction. Note that some information_schema InnoDB-specific tables (that are discussed in the Diagnosing locks section) store information about the existing locks, but they do not contain information about the locks that are created using the LOCK TABLES command.

Record-level locks lock one or more records. InnoDB has the following types of record-level locks: record locks, gap locks, and next-key locks.

  • A record lock involves a single index record. If no index has been explicitly created for the table, record locks involve records in the clustered index (which is described in the previous chapter).
  • A next-key lock involves an index record and all the records that precede it. This prevents other connections from inserting or modifying the records that have already been accessed by the current transaction. Next-key locks are used with the REPEATABLE READ isolation level.
  • A gap lock is a lock on a set of records. This set can be composed by one record, multiple records, or it can be empty. This lock type is not used for a single row search on a UNIQUE index, unless the index is composed of multiple columns.

A particular case of gap lock is the insertion gap lock. It is acquired before the insertion of a new row. If another connection tries to insert a row with the same index values, it will be put on hold until the current transaction commits or rolls back the changes.

Gap locks are not used with the READ COMMITTED isolation level.

Diagnosing locks

While locks are necessary to guarantee the consistency of data, they can cause performance problems. Each lock can cause one or more sessions to wait, slowing down the applications. If sessions have to wait too long or too often, we have to find out the reason and fix the problem.

The SHOW ENGINE INNODB STATUS statement is usually the fastest way to get human-readable information about locks. Its output is quite long, but fortunately it is human-readable, friendly for regular expressions, and divided into some useful sections. The section which lists existing locks is TRANSACTIONS. Here is an example:

------------ 
TRANSACTIONS 
------------ 
Trx id counter 14488 
Purge done for trx's n:o < 14477 undo n:o < 0 state: running but idle 
History list length 230 
LIST OF TRANSACTIONS FOR EACH SESSION: 

---TRANSACTION 14509, ACTIVE 3 sec inserting 
mysql tables in use 1, locked 1 
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1 
MySQL thread id 5, OS thread handle 0x7f5d48554700, query id 161 localhost root update 
INSERT INTO t VALUES (1) 
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 48 page no 4 n bits 72 index `a` of table `test`.`t` trx id 14509 lock mode S waiting 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 
 0: len 4; hex 80000001; asc     ;; 
 1: len 6; hex 000000000601; asc       ;; 
------------------ 
---TRANSACTION 14486, ACTIVE 9 sec 
2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1 
MySQL thread id 4, OS thread handle 0x7f5d4859d700, query id 154 localhost root cleaning up

For each transaction, an ID is shown. The last listed transaction has been active for nine seconds. We know its thread ID is 4, which is important to debug. By executing SHOW PROCESSLIST, we can find out what thread 4 is doing. The operating system thread ID is also shown, in the hexadecimal format. It can be converted to a decimal number by using the UNHEX() function. We know it holds a lock on 1 row.

Another transaction is shown in this example. It has been active for three seconds. The transaction has been blocked for three seconds from inserting a row by the existing lock.

To get more specific information about one or more locks, the INNODB_LOCKS table in the information_schema database can be queried. Note that locks appear in that table only if they are blocking a transaction. The table consists of the following columns:

  • LOCK_ID: This is a string ID. Its format may change in the future.
  • LOCK_TRX_ID: This is the transaction ID that appears as a result of SHOW ENGINE INNODB STATUS.
  • LOCK_MODE: These are the possible values: S, X, IS, IX; for gap locks: S_GAP, X_GAP, IS_GAP, IX_GAP; for autoincrement locks: AUTO_INC.
  • LOCK_TYPE: This column shows whether the lock type is RECORD or TABLE.
  • LOCK_TABLE: This is the name of the table that is involved by the lock. It includes the database name and the table name, both quoted.
  • LOCK_INDEX: For record locks, this column shows the name of the index whose record is locked.
  • LOCK_SPACE: For record locks, this column shows the name of the tablespace involved by the lock. The INNODB_SYS_TABLESPACES table shows which table this tablespace belongs to.
  • LOCK_PAGE: For record locks, this is the page number.
  • LOCK_REC: For record locks, this is the record number within the page.
  • LOCK_DATA: For record locks, this is the clustered index value for the locked record.

The INNODB_LOCK_WAITS table shows which transactions are waiting to obtain a lock, and which transactions are holding the locks which are blocking them. Its columns are:

  • REQUESTING_TRX_ID: The ID of the waiting transaction
  • REQUESTED_LOCK_ID: The ID of the requested lock
  • BLOCKING_TRX_ID: The ID of the blocking transaction
  • BLOCKING_LOCK_ID: The ID of the lock that is blocking the requesting transaction

The INNODB_TRX table holds information about a transaction's activities.

The following example shows a JOIN query between INNODB_LOCKS and INNODB_LOCK_WAITS, which shows all the transactions that are blocking another transaction, as well as the detailed information about the blocking lock:

MariaDB [test]> SELECT * FROM information_schema.INNODB_LOCK_WAITS lw LEFT JOIN information_schema.INNODB_LOCKS l ON lw.BLOCKING_LOCK_ID = l.LOCK_ID G 
*************************** 1. row *************************** 
requesting_trx_id: 14512 
requested_lock_id: 14512:48:4:2 
  blocking_trx_id: 14486 
 blocking_lock_id: 14486:48:4:2 
          lock_id: 14486:48:4:2 
      lock_trx_id: 14486 
        lock_mode: X 
        lock_type: RECORD 
       lock_table: `test`.`t` 
       lock_index: a 
       lock_space: 48 
        lock_page: 4 
         lock_rec: 2 
        lock_data: 1 
1 row in set (0.00 sec) 

Note that information_schema related to InnoDB does not show information about the locks obtained using LOCK TABLES. The reason is that those locks are handled by the MariaDB server itself, not by the InnoDB storage engine.

Locks used by various SQL statements

The UPDATE and DELETE statements lock all scanned index records, including the ones that do not satisfy the WHERE condition. Locking reads can free the locks on the index records that do not satisfy a WHERE condition but sometimes this cannot be done.

Note

When a statement which modifies data does not use any index, all the rows in the table need to be locked. The same happens with locking using the SELECT statement.

The UPDATE and DELETE statements acquire an exclusive next-key lock for each scanned record.

The INSERT statement acquires an insertion gap lock on the index values it is going to insert. Transactions that insert different values do not need to wait for each other. After the insertion, an exclusive record lock is placed on all the new rows until the end of the transaction. If a duplicate error occurs, normally the transaction does not rollback and a shared record lock is placed on the existing record. If the ON DUPLICATE KEY clause is present, an exclusive next-key lock is put on the existing record.

The INSERT … SELECT statement is similar to the INSERT statement and is used for the inserted tables, except that no insertion gap lock is set. With the READ COMMITTED isolation level, the SELECT part of the statement performs a consistent read. Otherwise, shared next-key locks are set on the read rows.

Foreign keys need to read some rows to guarantee that the integrity constraints are enforced. On each read record, a shared record lock is acquired.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.21.47