mysql中使用SELECT FOR UPDATE语句进行行级锁定

11次阅读

select for UPDATE 未锁住行的根本原因是事务未开启或autocommit=1;它仅在显式事务中生效,且依赖索引与隔离级别——RR下加间隙锁+记录锁,无索引则锁全表。

mysql中使用SELECT FOR UPDATE语句进行行级锁定

SELECT FOR UPDATE 为什么没锁住行

根本原因通常是事务未开启或自动提交未关闭。mysqlSELECT FOR UPDATE 只在显式事务中生效,且必须搭配 START TRANSACTIONBEGIN 使用;如果 autocommit=1(默认),每条语句执行完立即提交,锁会立刻释放。

  • 执行前务必确认:
    SELECT @@autocommit;

    返回 0 才安全;若为 1,需先执行 SET autocommit = 0;

  • 不能在视图、临时表、非事务引擎(如 MyISAM)上使用,否则报错 Error 1288: The target table ... of the FOR UPDATE clause cannot be updated
  • 锁定范围取决于 WHERE 条件是否命中索引:全表扫描时可能升级为表锁;无索引字段查询会锁全表,极大降低并发

SELECT FOR UPDATE 在可重复读(RR)隔离级别下的行为

MySQL 默认隔离级别是 REPEATABLE READ,此时 SELECT FOR UPDATE 使用的是**间隙锁(Gap Lock)+ 记录锁(Record Lock)**,不仅锁住匹配的行,还会锁住索引间隙,防止幻读。

  • 例如表 t(id PK, name),执行 SELECT * FROM t WHERE name = 'Alice' FOR UPDATE,若 name 无索引,则锁全表;若有索引但值不存在,仍会锁住该间隙(比如 ‘Alice’ 应该插入的位置)
  • 间隙锁不可被 SELECT ... LOCK IN SHARE MODE 兼容,但可被其他 SELECT FOR UPDATE 阻塞——这是死锁高发场景
  • 想禁用间隙锁?只能临时切到 READ COMMITTED 级别(SET session TRANSACTION ISOLATION LEVEL READ COMMITTED),但会失去幻读防护

如何验证某行是否已被 SELECT FOR UPDATE 锁定

最直接的方式是用另一会话尝试相同语句并观察阻塞行为,但生产环境不宜盲试。更稳妥的方法是查 INFORMATION_SCHEMA.INNODB_TRX 和锁信息表:

  • 查看当前活跃事务:
    SELECT trx_id, trx_state, trx_started, trx_query FROM INFORMATION_SCHEMA.INNODB_TRX;
  • 关联锁信息定位被锁行:
    SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS w INNER JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id INNER JOIN INFORMATION_SCHEMA.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
  • 注意:这些表只显示 InnoDB 层面的锁,不包含表级锁或元数据锁(MDL)

SELECT FOR UPDATE 和 UPDATE 的锁区别

SELECT FOR UPDATE 是“纯读锁”,不修改数据,但会加写锁(X 锁),阻止其他事务读写该行;而 UPDATE 语句本身隐式执行类似逻辑,但多了数据变更和 undo log 写入开销。

  • 若后续确定要更新,推荐直接用 UPDATE ... WHERE ...,避免多一次锁获取;SELECT FOR UPDATE 更适合“读-判-写”三步逻辑,比如库存扣减前先校验余额是否充足
  • UPDATE 在 RR 下也会加间隙锁,但仅当 WHERE 条件涉及索引且存在匹配行时才触发;而 SELECT FOR UPDATE 即使 WHERE 不匹配(如 WHERE id = 999999 不存在),只要索引可用,仍会锁间隙
  • 两者都受 innodb_lock_wait_timeout 控制,默认 50 秒,超时抛出 ERROR 1205: Deadlock found when trying to get lockERROR 1205: Lock wait timeout exceeded

实际应用中最容易忽略的是索引有效性与隔离级别的耦合影响——同一句 SELECT FOR UPDATE,在有无索引、RR 与 RC 下,锁的粒度和范围可能天差地别。

text=ZqhQzanResources