Databases 23 min read

What Locks Does SELECT FOR UPDATE Use? 20 MySQL Scenarios Revealed

This article experimentally verifies how SELECT FOR UPDATE behaves under MySQL 5.7 and 8.0 with both REPEATABLE‑READ and READ‑COMMITTED isolation levels, covering primary keys, unique indexes, ordinary indexes, no indexes and range queries, and summarizes when row‑level, gap‑level or table‑level locks are taken.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
What Locks Does SELECT FOR UPDATE Use? 20 MySQL Scenarios Revealed

Background

Many articles disagree on whether SELECT FOR UPDATE acquires row locks or table locks, and the answer directly impacts system performance. The conclusions vary because they often ignore MySQL version and transaction isolation level. This article tests four major cases (two MySQL versions × two isolation levels) across 20 scenarios and provides a systematic methodology.

Environment Preparation

Table definition:

<code>CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_no` varchar(16) DEFAULT NULL COMMENT '用户编号',
  `user_name` varchar(16) DEFAULT NULL COMMENT '用户名',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `address` varchar(128) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_user_no` (`user_no`),
  KEY `idx_user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;</code>

Initial data:

<code>insert into user values(null,'0001','user01',18,'北京');
insert into user values(null,'0002','user02',19,'上海');
insert into user values(null,'0003','user03',20,'广州');
insert into user values(null,'0004','user04',21,'深圳');
insert into user values(null,'0005','user05',22,'杭州');</code>

MySQL versions used:

<code>SELECT @@VERSION;   -- 5.7.22
SELECT @@VERSION;   -- 8.0.18</code>

Transaction isolation levels:

<code>SELECT @@TRANSACTION_ISOLATION;   -- REPEATABLE-READ</code>

Supported isolation levels:

READ_UNCOMMITTED

READ_COMMITTED (RC)

REPEATABLE_READ (RR)

SERIALIZABLE

Global and session isolation can be set with:

<code>SET GLOBAL transaction isolation level REPEATABLE READ;
SET SESSION transaction isolation level READ COMMITTED;</code>

Auto‑commit is disabled for the tests:

<code>SET @@AUTOCOMMIT=0;</code>

Scenario 1.1: V5.x‑RR‑Primary Key

Operation: SELECT * FROM user WHERE id = 1 FOR UPDATE then try to update the same row.

Result: the update is blocked, indicating a lock. Querying INFORMATION_SCHEMA.INNODB_LOCKS shows lock_mode = X , lock_type = RECORD , meaning a row‑level exclusive lock.

Updating a different row succeeds, confirming it is a row lock.

Conclusion : With a primary‑key condition, SELECT FOR UPDATE acquires a row‑level lock.

Scenario 1.2: V5.x‑RR‑Unique Index

Condition on the unique column user_no . The lock information is identical to the primary‑key case.

Conclusion : Unique‑index condition also results in a row‑level lock.

Scenario 1.3: V5.x‑RR‑Ordinary Index

Condition on user_name . The lock shows lock_mode = X and lock_type = RECORD plus a GAP lock, i.e., an exclusive gap lock.

Inserting a new row whose user_name matches the condition is blocked, demonstrating the gap lock.

Conclusion : Ordinary‑index condition yields a row lock together with an exclusive gap lock; inserts that satisfy the condition are blocked.

Scenario 1.4: V5.x‑RR‑No Index

Condition on address = '北京' (no index). The lock appears as a table‑level lock in the lock table, but lock_type is still RECORD.

Conclusion : Without an index, SELECT FOR UPDATE results in a table‑level lock under RR.

Scenario 1.5: V5.x‑RR‑Range Query

Condition id > 1 (range on indexed column). The lock shows a supremum pseudo‑record (next‑key lock) and a GAP lock.

Inserting a row whose generated id satisfies the range is blocked.

Conclusion : Range queries on indexed columns use gap/next‑key locks that block matching inserts.

Scenario 2.x: MySQL 8.0‑RR

All primary‑key, unique‑index and ordinary‑index cases behave similarly to 5.7‑RR, but lock inspection uses performance_schema.data_locks . The lock hierarchy includes an IX intention lock at the table level and an X lock (or X,REC_NOT_GAP) at the record level.

Ordinary‑index still adds a GAP lock; no‑index still yields a table lock.

Scenario 3.x: MySQL 5.7‑RC

Switching isolation to READ‑COMMITTED.

Primary‑key, unique‑index and ordinary‑index conditions all produce row‑level locks. The ordinary‑index case no longer has a GAP lock, so inserts with the same user_name succeed.

Without an index, SELECT FOR UPDATE still results in row‑level locks because MySQL scans the clustered primary key and locks each matching row, unlocking non‑matching rows during the scan.

Scenario 4.x: MySQL 8.0‑RC

Results mirror 5.7‑RC: primary‑key and unique‑index give row locks; ordinary‑index gives only row locks (no GAP); no‑index also gives row locks.

Range queries lock the matching rows but do not block inserts that fall into the range (no next‑key lock in this version/level).

Summary Table (textual)

MySQL 5.7‑RR: primary/unique → row lock; ordinary → row + gap lock; no index → table lock; range → row + gap (insert blocked).

MySQL 8.0‑RR: primary/unique → row lock; ordinary → row + gap lock; no index → table lock; range → row + gap (insert blocked).

MySQL 5.7‑RC: primary/unique/ordinary → row lock; ordinary → no gap; no index → row lock; range → row lock (insert blocked).

MySQL 8.0‑RC: primary/unique → row lock; ordinary → row lock (no gap); no index → row lock; range → row lock (insert not blocked).

Key Takeaways

When the query condition uses a primary key, unique index, or ordinary index, SELECT FOR UPDATE acquires row‑level locks.

Under REPEATABLE‑READ, an ordinary‑index condition adds an exclusive gap lock, causing inserts that match the condition to block.

Under REPEATABLE‑READ, a condition without any index results in a table‑level lock.

Under READ‑COMMITTED, a condition without an index results in row‑level locks, not a table lock.

Range queries on indexed columns generally use next‑key or gap locks; only MySQL 8.0‑RC does not block inserts for such ranges.

These conclusions help developers predict locking behavior and avoid performance pitfalls when using SELECT FOR UPDATE in MySQL.

MySQLlockingTransaction IsolationDatabase PerformanceSELECT FOR UPDATE
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

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.