SQL 行级锁与表级锁性能差异

1次阅读

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

SQL 行级锁与表级锁性能差异

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,确认 typeconstrefrange,而非 ALL
  • 避免在索引列上用函数或表达式:WHERE YEAR(created_at) = 2024 会让索引失效

postgresqlSELECT 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 LOCKSWAITING 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 开关、连接是否复用、甚至客户端超时设置,都会让锁行为彻底跑偏。

text=ZqhQzanResources