mysql 中 select … for update 默认加行级锁,但若 where 条件无有效索引,则退化为表级锁,导致并发性能下降和事务阻塞。

MySQL 中 SELECT ... FOR UPDATE 默认加什么锁?
MySQL 的行级锁不是凭空出现的,它高度依赖索引。没有合适索引时,SELECT ... FOR UPDATE 会退化为表级锁——不是因为语法写错了,而是优化器发现走不了索引,干脆锁整张表。
常见错误现象:SHOW ENGINE INNODB STATUS 里看到大量 table LOCK 而非 RECORD LOCK,同时并发更新变慢、事务阻塞明显。
使用场景:你本意是只锁某几条订单记录(比如 WHERE order_id = 123),但 order_id 字段没建索引,或建了但类型不匹配(如字段是 VARCHAR,查询却用数字 123,触发隐式转换)。
- 确保
WHERE条件列有有效索引(B+ 树,非函数索引) - 检查执行计划:
EXPLAIN SELECT ... FOR UPDATE,确认type是const、ref或range,而非ALL - 避免在索引列上用函数或表达式:
WHERE YEAR(created_at) = 2024会让索引失效
postgresql 的 SELECT FOR UPDATE 为什么有时比 MySQL 还慢?
PostgreSQL 默认用 MVCC + 行级锁,看似更“精细”,但它锁的是元组(tuple)物理位置,而 tuple 可能因 VACUUM 或更新产生多版本。当同一行被高频更新时,锁竞争会集中在该行的最新可见 tuple 上,形成热点。
性能影响:在高并发更新单条记录(如库存扣减)场景下,PostgreSQL 的锁等待时间可能高于 MySQL(后者若走唯一索引,锁定位更稳定)。
- 如果业务允许,优先用
UPDATE ... WHERE替代SELECT FOR UPDATE+UPDATE两阶段操作 - 对热点行,考虑分片(如把一个商品拆成多个虚拟库存记录)或改用
SKIP LOCKED配合队列消费 - 注意:PostgreSQL 14+ 支持
FOR UPDATE SKIP LOCKED,但 MySQL 8.0 才开始支持,旧版 MySQL 不可用
MyISAM 表里用 LOCK TABLES WRITE 会阻塞哪些操作?
MyISAM 只有表级锁,且锁粒度粗、无事务支持。LOCK TABLES orders WRITE 后,不只是 INSERT/UPDATE/delete 被堵,连 SELECT 都会被阻塞——除非显式加 LOW_PRIORITY 或用 CONCURRENT INSERT(仅对尾部插入有效)。
容易踩的坑:
应用层以为只是“写锁”,没意识到读也会等,导致监控看板卡顿、API 响应超时
锁未释放(比如忘记
UNLOCK TABLES或连接异常断开),整个表长期不可用在事务中混用 MyISAM 和 InnoDB 表,MyISAM 锁不会被事务回滚自动释放
避免在新项目中用 MyISAM;存量系统迁移前,先用
SHOW OPEN TABLES WHERE In_use > 0查活跃锁表若必须用,把
LOCK TABLES范围压到最小,操作完立刻UNLOCK TABLES不要跨连接复用锁:A 连接锁的表,B 连接无法访问,哪怕 B 只想读
InnoDB 死锁日志里出现 WAITING FOR this LOCK TO BE GRANTED 怎么快速定位?
死锁日志本身不直接告诉你哪条 SQL 触发了冲突,关键要看紧挨着的两段事务堆栈里的 HELD LOCKS 和 WAITING FOR 部分。真正的问题往往藏在“持有锁却还在等另一把锁”的那个事务里。
典型误判:看到 A 事务在等 B 事务释放锁,就去查 B 的 SQL——其实 B 可能早已提交,而 A 持有的锁(比如对用户表的二级索引锁)正挡着 C 事务的路。
- 先找
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:下面的lock_mode X locks rec but not gap对应的索引名和主键值 - 再往上翻,找到
*** (1) HOLDS THE LOCK(S):,看它还持有哪些锁——这些才是真正的“拦路虎” - 结合
information_schema.INNODB_TRX查当时长时间运行的事务,比对TRX_QUERY和锁涉及的表
行级锁的“细”是假象,它的代价藏在索引结构、事务生命周期和语句写法里。很多人调优只盯着 SQL,却忘了 autocommit 开关、连接是否复用、甚至客户端超时设置,都会让锁行为彻底跑偏。