MySQL各种锁详解
前言
锁是计算机在执行多线程或事务时用于并发访问同一共享资源的同步机制。MySQL中的锁在服务器层或存储引擎层实现,保证了数据访问的一致性和有效性。
MySQL锁可按以下方式分类:
- 模式:乐观锁与悲观锁
- 粒度:全局锁、表级锁、页级锁、行级锁
- 属性:共享锁、排它锁
- 状态:意向共享锁、意向排它锁
- 算法:间隙锁、临键锁、记录锁
全局锁
什么是全局锁?
全局锁是针对整个数据库的锁,主要包括读锁(共享锁)和写锁(排它锁)。
- 读锁(共享锁):阻止其他用户更新数据,但允许读取数据。适用于需要在一段时间内保持数据一致性的场景。
- 写锁(排它锁):阻止其他用户读取和更新数据。适用于需要修改大量数据且不希望被干扰的场景。
应用场景
- 全库备份:使用全局锁确保备份过程中所有表保持一致。例如,
FLUSH TABLES WITH READ LOCK
可阻止写操作。 - 整体数据迁移:在迁移过程中阻止写操作,确保数据正确迁移到新服务器。
- 全库只读:在维护或防止数据破坏时将数据库设为只读模式。
实现方式
使用FLUSH TABLES WITH READ LOCK
添加全局读锁,UNLOCK TABLES
释放锁。
风险
- 高开销:阻止所有数据修改操作,在高并发环境下可能导致大量线程等待。
- 性能问题:过度或不当使用全局锁可能导致性能下降。
建议:尽量避免在生产环境中使用全局锁,或减少锁持有时间。优先使用更细粒度的锁,如行锁或表锁。
表级锁
什么是表级锁?
表级锁是MySQL中最基本的锁策略,主要用于MyISAM存储引擎。特点是开销小、加锁快、无死锁,但锁粒度大,冲突概率高,并发度低。
类型
- 表共享读锁:允许读取操作,禁止写操作,读锁间不相互阻塞。
- 表独占写锁:允许读写操作,但其他事务无法操作该表。
应用场景
- 读密集型应用:读操作多,写操作少时,表级读锁支持高并发读。
- 写操作不频繁:写操作稀少或可容忍延迟时适用。
- 数据量小的应用:表级锁对小表的性能影响较小。
- 全表更新或删除:适合全表操作,如删除或更新所有记录。
触发表级锁的命令
ALTER TABLE
:修改表结构时锁定整个表。DROP TABLE
和TRUNCATE TABLE
:删除表或清空数据时锁定表。LOCK TABLES
:显式加读锁或写锁,如LOCK TABLES t1 WRITE, t2 READ
。- MyISAM全表扫描:MyISAM表的全表或大范围扫描触发表锁。
FLUSH TABLES WITH READ LOCK
:为所有表加全局读锁。
注意:InnoDB主要使用行锁,但在某些操作(如ALTER TABLE
或LOCK TABLES
)可能使用表锁。MyISAM仅支持表锁。
风险
- 性能下降:锁定整个表,高并发下可能导致请求阻塞。
- 并发性能差:写锁阻塞所有操作,读锁阻塞写操作。
- 锁等待和超时:大粒度锁增加等待时间,可能导致超时。
- 死锁风险:多表操作中若无固定加锁顺序,可能引发死锁。
建议:高并发或写密集场景下,使用InnoDB的行锁以提升性能。
行级锁
什么是行级锁?
行级锁是MySQL中粒度最小的锁,主要由InnoDB存储引擎支持。相比表锁或页锁,行锁并发性能更好,冲突更少,但需要更多内存和CPU资源。
类型
- 共享锁(S锁):允许读取一行数据,禁止修改。
- 排他锁(X锁):允许读写一行数据,禁止其他事务访问。
- 间隙锁:锁定索引记录间的间隙,防止插入,适用于非唯一索引。
应用场景
- 高并发读写:支持多事务并发操作不同行。
- 单行操作:适合基于主键或唯一索引的更新、删除、插入。
- 短期锁:避免长时间阻塞其他事务。
- 复杂事务:确保多行操作的数据一致性。
- 并发控制:维护事务隔离和一致性。
触发行级锁的命令
SELECT ... FOR UPDATE
:为选定行加排他锁(X锁)。SELECT ... LOCK IN SHARE MODE
:为选定行加共享锁(S锁)。INSERT
、UPDATE
、DELETE
:为受影响的行加排他锁。
注意:行锁仅在事务中有效(BEGIN
至COMMIT
/ROLLBACK
)。非事务环境中,锁在语句执行后立即释放。锁粒度取决于WHERE
子句使用的索引。
风险
- 死锁:事务相互等待锁资源可能导致死锁。
- 锁升级:锁过多行可能升级为表锁,增加冲突。
- 锁等待:阻塞事务可能降低性能。
- 资源消耗:大表或多事务增加内存和CPU使用。
- 调试复杂:细粒度锁使性能问题排查更困难。
建议:优化索引、事务管理和隔离级别以降低风险。
乐观锁
什么是乐观锁?
乐观锁假设并发冲突概率低,操作数据时不加锁,提交时检查是否有其他事务修改数据。MySQL无内置乐观锁,但可通过版本号或时间戳实现。
实现方式
- 创建版本字段:
CREATE TABLE Products ( id INT PRIMARY KEY, name VARCHAR(50), quantity INT, version INT );
- 读取数据:获取数据及版本号。
SELECT id, name, quantity, version FROM Products WHERE id = 1;
- 更新数据:验证版本号一致性并更新。
若更新行数为0,说明版本不匹配,需回滚事务。UPDATE Products SET name = 'NewName', quantity = 10, version = version + 1 WHERE id = 1 AND version = :oldVersion;
应用场景
- 低冲突环境:并发修改同一数据的概率低。
- 读多写少:减少读操作的锁开销。
- 短事务:快速事务减少锁等待。
- 分布式系统:网络延迟降低冲突可能性。
- 互联网应用:如电商网站,读操作为主。
缺点
- 冲突频繁:高并发下可能导致大量回滚。
- 重试开销:回滚和重试增加系统负担。
- 版本管理:版本号错误可能导致冲突检测失败。
- 编程复杂:需处理冲突和重试逻辑。
建议:适用于低冲突、读密集场景,高冲突时考虑悲观锁。
悲观锁
什么是悲观锁?
悲观锁假设并发冲突概率高,读写数据前加锁以防止其他事务干扰,确保数据一致性。
实现方式
- 排他锁:
START TRANSACTION; SELECT * FROM Orders WHERE OrderID = 1 FOR UPDATE; UPDATE Orders SET Quantity = 10 WHERE OrderID = 1; COMMIT;
- 共享锁:
START TRANSACTION; SELECT * FROM Orders WHERE OrderID = 1 LOCK IN SHARE MODE; COMMIT;
适用场景
- 写操作频繁:写操作多,冲突概率高。
- 高冲突环境:减少重试,提高效率。
- 强一致性需求:如金融业务,确保数据准确性。
缺点
- 性能开销:加锁解锁增加系统负担。
- 并发度低:锁限制同时访问。
- 死锁:事务相互等待可能引发死锁。
- 锁超时:长时间锁持有可能导致超时。
建议:在高冲突、强一致性场景中使用,权衡并发性能。
共享锁与排他锁
共享锁
什么是共享锁?
共享锁允许多个事务同时读取数据,但禁止修改。其他事务可加共享锁但不可加排他锁。
实现方式
START TRANSACTION;
SELECT * FROM Orders WHERE OrderID = 1 LOCK IN SHARE MODE;
COMMIT;
应用场景
- 读一致性:确保读取期间数据不被修改。
- 读多写少:支持高并发读。
- 避免脏读:防止读取未提交数据。
- 乐观读:仅在更新时加锁,减少开销。
风险
- 写阻塞:写操作需等待共享锁释放,影响写密集场景性能。
排他锁
什么是排他锁?
排他锁允许事务读写数据,禁止其他事务访问。
实现方式
START TRANSACTION;
SELECT * FROM Orders WHERE OrderID = 1 FOR UPDATE;
UPDATE Orders SET Quantity = 10 WHERE OrderID = 1;
COMMIT;
应用场景
- 数据一致性:防止并发修改。
- 避免脏读/不可重复读:确保读结果稳定。
- 单行并发修改:管理多事务修改同一行。
风险
- 性能影响:阻塞其他事务,降低高并发场景性能。
意向锁
概念
意向锁是表级锁,用于协调行锁与表锁,支持多粒度锁并存。
作用
当事务持有行锁时,MySQL自动为表添加意向锁。申请表锁时,检查意向锁而非逐行检查,提升性能。
类型
- 意向共享锁(IS):表示事务将加共享行锁。
- 意向排他锁(IX):表示事务将加排他行锁。
兼容性
锁类型 | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 互斥 |
IX | 兼容 | 兼容 | 互斥 | 互斥 |
S | 兼容 | 互斥 | 兼容 | 互斥 |
X | 互斥 | 互斥 | 互斥 | 互斥 |
为什么是表级锁?
- 行锁需逐行检查,性能低。
- 表级意向锁只需一次判断,提升效率。
间隙锁、临键锁、记录锁
间隙锁
什么是间隙锁?
间隙锁是InnoDB的锁机制,锁定索引间的间隙而非具体行,防止新记录插入,确保一致性和隔离性。
类型
- 区间-区间间隙锁:锁定两索引键间的间隙。
- 区间-记录间隙锁:锁定索引键与记录间的间隙。
- 记录-区间间隙锁:锁定记录与索引键间的间隙。
应用场景
- 防止幻读:阻止锁范围内插入新行。
- 范围查询:确保查询期间无新行插入。
- 减少死锁:某些情况下避免死锁。
示例
START TRANSACTION;
SELECT * FROM Orders WHERE OrderID BETWEEN 1 AND 100 FOR UPDATE;
COMMIT;
缺点
- 性能影响:阻止插入,降低并发性能。
- 死锁风险:重叠范围可能引发死锁。
- 复杂性:理解和优化需深入知识。
- 过度锁定:可能锁定超出需要的数据。
临键锁
什么是临键锁?
临键锁是记录锁与间隙锁的组合,锁定一行及前面的间隙(左开右闭区间)。仅用于非唯一索引,防止幻读。
示例
对于age
列值[10, 24, 32, 45]
,临键锁包括:
(-∞, 10]
(10, 24]
(24, 32]
(32, 45]
(45, +∞]
应用场景
- 防止幻读:确保范围查询一致性。
- 范围查询与修改:维护更新期间一致性。
- 减少死锁:某些场景下降低死锁风险。
缺点
- 性能影响:锁定行和间隙,降低并发。
- 过度锁定:可能锁定不必要范围。
- 复杂性:优化需深入理解。
- 死锁风险:重叠范围可能引发死锁。
记录锁
什么是记录锁?
记录锁锁定InnoDB中的单个索引记录。若无主键或唯一索引,InnoDB使用隐藏聚簇索引加锁。
类型
- 共享锁(S锁):允许多事务读,禁止写。
- 排他锁(X锁):禁止其他事务读写。
应用场景
- 单行修改:确保更新期间独占访问。
- 单行查询:防止读取期间数据变化。
- 数据一致性:如转账操作,确保多行一致。
示例
SELECT * FROM Orders WHERE OrderID = 1 FOR UPDATE;
UPDATE Orders SET Status = 'Processed' WHERE OrderID = 1;
总结
MySQL锁策略的选择需根据应用的并发性、一致性和性能需求。全局锁适合全库操作但开销大;表锁简单但并发低;行锁并发高但资源消耗大;乐观锁适合低冲突场景,悲观锁确保高冲突一致性;间隙锁、临键锁、记录锁解决特定并发问题。需平衡锁粒度、隔离级别和负载特性以优化性能。
评论区