WHERE条件未走索引会导致行锁退化为表级竞争,引发锁扩大;必须确保WHERE命中最左匹配的唯一索引,配合短事务与合适隔离级别(如READ COMMITTED)才能有效避免。
这不是慢查询,是锁扩大。哪怕你只更新id = 123这一行,只要WHERE没命中索引,InnoDB 就会全表扫描,对每行加意向锁甚至行锁——其他事务一来就排队等这把“本不该存在”的锁。
EXPLAIN查type字段:出现ALL或index必须停手优化WHERE user_id = '123'(user_id是INT)→ 改成WHERE user_id = 123
WHERE DATE(create_time) = '2026-05-01' → 改成WHERE create_time >= '2026-05-01' AND create_time
(status, category),但只查WHERE category = 'A',照样全表扫这两个语句内部依赖快速定位。定位不准,就会在查找阶段误加大量间隙锁(Gap Lock),导致插入新记录也被阻塞,甚至死锁。
SELECT * FROM orders WHERE order_no = 'ORD-123' FOR UPDATE:如果order_no有唯一索引 → 只加记录锁,安全SELECT * FROM orders WHERE user_id = 1001 FOR UPDATE:若user_id无索引 → 全表扫描 + 每行加锁 → 实际退化为表级竞争INSERT INTO orders (...) ON DUPLICATE KEY UPDATE ...:必须确保ON DUPLICATE KEY依赖的字段(如order_no)有唯一索引,否则查找阶段会锁住整个可能插入的间隙UNIQUE(email)和UNIQUE(phone))并发冲突时,InnoDB 加锁顺序不一致,也可能死锁自增主键 + 聚簇索引 = 物理位置固定,没法靠数据分布摊薄压力。8000 QPS 全挤在UPDATE goods SET stock = stock - 1 WHERE id = 123这一行上,不是并行,是排队。
goods_id % 10拆成 10 个 key,DECRBY goods:123:shard_3 1原子操作,返回 ≥ 0 才放行Redis Streams)按可控速率(如 500 QPS)批量执行UPDATE goods SET stock = stock - ? WHERE id = 123 AND stock >= ?
CREATE TABLE goods_stock_shard (goods_id INT, shard_id TINYINT, stock INT, PRIMARY KEY(goods_id, shard_id)),把单行热变成多行温READ COMMITTED:它不加间隙锁,死锁概率骤降;多数业务根本不需要REPEATABLE READ语义锁不是被抢走的,是被“占着不放”的。事务里调一次 HTTP、解析一段 JSON、甚至sleep(100),X 锁就挂着不动,后面所有请求全卡住。
UPDATE + ROW_COUNT()检查,绝不在里面调外部接口、写日志文件或做复杂计算EXPLAIN看key和rows是硬指标version字段)只是把重试甩给应用层;高并发下重试风暴可能压垮服务本身,不适合秒杀库存、资金余额等强一致高频写场景真正容易被忽略的是:你改完索引、调完参数、上了缓存,却忘了检查事务是否真的短小精悍,或者SELECT FOR UPDATE是否真落在唯一索引上。锁不会自己变聪明,它只忠实地执行你写的 SQL 和事务结构。