Databases 13 min read

Understanding MySQL InnoDB Locks, Latches, and Intention Locks with Source‑Code Analysis

This article explains MySQL 8.0.17 InnoDB locking mechanisms—including latches, row and table locks, lock modes, intention locks, their compatibility matrix, and detailed source‑code examples that illustrate how IX and X locks are acquired during record updates.

政采云技术
政采云技术
政采云技术
Understanding MySQL InnoDB Locks, Latches, and Intention Locks with Source‑Code Analysis

Preparation

MySQL kernel version: 8.0.17

Understanding lock and latch

Latch

Latch is a lock placed on B‑tree pages when locating a record; it is usually released immediately after the record is accessed or modified, and its scope is much smaller than a lock.

Lock

Lock is the transactional lock used by MySQL, applied to tables or rows.

Lock mode

Lock modes include Share (S) and Exclusive (X) (corresponding to LOCK_S and LOCK_X in the source). Gap modes include Record lock, Gap lock, and Next‑key lock (corresponding to LOCK_REC_NOT_GAP , LOCK_GAP , LOCK_ORDINARY ). An insert‑intention lock, represented by (LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION) , is mutually exclusive with Gap/Next‑key locks; it is set when a transaction intends to insert a new record into a gap and must wait for any existing Gap/Next‑key lock to be released.

Lock types

Intention Locks – InnoDB supports multiple‑granularity locking, allowing row and table locks to coexist.

Record Locks – A lock on an index record.

Gap Locks – A lock on the gap between index records (or before the first/after the last record).

Next‑Key Locks – A combination of a record lock and a gap lock on the preceding gap.

Intention locks

Table‑level lock compatibility matrix:

X

IX

S

IS

X

Conflict

Conflict

Conflict

Conflict

IX

Conflict

Compatible

Conflict

Compatible

S

Conflict

Conflict

Compatible

Compatible

IS

Conflict

Compatible

Compatible

Compatible

The X, IX, S, and IS entries in the matrix are table‑level locks and do not represent row‑level locks.

Lock meanings:

X : exclusive lock IX : intention exclusive lock S : shared lock IS : intention shared lock

Within a transaction ( trx_t ), lock information is stored in trx_lock_t , which includes trx->lock.trx_locks (row locks) and trx->lock.table_locks (table locks).

MySQL introduces intention locks to allow them to coexist with row locks. For example, SELECT ... FOR SHARE sets an IS lock, while SELECT ... FOR UPDATE sets an IX lock. The locking rules are:

When a transaction requests a shared row lock, it must first acquire an IS (intention shared) table lock.

When a transaction requests an exclusive row lock, it must first acquire an IX (intention exclusive) table lock.

IX and IS are table‑level locks; they do not conflict with row‑level X or S locks, only with other table‑level X or S locks. Row‑level X and S follow the usual shared/exclusive rules. Intention locks avoid scanning the entire lock hash when a table‑level X lock is needed.

Source‑code analysis

The following excerpt shows how an IX lock is created when updating a record.

/* storage/innobase/lock/lock0lock.cc */
/** Creates a table lock object and adds it as the last in the lock queue of the table.
    Does NOT check for deadlocks or lock compatibility.
    @return own: new lock object */
UNIV_INLINE
lock_t *lock_table_create(dict_table_t *table, ulint type_mode, trx_t *trx)
{
    lock_t *lock;
    ut_ad(table && trx);
    ut_ad(lock_mutex_own());
    ut_ad(trx_mutex_own(trx));
    /* check transaction state */
    check_trx_state(trx);
    ++table->count_by_mode[type_mode & LOCK_MODE_MASK];
    if (type_mode == LOCK_AUTO_INC) {
        lock = table->autoinc_lock;
        table->autoinc_trx = trx;
        ib_vector_push(trx->autoinc_locks, &lock);
    } else if (trx->lock.table_cached < trx->lock.table_pool.size()) {
        lock = trx->lock.table_pool[trx->lock.table_cached++];
    } else {
        lock = static_cast
(mem_heap_alloc(trx->lock.lock_heap, sizeof(*lock)));
    }
    lock->type_mode = ib_uint32_t(type_mode | LOCK_TABLE);
    lock->trx = trx;
    lock->un_member.tab_lock.table = table;
    ut_ad(table->n_ref_count > 0 || !table->can_be_evicted);
    UT_LIST_ADD_LAST(trx->lock.trx_locks, lock);
    ut_list_append(table->locks, lock, TableLockGetNode());
    if (type_mode & LOCK_WAIT) {
        lock_set_lock_and_trx_wait(lock, trx);
    }
    lock->trx->lock.table_locks.push_back(lock);
    MONITOR_INC(MONITOR_TABLELOCK_CREATED);
    MONITOR_INC(MONITOR_NUM_TABLELOCK);
    return(lock);
}

/** Sets the wait flag of a lock and the back pointer in trx to lock.
    @param[in]  lock  The lock on which a transaction is waiting */
UNIV_INLINE
void lock_set_lock_and_trx_wait(lock_t *lock)
{
    auto trx = lock->trx;
    ut_a(trx->lock.wait_lock == NULL);
    ut_ad(lock_mutex_own());
    ut_ad(trx_mutex_own(trx));
    trx->lock.wait_lock = lock;
    trx->lock.wait_lock_type = lock_get_type_low(lock);
    lock->type_mode |= LOCK_WAIT;
}

After acquiring the IX lock, row_upd_step() calls row_upd_clust_step() , which eventually invokes lock_clust_rec_modify_check_and_lock() to request an X lock on the target record.

----------------
| row_upd_step() |   /* request IX lock */
----------------
   |
   |   ----------------
   -> |      ...       |
       ----------------
        |
        |   ----------------------
        -> | row_upd_clust_step() |
            ----------------------
                |
                |   ----------------------------------------
                -> | lock_clust_rec_modify_check_and_lock() /* request X lock */ |
                    ----------------------------------------

If a user issues a LOCK TABLE statement, the system calls lock_table_other_has_incompatible() to check table‑level lock compatibility; a conflict puts the thread into a waiting state.

/** Checks if other transactions have an incompatible mode lock request in the lock queue.
    @return lock or NULL */
UNIV_INLINE
const lock_t *lock_table_other_has_incompatible(const trx_t *trx, ulint wait,
                                            const dict_table_t *table, lock_mode mode)
{
    const lock_t *lock;
    ut_ad(lock_mutex_own());
    // Fast‑path: if no S or X locks exist, return NULL.
    if ((mode == LOCK_IS || mode == LOCK_IX) &&
        table->count_by_mode[LOCK_S] == 0 && table->count_by_mode[LOCK_X] == 0) {
        return NULL;
    }
    for (lock = UT_LIST_GET_LAST(table->locks); lock != NULL;
         lock = UT_LIST_GET_PREV(tab_lock.locks, lock)) {
        if (lock->trx != trx && !lock_mode_compatible(lock_get_mode(lock), mode) &&
            (wait || !lock_get_wait(lock))) {
            return lock;
        }
    }
    return NULL;
}

Summary

MySQL intention locks do not conflict with each other; only IS is compatible with S, while intention locks are mutually exclusive with shared and exclusive locks.

IX and IS are table‑level locks and do not conflict with row‑level X or S locks.

References

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-intention-locks

https://zhuanlan.zhihu.com/p/412358771

InnoDBMySQLLocksSource CodeDatabase ConcurrencyIntention LocksLatches
政采云技术
Written by

政采云技术

ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.