Record lock, Gap lock,Next-key lock в MySQL InnoDB — это блокировки

Лекция 11 мин.



Привет, Вы узнаете о том , что такое record lock, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое record lock, gap lock, next-key lock , настоятельно рекомендую прочитать все из категории Базы данных - MySql (Maria DB).

Gap lock в MySQL InnoDB — это блокировка не самой существующей строки, а промежутка между индексными значениями.

Главная цель — не дать другой транзакции вставить новую строку в этот промежуток.

Простой пример

Допустим, есть индекс:

id
---
10
20
30

Транзакция 1:

START TRANSACTION;

SELECT *
FROM users
WHERE id BETWEEN 10 AND 20
FOR UPDATE;

InnoDB может заблокировать не только строки 10 и 20, но и gap между ними:

10  [-----------]  20
     GAP LOCK

Теперь другая транзакция:

INSERT INTO users (id) VALUES (15);

может заблокироваться, потому что 15 попадает в заблокированный gap.

Record lock, Gap lock,Next-key lock в MySQL InnoDB — это блокировки

Зачем это вообще нужно?

Представьте:

SELECT *
FROM users
WHERE id > 10 AND id < 20
FOR UPDATE;

В первой транзакции сейчас вообще нет подходящих строк.

Без gap lock вторая транзакция могла бы сделать:

INSERT INTO users (id) VALUES (15);

И после этого первая транзакция при повторном чтении внезапно увидела бы новую строку 15.

Это связано с проблемой phantom reads.

Gap lock позволяет InnoDB сказать:

«В этом диапазоне сейчас ничего нет — и пока я держу блокировку, не позволяй другим транзакциям вставить туда новые индексные записи».

Важное отличие

Record lock:

10 [LOCK] 20

Блокируется конкретная существующая запись 10.

Gap lock:

10 [ GAP LOCK ] 20

Блокируется пространство между индексными записями — чтобы туда нельзя было вставить новую запись.

next-key lock — это комбинация:

GAP LOCK + RECORD LOCK

Именно next-key locks часто встречаются при SELECT ... FOR UPDATE и range-запросах в InnoDB.

Главное — gap lock не стоит использовать “на всякий случай”. Обычно его появление — следствие SELECT ... FOR UPDATE с диапазоном или определенного уровня изоляции.

Когда gap lock реально нужен

Он нужен, когда тебе важно гарантировать не только:

«эту существующую запись никто не изменит»

но и:

«в этот диапазон никто не сможет вставить новую запись до окончания моей транзакции».

Типичный пример — резервирование диапазона.

Допустим, есть таблица:

orders
id
user_id
status

Вы проверяете:

SELECT *
FROM orders
WHERE user_id = 123
  AND status = 'pending'
FOR UPDATE;

Если вамнужно гарантировать, что между проверкой и вставкой другой transaction не создаст такую же запись, одного блокирования существующих строк может быть недостаточно.

Но часто это лучше решать уникальным индексом, а не gap lock.

На конкретном примере покажем почему SELECT ... WHERE id = 15 FOR UPDATE иногда ставит gap lock, даже когда строки 15 вообще не существует .

Да — это как раз один из самых важных нюансов InnoDB. Разберем на конкретном примере.

Допустим:

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
) ENGINE=InnoDB;

INSERT INTO users VALUES
(10, 'Alice'),
(20, 'Bob');

Получаем индекс:

10              20
│                │
└────── gap ─────┘

Транзакция №1

START TRANSACTION;

SELECT *
FROM users
WHERE id = 15
FOR UPDATE;

Записи id = 15 не существует.

И вот тут возникает интересное:

InnoDB не может поставить record lock на 15, потому что такой записи нет.

Поэтому при REPEATABLE READ он может поставить gap lock на диапазон, куда попала бы запись 15:

10        [ GAP LOCK ]        20
           ↑
          15

То есть фактически:

«Строки 15 сейчас нет, но я запрещаю другой транзакции вставить 15 в этот диапазон».

Транзакция №2

Теперь другой connection:

START TRANSACTION;

INSERT INTO users (id, name)
VALUES (15, 'Charlie');

И он зависнет на блокировке.

Почему?

Потому что первая транзакция сказала:

10 ──────── X ──────── 20
           ↑
      нельзя INSERT

Вторая хочет вставить:

10 ──── 15 ──── 20
       ↑
     INSERT

А 15 находится внутри gap, который заблокирован.

После:

-- connection 1
COMMIT;

первая блокировка снимается, и второй INSERT сможет продолжить.

Зачем MySQL вообще это делает?

Представим, что gap lock не было бы.

Транзакция №1:

START TRANSACTION;

SELECT *
FROM users
WHERE id = 15
FOR UPDATE;

Получает:

ничего

Потом транзакция №2:

INSERT INTO users VALUES (15, 'Charlie');
COMMIT;

А транзакция №1 продолжает работать.

Получается неприятная ситуация:

T1: "15 не существует"
          ↓
T2: INSERT 15
          ↓
T1: "а теперь 15 существует"

Это один из вариантов phantom problem.

Поэтому InnoDB при REPEATABLE READ может заблокировать сам промежуток, а несуществующую строку.

А что будет, если id = 15 существует?

Допустим:

10
15
20

И выполняем:

SELECT *
FROM users
WHERE id = 15
FOR UPDATE;

Теперь существует конкретная запись.

InnoDB может поставить record lock на 15:

10       [15 LOCKED]       20

И другой:

UPDATE users
SET name = 'X'
WHERE id = 15;

будет ждать.

Есть еще один важный нюанс

Если запрос идет по PRIMARY KEY / UNIQUE index и ты делаешь точное равенство, поведение отличается от range scan.

Например:

WHERE id = 15

где id — PRIMARY KEY.

Если 15 существует → блокируется запись.

Если 15 не существует → в REPEATABLE READ InnoDB может блокировать соответствующий gap.

А если запрос:

WHERE id > 10 AND id < 20

то ситуация еще интереснее: здесь уже range locking, и InnoDB может использовать next-key locks — комбинацию record lock + gap lock.

Визуально

              InnoDB index

      10          20          30
       │           │           │
       ▼           ▼           ▼
───────●───────────●───────────●──────
       ↑           ↑
       │           │
       └── gap ────┘

SELECT ... Об этом говорит сайт https://intellect.icu . WHERE id = 15 FOR UPDATE при отсутствии 15:

───────●═══════════●───────────●──────
      10   GAP     20
           LOCK

Поэтому:

INSERT INTO users(id) VALUES (15);

ждет.

А:

INSERT INTO users(id) VALUES (25);

не должен ждать из-за этого конкретного gap lock, потому что 25 находится уже в другом диапазоне.


И вот здесь появляется очень практический вывод: если ты видишь Lock wait timeout на INSERT, хотя никто вроде бы не блокировал строку с таким id, вполне возможно, что ее вообще не существует — и ее блокирует gap lock другой транзакции.

Как предотвратить проблемы с gap lock

1. Использовать правильный индекс

Это очень важно.

Например:


 
SELECT *
FROM orders
WHERE user_id = 123
FOR UPDATE;

Если user_id нормально индексирован, InnoDB блокирует гораздо более узкий диапазон.

Без подходящего индекса ситуация может быть значительно хуже: InnoDB может просматривать и блокировать большое количество индексных записей.

Поэтому первое правило:

Проверяй EXPLAIN и индексы для запросов, которые выполняются FOR UPDATE.

2. Если нужен только row lock — стараться делать точечный запрос по уникальному индексу

Например:

SELECT *
FROM users
WHERE id = 123
FOR UPDATE;

где:

id PRIMARY KEY

Это намного лучше, чем:

SELECT *
FROM users
WHERE id > 100 AND id < 200
FOR UPDATE;

В первом случае тебе нужна конкретная запись.

Во втором ты говоришь базе:

«Заблокируй диапазон».

И тут уже появляются next-key/gap locks.

3. Не держать транзакцию долго

Очень важное правило.

Плохо:

START TRANSACTION;

SELECT ... FOR UPDATE;

-- HTTP request
-- обращение к API
-- расчеты
-- пользователь что-то делает
-- 10 секунд...

UPDATE ...;

COMMIT;

Все это время lock живет.

Лучше:

START TRANSACTION;

SELECT ... FOR UPDATE;

UPDATE ...;

COMMIT;

То есть критическая секция должна быть максимально короткой.

4. Иногда лучше вообще не использовать FOR UPDATE

Например, если ты делаешь:

SELECT balance
FROM accounts
WHERE id = 123
FOR UPDATE;

только для того, чтобы потом:

UPDATE accounts
SET balance = balance - 100
WHERE id = 123;

можно часто сделать атомарный UPDATE:

UPDATE accounts
SET balance = balance - 100
WHERE id = 123
  AND balance >= 100;

А затем проверить:

affected_rows == 1

Это зачастую проще и уменьшает количество ручных блокировок.

5. Для защиты от дублей часто лучше UNIQUE INDEX

Допустим, задача:

У пользователя может быть только один активный subscription.

Не обязательно делать:

SELECT ... FOR UPDATE;
-- проверка
-- INSERT

Лучше, если модель данных позволяет, обеспечить это ограничением:

UNIQUE (...)

и обрабатывать конфликт вставки.

Ограничения БД часто лучше ручных блокировок.

Когда gap lock действительно оправдан?

Хороший пример — очередь/резервирование диапазона, когда тебе нужно атомарно:

  1. проверить отсутствие записи в диапазоне;
  2. не дать другой транзакции вставить туда запись;
  3. создать/изменить данные;
  4. закоммитить.

То есть тебе действительно нужна гарантия:

[----------- ЗАЩИЩЁННЫЙ ДИАПАЗОН -----------]

А не просто:

[LOCK] конкретной строки

И еще важный момент — isolation level

Gap locks особенно связаны с REPEATABLE READ, который является стандартным isolation level для InnoDB.

Можно использовать:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

В READ COMMITTED InnoDB значительно меньше использует gap locking для обычных поисковых операций, хотя некоторые gap locks все равно остаются, например связанные с проверками уникальности и внешними ключами.

Поэтому переключать весь проект на READ COMMITTED только ради избавления от gap locks я бы не советовал. Сначала нужно понять, зачем именно возникла блокировка.

Практическое правило

Я бы держал в голове такую последовательность:

Нужно защитить существующую строку?
        ↓
SELECT ... FOR UPDATE
по PRIMARY KEY / UNIQUE INDEX
        ↓
Да → row/record lock

Нужно запретить INSERT в диапазон?
        ↓
Нужна range locking семантика
        ↓
gap/next-key lock оправдан

Нужно просто не допустить дубликат?
        ↓
UNIQUE INDEX предпочтительнее lock

Можно выполнить изменение одним UPDATE?
        ↓
Атомарный UPDATE предпочтительнее SELECT FOR UPDATE

И самое главное: gap lock сам по себе не проблема. Проблема возникает, когда приложение неожиданно блокирует большой диапазон и из-за этого другие транзакции начинают ждать.

Исследование, описанное в статье про record lock, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое record lock, gap lock, next-key lock и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Базы данных - MySql (Maria DB)

создано: 2026-10-06
обновлено: 2026-10-06
1



Поделиться:
Пожаловаться

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выполнения задания
  • Возможно применение функции гаранта на сделку
  • Приоритетная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можете продать (как исполнитель) или купить (как заказчик) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно применение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей

Комментарии

Оставить комментарий

Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Лекции и учебник по "Базы данных - MySql (Maria DB)"

Термины: Базы данных - MySql (Maria DB)