MySQL的锁机制是其核心特性之一,用于管理对共享资源的并发访问,确保数据的一致性和完整性。 不同的存储引擎(如InnoDB和MyISAM)支持不同粒度的锁。
一、MySQL锁的分类
MySQL的锁可以从多个维度进行分类,主要可以分为按粒度划分、按属性/模式划分以及按算法划分。
1. 按锁定的粒度划分
这是最常见的分类方式,指锁定的资源范围大小。
| 锁类型 | 描述 | 优点 | 缺点 | 适用引擎 |
|---|---|---|---|---|
| 全局锁 (Global Lock) | 锁定整个数据库实例。一个典型命令是 FLUSH TABLES WITH READ LOCK,常用于数据库备份。 | 可以方便地进行整库备份,确保数据一致性。 | 开销大,会阻塞所有更新操作,对业务影响大。 | 所有引擎 |
| 表级锁 (Table-level Lock) | 直接锁定整张表。 | 开销小,加锁快,不会出现死锁。 | 锁定粒度大,并发冲突概率高,并发度最低。 | MyISAM, InnoDB |
| 页级锁 (Page-level Lock) | 锁定数据页,粒度介于表锁和行锁之间。 | 开销和并发能力介于表锁和行锁之间。 | 会出现死锁,目前已较少使用。 | BDB引擎 |
| 行级锁 (Row-level Lock) | 仅锁定被操作的行记录。 | 锁定粒度最小,并发冲突概率最低,并发度最高。 | 开销大,加锁慢,可能会出现死锁。 | InnoDB |
注意:
- MyISAM 存储引擎主要使用表级锁,因此并发写入性能较差。
- InnoDB 存储引擎同时支持表级锁和行级锁,默认使用行级锁,提供了更好的并发性能。
2. 按属性/模式划分
这描述了锁的兼容性。
| 锁类型 | 别名 | 描述 | 兼容性 |
|---|---|---|---|
| 共享锁 (Shared Lock) | S锁, 读锁 | 允许多个事务同时读取同一资源,但阻止其他事务获取排他锁。 事务可以通过 SELECT ... LOCK IN SHARE MODE (MySQL 8.0 之前) 或 SELECT ... FOR SHARE (MySQL 8.0 及之后) 显式地为记录加共享锁。 | 与其他共享锁(S锁)兼容,与排他锁(X锁)互斥。 |
| 排他锁 (Exclusive Lock) | X锁, 写锁 | 只允许持有该锁的事务对资源进行读写,阻止其他任何事务获取该资源的任何锁。 INSERT, UPDATE, DELETE 操作会自动为涉及的数据行加上排他锁。 也可以通过 SELECT ... FOR UPDATE 显式添加。 | 与任何其他锁(S锁或X锁)都互斥。 |
| 意向锁 (Intention Lock) | IS锁, IX锁 | 意向锁是表级锁,用于表示事务打算在表中的某些行上设置行级锁(共享或排他)。 它能让InnoDB在判断表级锁兼容性时,无需扫描整个表的行级锁,从而提高效率。它分为意向共享锁(IS)和意向排他锁(IX)。 | 意向锁之间是互相兼容的。 |
3. 按算法划分 (InnoDB特有)
InnoDB的行级锁是基于索引实现的,其具体实现分为以下几种。
| 锁类型 | 描述 | 目的 |
|---|---|---|
| 记录锁 (Record Lock) | 这是标准的行锁,它锁定的是索引记录本身。 | 精确地锁定某一条记录,防止被其他事务修改。 |
| 间隙锁 (Gap Lock) | 锁定一个索引记录之间的“间隙”,但不包括记录本身。 | 防止其他事务在这个间隙中插入新的记录,从而避免“幻读”问题。这主要在**可重复读(REPEATABLE READ)**隔离级别下起作用。 |
| 临键锁 (Next-Key Lock) | 它是记录锁和间隙锁的组合,锁定一个范围,并包括记录本身(左开右闭区间)。 InnoDB在可重复读隔离级别下默认使用临键锁。 | 既能锁定记录本身,又能防止幻读,是InnoDB默认的行锁算法。 |
| 插入意向锁 (Insert Intention Lock) | 一种特殊的间隙锁,在INSERT操作之前设置。 它表明有事务想要在某个间隙中插入数据,但只有在不与现有间隙锁或临键锁冲突时才会成功。 | 提高并发插入的性能。 |
二、如何查询和监控锁信息
当出现查询缓慢、事务阻塞等问题时,查看当前的锁信息至关重要。
1. 使用 SHOW 命令
查看表是否被锁:
sqlSHOW OPEN TABLES WHERE In_use > 0;如果
In_use列的值大于0,说明该表当前被加了表锁或正在被某个事务使用。查看InnoDB引擎状态: 这是一个功能非常强大的命令,可以提供详细的InnoDB运行状态,包括最近的死锁信息、事务和锁等待情况。
sqlSHOW ENGINE INNODB STATUS\G在输出结果中,重点关注
LATEST DETECTED DEADLOCK和TRANSACTIONS部分。
2. 查询系统信息库 (information_schema / performance_schema)
MySQL将锁的相关信息存储在系统表中,通过查询这些表可以获得更精确的锁数据。
对于 MySQL 5.7 及更早版本: 主要使用
information_schema库中的表。sql-- 查看当前所有事务 SELECT * FROM information_schema.INNODB_TRX; -- 查看当前出现的锁 SELECT * FROM information_schema.INNODB_LOCKS; -- 查看锁等待关系 SELECT * FROM information_schema.INNODB_LOCK_WAITS;对于 MySQL 8.0 及更高版本: 锁相关的信息被移到了
performance_schema库中,提供了更高效和详细的监控。information_schema.INNODB_LOCKS等表已被废弃。sql-- 查看当前出现的锁 SELECT * FROM performance_schema.data_locks; -- 查看锁等待关系 SELECT * FROM performance_schema.data_lock_waits;以下是一个查询锁等待的实用SQL语句(适用于MySQL 8.0):
sqlSELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query FROM performance_schema.data_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id;
总结与建议
- 选择合适的存储引擎:对于需要高并发和事务支持的场景,应优先选择 InnoDB。 对于读密集且不需要事务的场景,MyISAM 可能是个不错的选择。
- 优化SQL和索引:InnoDB的行锁是基于索引的,如果SQL查询没有命中索引,可能会导致行锁升级为表锁,从而大大降低并发性能。 因此,合理设计索引至关重要。
- 控制事务大小:尽量保持事务简短,减少锁的持有时间,可以有效降低锁冲突的概率。