mysql事务和锁有什么关系_mysql并发控制原理

来源:这里教程网 时间:2026-02-28 20:50:04 作者:

事务的隔离性靠锁和MVCC共同实现,不是“用了事务就自动上锁”,而是根据操作类型、隔离级别、SQL语句显式/隐式触发锁机制。

哪些SQL会真正加行锁?

很多人以为

SELECT
一定不加锁,其实不然:在
REPEATABLE READ
隔离级别下,普通
SELECT
是快照读(走 MVCC,不加锁),但只要带上
FOR UPDATE
LOCK IN SHARE MODE
,就会立即申请行级排他锁(X锁)或共享锁(S锁)。

UPDATE
/
DELETE
语句,即使没命中索引,也可能升级为表锁或间隙锁(尤其是 WHERE 条件无法使用索引时)
INSERT
会加插入意向锁(Insert Intention Lock),与间隙锁冲突——这是幻读防控的关键一环
全表扫描的
SELECT ... FOR UPDATE
会为所有扫描过的记录加锁,甚至可能锁住不存在但符合范围的“间隙”(即间隙锁)

为什么
UPDATE
卡住,但
SELECT
不卡?

因为默认隔离级别下,

SELECT
走的是 MVCC 快照读,它不争抢锁,只读自己事务启动时刻可见的版本;而
UPDATE
必须获取 X 锁才能修改,一旦目标行已被其他未提交事务锁定,就会进入等待队列(
is_waiting=true
)。

这种“读不阻塞写、写不阻塞读”的分离,是 InnoDB 高并发的基础 但注意:如果
SELECT
加了
FOR UPDATE
,它就变成当前读,会和
UPDATE
互斥
SHOW ENGINE INNODB STATUS\G
可查到锁等待链,比如
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

意向锁(IX/IS)不是摆设,它是表锁和行锁的“通行证”

你执行

SELECT ... FOR UPDATE
时,InnoDB 先申请表级
IX
锁,再尝试对具体行加 X 锁。这个
IX
的作用不是锁数据,而是快速告诉别人:“这张表里有行正被写”。没有它,每次加行锁前都得扫全表检查有没有表锁,性能崩盘。

LOCK TABLES t WRITE
会阻塞所有 IX/IS,所以它能彻底禁止其他事务访问该表
两个事务同时对同一张表的不同行加 X 锁,各自持 IX 锁完全不冲突——这就是多粒度锁的设计精妙处 误以为“没显式锁表就不会被阻塞”,结果一个 DDL(如
ALTER TABLE
)来了,发现被
IX
锁住了元数据锁(MDL),整个表卡住

MVCC 和锁不是二选一,而是按需协作

MVCC 解决的是“读-写并发”,但它不解决“写-写并发”——两个事务同时

UPDATE
同一行,必然要靠 X 锁串行化。而 MVCC 的版本链依赖 undo log,一旦事务长时间不提交,undo log 不能回收,会导致历史版本堆积、空间暴涨、甚至阻塞 purge 线程。

长事务是锁和 MVCC 的双重敌人:既可能持有锁不放,又让 purge 停摆
READ COMMITTED
下每次
SELECT
都生成新 Read View,
REPEATABLE READ
复用事务首次读的 Read View——这意味着后者能避免不可重复读,但幻读仍需间隙锁补位
想彻底规避锁竞争?业务层可考虑乐观锁(如
version
字段 +
WHERE version = ?
),但要注意它不防脏读,也不替代事务边界

真正容易被忽略的,是锁的“隐形成本”:锁结构本身占内存、等待队列调度有开销、死锁检测会暂停事务几毫秒。与其纠结“要不要加锁”,不如先看清楚——你的 SQL 走的是快照读还是当前读,是否命中索引,事务是否真的必要短平快。

相关推荐