一、锁的概念
锁是计算机协调多个进程/线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题。锁冲突也是影响数据库并发访问性能的一个重要因素。由此可见锁对数据库的重要性。
MySQL 按锁粒度分三类:
- 全局锁:锁定数据库中所有表
- 表级锁:每次操作锁住整张表
- 行级锁:每次操作锁住对应的行数据
二、查看数据库中的锁信息
select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;
三、全局锁
3.1 概念
全局锁:对整个数据库实例加锁,加锁后整个实例处于只读状态,其它线程在对数据库操作并提交事务时都将被阻塞。只能读,不能写。
典型场景:全库逻辑备份,对所有表进行锁定,从而获取一致性视图,保证数据的完整性。
-- 数据库备份
flush tables with read lock;
mysqldump -h xxx.xxx.xxx.xxx -uroot -p1234 ${数据库名称} > xxx.sql (备份文件)
unlock tables;
使用 InnoDB 引擎进行数据备份时,添加 --single-transaction 参数来完成不加锁的一致性数据备份,底层采用快照来实现。
mysqldump -h xxx.xxx.xxx.xxx --single-transaction -uroot -p1234 ${数据库名称} > xxx.sql (备份文件)
3.2 特点
数据库加全局锁是一个比较重的操作,会存在以下问题:
- 如果在主库备份,备份期间都不能执行更新操作,业务操作停摆
- 如果在从库备份,备份期间从库不能执行主库同步过来的二进制日志(binlog),导致主从延迟
四、表级锁
4.1 概念
表级锁:每次操作将锁住整张表
锁定粒度大,发生锁冲突概率高,并发度低
应用在 MyISAM、InnoDB、BDB 等存储引擎中
表级锁主要分以下三类:
- 表锁
- 元数据锁(meta data lock,简称MDL)
- 意向锁
4.2 表锁
锁名称 | 简称 | 加锁客户端 | 其它客户端 |
---|---|---|---|
表共享读锁(read lock) | 读锁 | 可以读,不能写 | 可以读,写阻塞,直到读锁解除,执行写入操作 |
表独占写锁(write lock) | 写锁 | 可读,可写 | 读,写阻塞,直到写锁解除,再执行相应的读/写操作 |
语法
-- 加锁
lock tables 表名... read / write;
-- 释放锁,执行解锁命令 或 断开客户端连接
unlock tables;
4.3 元数据锁(meta data lock, 简称 MDL)
MDL 加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上
主要作用:维护表元数据的一致性,在表上有活动事务的时候,不可以对表的元数据进行写入操作
目的:避免 DML 与 DDL 冲突,保证读写的正确性
在 MySQL 5.5 中引入 MDL,
当对表进行增删改查的时候,加 MDL 读锁(共享);
当对表结构进行变更操作的时候,加 MDL 写锁(排他);
对应SQL | 锁类型 | 说明 |
---|---|---|
lock tables xxx read/write | shared_read_only/shared_no_read_write | |
select、select…lock in share mode | shared_read | 与 shared_read、shared_write 兼容,与 exclusive 互斥 |
insert、update、delete、select…for update | shared_write | 与 shared_read、shared_write 兼容,与 exclusive 互斥 |
alter table… | exclusive | 与其它的 MDL 都互斥 |
4.4 意向锁
意向锁:为避免 MDL 在执行时,行锁 与 表锁 冲突。
加锁过程是系统自动控制,无需显式使用。
在 InnoDB 中引入了意向锁,使得在加表锁的时候避免对每行数据进行锁的检查。
意向锁分类 | 执行sql语句类型 | 兼容情况 |
---|---|---|
意向共享锁(IS) | 由语句 select … lock in share mode 添加 | 与表锁共享锁(read)兼容,与表锁排他锁(write)互斥 |
意向排他锁(IX) | 由 insert、update、delete、select … for update 添加 | 与表锁共享锁(read)及排他锁(write)都互斥。意向锁之间不会互斥 |
意向锁出现前,当线程1 开启事务并执行 update 语句时,会在修改的数据行上添加行锁;在线程1 提交事务之前,线程2 执行锁表操作 lock tables xxx read / write 时,InnoDB 会对该表的每一行数据进行检查,查看是否有行级锁存在,是否可以进行锁表操作。
意向锁出现之后,当线程1 开启事务并执行 update 语句时,会在修改的数据行上添加行级锁,并在该表上自动添加意向锁。当线程1提交事务之前,线程2 执行锁表操作 lock tables xxx read / write 时,InnoDB 检查到该表存在意向锁,若意向锁与当前所加表锁是兼容的,则直接添加表锁;若不兼容,则阻塞等待,直到线程1 事务提交,释放行级锁及意向锁后,再进行锁表操作
五、行级锁
5.1 概念
行级锁:每次操作锁住对应行数据
锁定粒度最小,发生锁冲突概率最低,并发度高
应用在 InnoDB 存储引擎中
分类 | 说明 | 兼容情况 |
---|---|---|
共享锁(S) | 允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁 | 共享锁之间兼容,共享锁与排他锁之间互斥 |
排他锁(X) | 允许获取排他锁的事务更新数据,阻止其它事务获得相同数据集的共享锁和排他锁 | 排他锁与共享锁和排他锁之间,互斥 |
InnoDB 对数据是基于 索引 组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加锁
5.2 行级锁分类
行级锁分类 | 说明 | 隔离级别 |
---|---|---|
行锁(Record Lock) | 锁定单个行记录的锁,防止其它事务对此进行 update 和 delete 操作 | RC (读已提交)、RR(可重复读) |
间隙锁(Gap Lock) | 锁定索引记录之间的间隙(不含间隙前后两条记录),确保索引记录间隙不变,防止其它事务在这个间隙进行 insert,产生幻读 | RR(可重复读) |
临键锁(Next-Key Lock) | 行锁和间隙锁组合,同时锁住数据并锁住数据前面的的间隙Gap | RR(可重复读) |
注意:间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁
5.3 行级锁优化
默认情况下, InnoDB 在 Repeatable Read (可重复读)事务隔离级别运行,使用 next-key 锁进行搜索和索引扫描,防止幻读。
行锁
-
针对唯一索引进行检索,对已存在的记录进行等值匹配时,将会优化为 行锁
-
InnoDB 的行锁是针对索引添加,若不通过索引条件检索数据时,InnoDB 将对表中所有记录进行加锁。此时 行锁 --> 表锁
间隙锁 / 临键锁
- 索引上的等值查询(唯一索引),给不存在的记录加锁,优化为 间隙锁
- 索引上的等值查询(普通索引),向右遍历时,最后一个值不满足查询需求时,next-key lock(临键锁) 退化为 间隙锁
B+ Tree 的叶子节点为从左往右、有序、双向列表结构。
当为普通索引时,在被操作数据(45)的前后可以插入数值相同的数据(45)。
InnoDB 为保证不出现幻读的现象,会将被操作数据(45)所在行及其前后的间隙同时进行加锁操作。
若存在多条相同值当数据
第一个45前面的间隙 ➕ 第一个45所在行数据 ---> 临键锁
第二个45前面的间隙 ➕ 第二个45所在行数据 ---> 临键锁
……
当后一个值不满足查询条件时,
第n个45后面的间隙 ---> 间隙锁
- 索引上的范围查询(唯一索引),添加 临键锁,会访问到不满足条件的第一个值为止