全局锁
定义
全局锁(Global Lock)是指在 MySQL 数据库中对整个数据库实例进行的锁定。当一个全局锁被施加时,所有其他的查询和操作都会被阻塞,直到锁被释放。这种锁定通常用于维护数据库的完整性,确保在进行特定操作时数据库处于一致状态。
MySQL 主要通过 FLUSH TABLES WITH READ LOCK
命令来实现全局锁。此命令会阻止任何写入操作,而允许读取操作,从而在某些需要全局查看数据的情况下保持数据的一致性。
SQL
加锁
flush table with read lock;
释放锁
unlock tables;
常出现的情况
-
备份:
- 在进行逻辑备份(使用
mysqldump
)时,为了确保备份过程中的数据一致性,可能会使用全局锁。通过锁定数据库,确保在备份期间数据不被修改。
- 在进行逻辑备份(使用
-
维护操作:
- 在执行需要对数据库进行结构修改的操作(如修改表结构、删除表等)时,可能会需要全局锁来确保这些操作不会被其他正在执行的事务影响。
-
高并发环境:
- 在高并发访问的环境中,某些操作可能需要全局锁以保证数据的完整性和一致性。
为何会出现全局锁
全局锁会在以下情况下出现:
-
人为触发:
- 通过执行特定的 SQL 命令(如
FLUSH TABLES WITH READ LOCK
)来施加全局锁。
- 通过执行特定的 SQL 命令(如
-
故障恢复:
- 在某些情况下,需要锁定数据库以进行故障恢复和数据修复。全局锁可以确保在处理过程中不再有其他事务对数据进行修改。
-
数据一致性需求:
- 在一些特定应用场景中,为了保持数据一致性,可能需要在某个时刻锁定所有数据。
如果不加锁会出现什么问题
-
数据不一致性:
- 如果在备份或维护期间不加锁,可能会导致备份的数据与数据库当前状态不一致。例如,在备份时,如果数据正在被修改,备份得到的数据可能是部分更新的数据,无法反映数据库的真实状态。
-
死锁:
- 在高并发环境中,如果多个事务在不同的顺序上请求锁,可能会导致死锁,从而使得某些事务无法完成。
-
数据损坏:
- 某些数据库操作(如表结构修改)如果在写操作进行时执行,可能会导致数据损坏或丢失。
-
难以恢复操作:
- 如果在进行数据维护时不加锁,且遇到错误或异常,可能会导致数据处于不一致的状态,恢复操作将变得复杂。
具体示例
假设有一个在线购物系统,用户不断进行商品的购买和库存的更新操作。在备份期间,如果不加全局锁,可能会发生以下情况:
- 用户 A 正在购买一件商品,库存从 10 减少到 9。
- 同时,备份操作也在进行,备份过程中的库存仍然是 10。
- 一旦备份完成,用户 A 的购买事务提交,库存变为 9,而备份中的数据仍然反映库存为 10。这就导致了数据的不一致性。
注意
数据库中加全局锁,是一个比较重的操作,存在以下问题
1.如果主库进行备份,那么备份期间都不能执行更新,业务基本属于停摆状态
2.如果再从库进行备份,那么备份期间从库无法执行主库同步过来的二进制文件(binlog),会导致主从延迟
再InnoDB引擎中,我们可以在备份时加上 --single-transaction 参数来完成不加锁的一致性数据库备份
表级锁
1. 表锁(Table Lock)
定义
表锁是对整个表施加的锁,主要用于控制对表的读写访问。表锁有两种类型:
- 共享读锁/读锁(READ LOCK):允许多个事务并发读取表,但不允许任何事务修改表。
- 表独占写锁/写锁(WRITE LOCK):只允许一个事务修改表,并阻止其他事务读取或修改。
SQL
加锁
lock tables 表名 read/write
释放锁
unlock tables;
-- 或者关闭连接客户端锁会自动释放
常见情况
- 大规模数据导入:在进行数据导入时,可以使用表锁来防止其他操作对表的修改,以确保数据的完整性。
- 维护操作:在对表进行结构修改、删除等操作时,通常会使用表锁以确保没有其他事务干扰。
出现原因
表锁的出现主要是为了确保在某些关键操作期间,表的状态不会被其他事务改变,从而保证操作的原子性和一致性。
不加锁可能出现的问题
- 数据不一致性:在进行复杂的读写操作时,如果不使用表锁,其他事务可能会修改正在操作的数据,导致数据的不一致性。
- 错误结果:在读取数据期间,其他事务可能会对数据进行修改,导致返回的数据与预期不符。
2. 元数据锁(Metadata Lock,MDL)
定义
元数据加锁过程是系统自动控制的,无需显示使用,在访问一张表的时候会自动加上,元数据锁主要作用是维护表元数据得数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。(避免DML和DDL冲突,保证读写的正确性
)在MySQL 5.5中引入了元数据锁,当对一张表进行增删改查时,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)
常见情况
- 表结构变更:在执行
ALTER TABLE
、DROP TABLE
或者CREATE INDEX
等操作时,元数据锁会被触发。 - 事务中的DDL操作:当一个事务对表进行 DDL 操作时,元数据锁可以防止其他事务对该表进行任何 DML 操作。
出现原因
元数据锁的存在是为了保护数据库的结构不被同时修改,确保在执行 DDL 操作时不会影响到正在进行的 DML 操作。
不加锁可能出现的问题
- 元数据损坏:如果在对表执行 DDL 操作时不加锁,可能导致数据字典的损坏,进而影响数据库的稳定性和可用性。
- 不一致的表结构:在读取表结构或进行其他操作时,如果没有适当的锁机制,可能导致表结构不一致,影响查询和数据修改的正确性。
3. 意向锁(Intent Lock)
意向锁是 MySQL 中的一种重要锁机制,主要用于协调行级锁和表级锁之间的关系。通过意向锁,数据库可以在不直接锁定整个表的情况下,指示某个事务将要对表中的特定行进行锁定。这种机制能够有效避免死锁和锁竞争,提升并发性能。
查看意向锁
select * from performance_schema.data_locks;
意向锁的种类
-
意向共享锁(IS):表示事务希望在某个行上获取共享锁。
由语句 select … lock in share mode添加
-
意向排他锁(IX):表示事务希望在某个行上获取排他锁。
由insert、update、delete、select … for update 添加
兼容性
表共享锁(read) | 表锁排他锁(write) | |
---|---|---|
意向共享锁(IS) | 兼容 | 互斥 |
意向排他锁(IX) | 互斥 | 互斥 |
意向锁直接不存在互斥
好的,我们可以通过一个更通俗易懂的例子来说明意向锁(Intent Lock)的工作机制,同时结合数据表格的形式来帮助理解。
场景描述
假设我们有一个名为 employees
的表,包含以下字段:
ID | Name | Salary |
---|---|---|
1 | Alice | 60000 |
2 | Bob | 55000 |
3 | Charlie | 70000 |
4 | David | 50000 |
5 | Eva | 65000 |
现在,我们有两个事务(Transaction A 和 Transaction B)需要对这个表进行操作。我们将通过这个例子来展示意向锁的必要性及其作用。
假设无意向锁
- 事务 A 开始,事务A欲更新 id为 3 的数据,对数据行加了一个行锁
- 此时事务B 想要对表加表锁,需要扫描表中数据去判断表中是否有锁,如果有所需要判断锁的类型从而判定能否加锁,因存在全表扫描效率很低
存在意向锁
- 事务 A 开始,事务A欲更新 id为 3 的数据,对数据行加了一个行锁,对表加一个意向锁
- 此时事务B 想要对表加表锁,判断表的意向锁和想要加的锁是否兼容,如果兼容则加锁成功,如果不兼容则出现阻塞,阻塞到事务A完成
结果分析
-
避免死锁:
- 由于意向锁的存在,事务 B 清楚地知道它不能直接对该表加锁。这样可以避免它与事务 A 之间可能出现的死锁情况。
-
提高并发性能:
- 如果没有意向锁,事务 B 可能会在没有必要的情况下等待整个表的锁,这会导致性能下降。在意向锁的机制下,事务 B 知道需要等待事务 A 先完成,才能安全地进行操作。
行级锁
1. 行级锁(Row-Level Lock)
定义
行级锁是数据库中最细粒度的锁定机制,允许多个事务同时在同一张表中操作不同的行。行级锁提升了数据库的并发性,因为它允许多个事务同时进行,而不互相干扰。
特点
- 高并发:多个事务可以同时对不同的行进行操作。
- 低锁争用:由于锁定的范围较小,行级锁减少了锁竞争。
示例
假设我们有一个 employees
表,如下所示:
ID | Name | Salary |
---|---|---|
1 | Alice | 60000 |
2 | Bob | 55000 |
3 | Charlie | 70000 |
-
事务 A 更新 ID = 1 的记录:
START TRANSACTION; UPDATE employees SET Salary = 65000 WHERE ID = 1;
-
事务 B 更新 ID = 2 的记录:
START TRANSACTION; UPDATE employees SET Salary = 60000 WHERE ID = 2;
在这个示例中,事务 A 和事务 B 可以同时进行,因为它们操作的是不同的行(ID = 1 和 ID = 2)。这展示了行级锁的高并发性。
2. 行级锁的间隙锁(Gap Lock)
定义
间隙锁是一种专门的锁定机制,主要用于防止幻读。在执行范围查询时,间隙锁锁定了行与行之间的“间隙”,以防止其他事务在这个间隙中插入新的行。
特点
- 防止幻读:间隙锁可以防止其他事务插入新行,从而避免在同一事务中多次查询时得到不同的结果。
- 较高的锁定范围:除了锁定实际的行,间隙锁还锁定了行之间的空间。
示例
继续使用 employees
表,假设我们有以下记录:
ID | Name | Salary |
---|---|---|
1 | Alice | 60000 |
2 | Bob | 55000 |
3 | Charlie | 70000 |
- 事务 A 执行以下查询,查找 ID 在 1 到 2 之间的员工:
START TRANSACTION; SELECT * FROM employees WHERE ID BETWEEN 1 AND 2 FOR UPDATE;
此时,事务 A 会在 ID = 1 和 ID = 2 的行之间加上间隙锁,这样任何其他事务都不能在 ID = 1 和 ID = 2 之间插入新行。
- 事务 B 尝试插入 ID = 1.5 的记录:
INSERT INTO employees (ID, Name, Salary) VALUES (1.5, 'David', 50000);
由于事务 A 在 ID = 1 和 ID = 2 之间加了间隙锁,事务 B 将被阻塞,直到事务 A 提交或回滚。
3. 临表锁(Table Lock)
定义
临表锁是对整个表的锁定机制,通常用于需要对表中的所有行进行操作的情况。临表锁分为共享锁(S)和排他锁(X)。
特点
- 锁定范围大:锁定整个表,所有对该表的读写操作都会被阻塞,直到锁被释放。
- 较低的并发性:由于锁定整个表,临表锁可能导致其他事务的等待,从而降低并发性能。
示例
使用 employees
表:
ID | Name | Salary |
---|---|---|
1 | Alice | 60000 |
2 | Bob | 55000 |
3 | Charlie | 70000 |
-
事务 A 需要对整个表加排他锁进行一些批量更新操作:
START TRANSACTION; LOCK TABLES employees WRITE; UPDATE employees SET Salary = Salary * 1.1; -- 可能是给所有员工加薪 COMMIT;
-
事务 B 尝试在事务 A 期间读取
employees
表:START TRANSACTION; SELECT * FROM employees;
由于事务 A 已经对 employees
表加了临表锁,事务 B 将被阻塞,直到事务 A 提交或回滚。
4. 临键锁(Next-Key Lock)
定义
临键锁(Next-Key Lock)是一种结合了行级锁和间隙锁的锁定机制。它不仅锁定了记录行本身,还锁定了行与行之间的间隙,这种锁的设计目的是为了防止幻读。
特点
- 组合锁定:同时锁定行和行之间的间隙,以防止其他事务在这个间隙中插入新记录。
- 防止幻读:通过锁定间隙,确保在一个事务内的多次查询结果一致。
示例
使用 employees
表,结构如下:
ID | Name | Salary |
---|---|---|
1 | Alice | 60000 |
2 | Bob | 55000 |
3 | Charlie | 70000 |
假设我们有两个事务(Transaction A 和 Transaction B)进行如下操作:
事务 A 的操作
- 事务 A 执行一个范围查询,并希望获取 ID 在 1 到 3 之间的员工记录:
START TRANSACTION; SELECT * FROM employees WHERE ID BETWEEN 1 AND 3 FOR UPDATE;
此时,事务 A 会对 ID = 1 和 ID = 3 的行加上临键锁,同时也会锁定 ID = 1 和 ID = 2 之间的间隙。
事务 B 的操作
- 事务 B 尝试插入一个新的记录,ID = 2.5:
START TRANSACTION; INSERT INTO employees (ID, Name, Salary) VALUES (2.5, 'David', 50000);
由于事务 A 已经对 ID = 1 和 ID = 3 的行加了锁,并且锁定了 ID = 1 和 ID = 2 之间的间隙(因为 ID = 2 是范围查询的结束条件),事务 B 将会被阻塞,无法在 ID = 1 和 ID = 2 之间插入新行。
总结
- 行级锁 提供了较高的并发性,允许多个事务同时操作不同的行。
- 间隙锁 用于防止幻读,锁定行之间的间隙,从而避免新行的插入。
- 临表锁 锁定整个表,通常用于需要对表进行批量操作的场景,但可能导致较低的并发性。
- 临键锁 结合了行级锁和间隙锁的特性,既锁定指定的行,也防止在行与行之间插入新的记录,从而有效防止幻读。
这些锁机制在数据库的并发控制中起着至关重要的作用,通过合理地使用它们,可以确保数据的一致性和完整性。