详解MySQL锁机制
MySQL 的锁机制是数据库并发控制的核心,InnoDB 存储引擎提供了多种锁类型来保证数据的一致性和完整性。下面我将系统性地讲解 MySQL 的锁机制。
1. 锁的基本分类
1.1 按锁的粒度划分
锁类型 | 描述 | 特点 | 常见存储引擎 |
---|---|---|---|
表锁 | 锁定整张表 | 开销小,加锁快;并发度低 | MyISAM, InnoDB等 |
行锁 | 锁定索引记录 | 开销大,加锁慢;并发度高 | InnoDB |
页锁 | 锁定一页(介于表锁和行锁之间) | 已弃用,被行锁取代 | BDB(已淘汰) |
-
为什么表锁开销小?
-
表锁只需在内存中维护一个锁结构(整个表1个锁)。
-
行锁需要为每行/每个索引条目维护锁结构(可能成千上万个锁)。
-
-
为什么行锁并发度高?
-
表锁:任何写操作会阻塞其他所有读写操作(如
UPDATE
会阻塞所有SELECT
)。 -
行锁:只有访问同一行的事务会相互阻塞(不同行可并发操作)。
-
-
InnoDB的锁实现特点:
-
默认使用行锁,但无索引或索引失效时会退化为表锁(例如:
UPDATE table WHERE non_index_column=1
)。 -
意向锁(IS/IX)是表级锁,用于快速判断表中是否有行被锁定。
-
-
如何避免锁升级?
-
确保查询走索引:通过
EXPLAIN
验证。 -
避免长事务:长时间未提交的事务会持有锁。
-
-
经典案例验证
-- 会话1(使用行锁,有索引) BEGIN; UPDATE users SET name='张三' WHERE id=1; -- 只锁定id=1的行-- 会话2(并发操作不受影响) UPDATE users SET name='李四' WHERE id=2; -- 正常执行-- 会话3(无索引退化为表锁) UPDATE users SET name='王五' WHERE name='老用户'; -- 若name无索引,会锁定整个表
1.2 按锁的性质划分
锁类型 | 描述 | 兼容性 |
---|---|---|
共享锁(S) | 读锁,允许多个事务同时读取 | 与S锁兼容,与X锁互斥 |
排他锁(X) | 写锁,独占锁,阻止其他任何锁 | 与所有锁都互斥 |
意向锁 | 表级锁,表明事务将要锁定某些行 | IS/IX之间兼容 |
2. InnoDB 行锁详解
2.1 记录锁(Record Lock)
-
锁定索引中的单条记录
-
通过索引条件精确匹配时触发
-- 对id=1的记录加X锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;
2.2 间隙锁(Gap Lock)
-
锁定索引记录之间的间隙
-
防止幻读问题的关键
-
只在REPEATABLE READ隔离级别下有效
-- 锁定id在(5,10)区间的间隙
SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
2.3 临键锁(Next-Key Lock)
-
记录锁 + 间隙锁的组合
-
锁定记录及记录前面的间隙
-
InnoDB默认的行锁算法
-- 锁定id>=5的记录及前面的间隙
SELECT * FROM users WHERE id >= 5 FOR UPDATE;
2.4 插入意向锁(Insert Intention Lock)
-
特殊的间隙锁
-
多个事务在相同间隙插入不同数据时不互相阻塞
3. 表级锁
3.1 意向锁(Intention Lock)
-
表级锁,表明事务将要锁定表中的某些行
-
类型:
-
IS(意向共享锁):事务打算在某些行上加S锁
-
IX(意向排他锁):事务打算在某些行上加X锁
-
3.2 自增锁(AUTO-INC Lock)
-
特殊表锁,用于自增列插入
-
保证自增ID连续且唯一
3.3 元数据锁(MDL)
-
系统自动加锁,保护表结构
-
访问表时会自动加MDL读锁
-
修改表结构时会加MDL写锁