锁是数据库并发控制的核心机制,用于协调多个会话对共享资源的访问,防止并发操作导致的数据不一致问题。MySQL提供了多种锁机制,适用于不同的存储引擎和业务场景,合理使用锁可以平衡并发性能和数据一致性。
一、锁的基本概念
1. 锁的作用
- 保证数据一致性:防止多个事务同时修改同一数据导致的异常
- 支持并发控制:允许多个事务安全地同时操作数据库
- 解决隔离问题:实现事务隔离级别,避免脏读、不可重复读和幻读
2. 锁的分类维度
- 按锁的粒度:表级锁、行级锁、页级锁
- 按锁的模式:共享锁(读锁)、排他锁(写锁)
- 按锁的实现方式:隐式锁、显式锁
- 按操作类型:DML锁、DDL锁
3. 锁与事务隔离级别的关系
不同的事务隔离级别通过不同的锁机制实现:
- 读未提交:基本不加锁
- 读已提交:行级锁,事务提交后释放
- 可重复读:行级锁+间隙锁,保证事务期间数据一致性
- 串行化:表级锁,完全串行执行事务
二、按锁的粒度分类
1. 表级锁
表级锁是对整个表施加的锁,锁定粒度大,开销小,加锁快,并发度低。
(1)特点
- 锁定整个表,任何对该表的操作都需要等待锁释放
- 加锁和释放锁的速度快,资源消耗少
- 容易产生锁冲突,并发性能较差
(2)支持的存储引擎
MyISAM、InnoDB、MEMORY等所有存储引擎都支持表级锁,其中MyISAM只支持表级锁。
(3)表级锁的类型
① 表共享读锁(Table Read Lock):
多个会话可以同时获取读锁,只能读取数据,不能修改。
-- 加读锁 LOCK TABLES 表名 READ; -- 释放锁 UNLOCK TABLES;
② 表独占写锁(Table Write Lock):
只有一个会话可以获取写锁,持有写锁的会话可以读写数据,其他会话不能读写。
-- 加写锁 LOCK TABLES 表名 WRITE; -- 释放锁 UNLOCK TABLES;
(4)适用场景
- 读多写少的场景(如静态数据查询)
- 全表操作(如批量更新、表结构修改)
- 不支持行级锁的存储引擎(如MyISAM)
2. 行级锁
行级锁是对表中单行记录施加的锁,锁定粒度小,开销大,加锁慢,并发度高。
(1)特点
- 只锁定需要操作的行,其他行不受影响
- 加锁和释放锁的速度慢,资源消耗多
- 锁冲突少,并发性能好
- 可能产生死锁
(2)支持的存储引擎
主要由InnoDB存储引擎支持,是InnoDB实现高并发的核心机制。
(3)行级锁的类型
① 共享锁(S锁,Shared Lock):
允许事务读取一行数据,多个事务可以同时持有同一行的共享锁。
-- 加共享锁 SELECT * FROM 表名 WHERE 条件 LOCK IN SHARE MODE;
② 排他锁(X锁,Exclusive Lock):
允许事务更新或删除一行数据,同一行只能有一个排他锁,且与共享锁互斥。
-- 加排他锁 SELECT * FROM 表名 WHERE 条件 FOR UPDATE;
(4)InnoDB的隐式加锁机制
InnoDB会自动为DML操作加锁:
- INSERT:对插入的新行加排他锁
- UPDATE:对更新的行加排他锁
- DELETE:对删除的行加排他锁
- SELECT:默认不加锁(快照读)
(5)适用场景
- 写操作频繁的场景
- 并发量高的OLTP系统
- 需要精细控制锁定范围的场景
3. 页级锁
页级锁是介于表级锁和行级锁之间的锁,锁定一个数据页(通常为16KB)。
(1)特点
- 锁定粒度介于表级锁和行级锁之间
- 并发性能中等
(2)支持的存储引擎
主要由BDB存储引擎支持,MySQL中使用较少。
三、InnoDB的特殊锁机制
1. 意向锁(Intention Locks)
意向锁是表级锁,用于表示事务即将对表中的行施加共享锁或排他锁,提高加锁效率。
(1)类型
- 意向共享锁(IS):事务打算对表中的某些行加共享锁
- 意向排他锁(IX):事务打算对表中的某些行加排他锁
(2)作用
在加表级锁前,先检查意向锁,避免逐行检查行级锁,提高加锁效率。
(3)兼容性
- 意向锁之间相互兼容
- 意向共享锁(IS)与表共享锁(S)兼容,与表排他锁(X)互斥
- 意向排他锁(IX)与表共享锁(S)和表排他锁(X)都互斥
2. 间隙锁(Gap Locks)
间隙锁是对索引记录之间的间隙或索引记录前后的间隙施加的锁,防止其他事务在间隙中插入数据,解决幻读问题。
(1)特点
- 锁定的是范围,而不是具体的行
- 只在InnoDB的可重复读(REPEATABLE READ)隔离级别下生效
- 主要用于防止幻读
(2)示例
-- 表中有id为10、20、30的记录 -- 对id在10到30之间的记录加锁 SELECT * FROM users WHERE id BETWEEN 10 AND 30 FOR UPDATE; -- 此时无法在id=10到30之间插入新记录(如id=15) INSERT INTO users (id) VALUES (15); -- 会阻塞,直到锁释放
3. 临键锁(Next-Key Locks)
临键锁是行级锁和间隙锁的组合,锁定索引记录本身和其前面的间隙,是InnoDB在可重复读隔离级别下的默认锁机制。
(1)特点
- 包含索引记录本身的行锁和该记录前面的间隙锁
- 是InnoDB防止幻读的主要机制
(2)示例
-- 表中有id=10、20的记录 -- 临键锁会锁定id=20的行和10-20之间的间隙 SELECT * FROM users WHERE id = 20 FOR UPDATE;
4. 插入意向锁(Insert Intention Locks)
插入意向锁是一种特殊的间隙锁,用于表示插入意图,多个事务可以在同一间隙中插入不同位置的记录而不会冲突。
示例
-- 事务1锁定id=10到30的间隙 SELECT * FROM users WHERE id BETWEEN 10 AND 30 FOR UPDATE; -- 事务2可以插入id=15的记录 INSERT INTO users (id) VALUES (15); -- 事务3可以插入id=25的记录,不会与事务2冲突 INSERT INTO users (id) VALUES (25);
四、锁的状态与管理
1. 查看锁状态
-- 查看当前锁等待情况(MySQL 5.7+) SELECT * FROM performance_schema.data_locks; SELECT * FROM performance_schema.data_lock_waits; -- 查看事务和锁信息(旧版本) SHOW ENGINE INNODB STATUS;
2. 锁等待超时设置
-- 查看当前锁等待超时时间(默认50秒) SELECT @@innodb_lock_wait_timeout; -- 修改锁等待超时时间(会话级别) SET innodb_lock_wait_timeout = 10; -- 修改锁等待超时时间(全局级别) SET GLOBAL innodb_lock_wait_timeout = 10;
3. 死锁处理
死锁是指两个或多个事务相互等待对方释放锁而陷入无限等待的状态。
(1)InnoDB的死锁处理机制
InnoDB会自动检测死锁,选择回滚代价较小的事务来解除死锁。
(2)避免死锁的方法
- 按固定顺序访问表和行
- 尽量缩短事务长度,减少锁持有时间
- 降低事务隔离级别(如从可重复读改为读已提交)
- 避免在事务中执行复杂的查询和不必要的操作
(3)示例:死锁场景
-- 事务1 START TRANSACTION; UPDATE users SET name = 'A' WHERE id = 1; -- 事务2 START TRANSACTION; UPDATE users SET name = 'B' WHERE id = 2; -- 事务1继续执行,等待事务2释放id=2的锁 UPDATE users SET name = 'A' WHERE id = 2; -- 事务2继续执行,等待事务1释放id=1的锁 UPDATE users SET name = 'B' WHERE id = 1; -- 此时发生死锁,InnoDB会自动回滚其中一个事务
五、锁的使用最佳实践
1. 选择合适的锁粒度
- 并发度要求高、写操作频繁:使用行级锁(InnoDB)
- 读多写少、全表操作多:可以考虑表级锁
2. 合理设计索引
- 行级锁依赖索引,没有索引会导致表级锁
- 优化查询语句,确保锁只作用于必要的行
3. 控制事务长度
- 尽量缩短事务执行时间,减少锁持有时间
- 避免在事务中执行用户交互操作
4. 优化锁的使用方式
- 避免显式加锁,优先使用InnoDB的隐式锁机制
- 必要时使用SELECT ... FOR UPDATE代替UPDATE,提前锁定资源
- 合理设置隔离级别,在一致性和并发性能之间平衡
5. 监控和调优
- 定期监控锁等待情况,识别锁冲突热点
- 优化高频冲突的SQL语句
- 考虑分库分表,减少单表锁竞争
六、不同存储引擎的锁机制对比
1. InnoDB:支持行级锁、表级锁、意向锁、间隙锁等,适合高并发场景
2. MyISAM:只支持表级锁,读锁共享,写锁排他,适合读多写少场景
3. MEMORY:支持表级锁,与MyISAM类似
4. BDB:支持页级锁,使用较少
选择存储引擎时,锁机制是重要考虑因素,InnoDB因其灵活的锁机制成为大多数OLTP系统的首选。
MySQL锁机制详解
2245

被折叠的 条评论
为什么被折叠?



