mysql索引和锁之间有什么关系_mysql并发优化说明

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

索引没走对,行锁就变表锁

InnoDB 的行锁不是“按行号”锁的,而是通过索引项加锁——只有当

WHERE
条件能命中索引(且引擎层能直接过滤掉无关行),才真正只锁命中的那几行。否则,InnoDB 会先锁住所有扫描过的行(甚至退化为全表扫描+全表加锁)。

常见错误现象:
UPDATE orders SET status='done' WHERE user_id = 123
执行极慢、阻塞大量其他事务,
EXPLAIN
显示
type=ALL
(全表扫描)
根本原因:
user_id
没建索引,或建了但类型不匹配(如字段是
VARCHAR
,却用数字
123
查询,触发隐式转换导致索引失效)
实操建议:对所有
WHERE
JOIN
ORDER BY
字段建立合适索引;用
EXPLAIN FORMAT=TRADITIONAL
确认
key
列是否非
NULL
rows
是否显著小于表总行数

唯一索引等值查询只锁一行,非唯一索引可能锁多行

锁的范围和索引类型强相关——不是“有没有索引”,而是“什么索引 + 什么查询条件”。MySQL 会根据索引结构和隔离级别动态选择锁粒度。

场景对比:
SELECT * FROM users WHERE id = 10 FOR UPDATE
id
是主键)→ 只锁
id=10
这一行
SELECT * FROM users WHERE name = 'Alice' FOR UPDATE
name
是普通二级索引,且有多个 'Alice')→ 锁所有匹配的索引记录,以及这些记录之间的间隙(即临键锁)
容易踩的坑:以为加了索引就“安全”,结果因索引非唯一+高并发更新相同值,引发大量锁等待甚至死锁 优化方向:对高频更新的查询字段,优先使用唯一索引;若必须用非唯一索引,考虑在应用层加分布式锁或业务层限流,避免多个事务同时争抢同一索引值

没查到数据时,间隙锁可能“悄悄锁住一片”

这是最反直觉也最容易被忽略的一点:当唯一索引等值查询未命中记录,InnoDB 不会什么都不锁,而是加一个**间隙锁**(Gap Lock),防止其他事务在该间隙插入新记录——目的是保证可重复读(RR)隔离级别下的幻读一致性。

典型表现:
SELECT * FROM orders WHERE order_no = 'NO20260128001' FOR UPDATE
返回空,但后续
INSERT INTO orders (order_no, ...) VALUES ('NO20260128001', ...)
被阻塞
为什么这样设计:防止在事务 A 判断“此单号不存在”后,事务 B 插入同单号,导致 A 再次查询时出现幻读 注意点:间隙锁不冲突于纯读操作(如
SELECT ... LOCK IN SHARE MODE
不会阻塞 SELECT),但会阻塞 INSERT;若业务允许读已提交(RC)隔离级别,可降级以彻底关闭间隙锁(但需承担幻读风险)

覆盖索引能减少锁范围,还能避免回表带来的额外锁

当查询能被索引完全覆盖(即

SELECT
字段都在索引中),InnoDB 就无需回聚簇索引取数据——这意味着不仅快,而且锁得更少、更准。

举例:
SELECT user_id, amount FROM orders WHERE create_time > '2026-01-01'
,若存在联合索引
INDEX idx_time_amt (create_time, user_id, amount)
,则只锁该索引上满足条件的记录,不涉及主键索引行
对比陷阱:若索引只有
(create_time)
,就要回表查
user_id
amount
,此时不仅性能差,还会额外锁定主键索引上的对应行,扩大锁竞争面
实操建议:对高频、带
FOR UPDATE
LOCK IN SHARE MODE
的查询,优先设计覆盖索引;用
SHOW ENGINE INNODB STATUS\G
查看
TRANSACTIONS
部分的
lock_mode
lock_trx_id
,确认是否发生不必要回表

索引和锁的关系,本质是“数据库如何把逻辑意图翻译成物理锁定”。很多并发问题不是锁本身太重,而是索引没让锁落得准——一个没走索引的

UPDATE
,可能比十个精心设计的行锁更伤并发。真正要盯住的,永远是那条
EXPLAIN
输出里的
key
rows

相关推荐