为什么 SELECT ... FOR UPDATE
在高并发下容易锁表?
本质是它会在满足条件的行上加排他锁(X锁),如果查询没走索引,InnoDB 会升级为表级锁或锁住整个索引范围;更常见的是多个事务按不同顺序更新同一组行,导致死锁或长时间等待。
必须确保WHERE条件字段有有效索引,否则会触发全表扫描+间隙锁 避免在事务中混合执行
SELECT ... FOR UPDATE和非关联的
UPDATE,顺序不一致极易引发死锁 尽量缩短事务生命周期:查完立刻更新,不要在事务里做 HTTP 调用、日志写入等耗时操作
用 INSERT ... ON DUPLICATE KEY UPDATE
替代“先查后更”逻辑
典型场景如计数器、状态机流转、幂等插入——这类操作若拆成
SELECT+
INSERT/UPDATE,在并发下必然出现竞态:两个事务同时查到旧值,都执行更新,最终只生效一次。
INSERT INTO user_login_log (user_id, login_time, ip) VALUES (123, NOW(), '192.168.1.1') ON DUPLICATE KEY UPDATE login_time = VALUES(login_time), ip = VALUES(ip);前提是
user_id或组合字段有唯一索引(如
UNIQUE KEY (user_id)) 注意
VALUES(col)引用的是本次 INSERT 中该列的值,不是原记录值 不支持在
ON DUPLICATE KEY UPDATE子句中使用子查询,复杂逻辑需另寻方案
读多写少场景下,用 READ COMMITTED
隔离级别降低锁粒度
MySQL 默认隔离级别
REPEATABLE READ会启用间隙锁(Gap Lock)防止幻读,但代价是更大范围的锁冲突;而
READ COMMITTED只对实际命中的记录加行锁,不锁间隙,适合订单查询、商品详情等以读为主、写频次低的业务。 修改方式:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;或在配置文件中设
transaction_isolation = 'READ-COMMITTED'副作用:可能读到“不可重复读”,但多数 Web 应用对此不敏感 无法解决写-写冲突,仍需靠主键/唯一键约束 + 原子语句控制
分库分表前,先确认是不是索引和语句本身的问题
很多团队一遇到并发慢就想着分片,结果发现慢查询日志里全是
type: ALL或
Extra: Using filesort—— 根本问题在缺失索引或
ORDER BY/
GROUP BY没利用索引。 用
EXPLAIN FORMAT=TRADITIONAL看执行计划,重点关注
key、
rows、
Extra复合索引要遵循最左匹配,比如
(a,b,c)支持
WHERE a=1 AND b>2,但不支持
WHERE b=2
pt-query-digest分析慢日志比肉眼扫更可靠,尤其能暴露低频但高延迟的“隐形杀手”语句
真正需要分片的信号是单表超千万行且写入持续增长、二级索引维护成本明显上升,而不是单纯因为某条
UPDATE偶尔锁了 200ms。
