在MySQL的InnoDB存储引擎中,锁机制是支撑事务ACID特性、控制并发访问的核心组件,其中隐式锁定和显式锁定是两种最基础的锁定类型,而InnoDB的默认加锁策略会根据操作类型和隔离级别自动选择对应的锁定方式。

隐式锁定与显式锁定的核心区别
隐式锁定是InnoDB存储引擎自动为事务添加的锁,不需要用户编写额外的加锁语句,引擎会根据事务的隔离级别、执行的SQL操作类型自动判断需要加的锁类型和范围。比如在默认的REPEATABLE READ隔离级别下,执行普通的UPDATE语句修改某行数据时,InnoDB会自动为这行数据加上排他锁,这个加锁过程对用户完全透明。
显式锁定则是需要开发者通过特定的SQL语句主动申请的锁,常见的显式锁定语句包括SELECT ... FOR SHARE和SELECT ... FOR UPDATE,这类语句会明确告诉引擎需要对查询到的数据行加上共享锁或者排他锁,锁的持有时间同样遵循事务的生命周期,直到事务提交或回滚才会释放。
| 对比维度 | 隐式锁定 | 显式锁定 |
|---|---|---|
| 加锁方式 | InnoDB自动添加,无需用户干预 | 用户通过特定SQL语句主动申请 |
| 常见场景 | 普通的DML操作(INSERT、UPDATE、DELETE) | 需要提前锁定数据避免后续被修改的查询场景 |
| 锁类型 | 根据操作自动选择共享锁或排他锁 | 用户可指定共享锁或排他锁 |
| 可控性 | 低,由引擎规则决定 | 高,开发者可自定义加锁逻辑 |
InnoDB默认加锁策略解析
InnoDB的默认加锁策略和事务隔离级别强相关,MySQL默认的隔离级别是REPEATABLE READ,在该隔离级别下,InnoDB的默认加锁规则如下:
普通DML操作的默认加锁
执行INSERT、UPDATE、DELETE语句时,InnoDB会对涉及的数据行自动加上排他锁(X锁),同时如果是通过索引条件检索数据,会采用行级锁;如果没有使用索引导致全表扫描,则会升级为表级锁(实际是锁住所有行,效果和表锁一致)。
以下是模拟UPDATE操作隐式加锁的示例:
-- 开启事务 START TRANSACTION; -- 更新id为1的用户余额,InnoDB会自动为id=1的行加上排他锁 UPDATE user_account SET balance = balance - 100 WHERE id = 1; -- 此时其他事务无法修改id=1的这行数据,也无法对其加共享锁 -- 提交事务后锁自动释放 COMMIT;
普通查询的默认加锁
在REPEATABLE READ隔离级别下,普通的SELECT语句默认不会加任何锁,而是通过MVCC(多版本并发控制)机制读取数据的快照版本,因此普通的查询操作不会阻塞其他事务的写操作,也不会被其他事务的写操作阻塞。
显式锁定语句的默认行为
如果使用SELECT ... FOR SHARE,InnoDB会对查询到的数据行加上共享锁(S锁),此时其他事务可以读取这些数据,也可以对这些数据加共享锁,但是不能加排他锁,也就是不能修改这些数据,直到当前事务释放共享锁。
如果使用SELECT ... FOR UPDATE,InnoDB会对查询到的数据行加上排他锁(X锁),效果和DML操作的隐式排他锁一致,其他事务既不能修改这些数据,也不能对其加共享锁或排他锁。
以下是显式加锁的示例:
-- 开启事务 START TRANSACTION; -- 对id为1的用户数据加共享锁,其他事务可以读但不能修改这行数据 SELECT * FROM user_account WHERE id = 1 FOR SHARE; -- 对id为2的用户数据加排他锁,其他事务不能读(加锁读)也不能修改这行数据 SELECT * FROM user_account WHERE id = 2 FOR UPDATE; -- 事务提交后所有锁释放 COMMIT;
间隙锁的默认策略
在REPEATABLE READ隔离级别下,InnoDB默认会开启间隙锁(Gap Lock),当使用范围条件检索数据并加锁时,不仅会锁住符合条件的现有数据行,还会锁住这个范围之间的间隙,防止其他事务在这个范围内插入新数据,从而解决幻读问题。如果是唯一索引的等值查询,间隙锁会自动退化,只锁住对应的行。
实际场景中的选择建议
如果只是普通的增删改操作,不需要手动加锁,依赖InnoDB的隐式锁定即可,避免不必要的显式加锁增加锁冲突的概率。如果需要在读取数据后就确保这些数据不会被其他事务修改,比如先查询库存再扣减的场景,可以使用SELECT ... FOR UPDATE显式加排他锁,保证库存操作的原子性。
需要注意的是,显式锁定同样要遵循最小锁定范围的原则,尽量通过索引条件查询需要锁定的数据,避免因为全表扫描导致锁定范围过大,影响整个表的并发性能。