MySQL全局锁、行锁、表锁、间隙锁.md
MySQL全局锁、行锁、表锁、间隙锁
1.全局锁
1.1 定义
全局锁是对整个数据库实例加锁,让其处于只读状态。MYSQL可以通过Flush tables with read lock(FTWRL),执行该命令后数据更新语句(DML)数据增删改查操作以及数据操纵语句(DDL)修改表结构等操作将被阻塞。
1.2 全局锁的应用场景
最典型的场景就是全库逻辑备份,就是把整个库所有表都select出来存成文本。
假设现在数据库是主从架构,用FTWRL确保备份期间不会有其他线程对数据库做更新操作,然后整库备份,这时数据库实例会处于只读状态,就会造成两个问题:
- 如果在主库上备份,业务将会受到严重影响
- 如果在从库备份,从库将不会执行binlog,将会造成主从延迟。
如果非要备份,只能在业务悠闲的时候进行。
2. 表级锁
MySQL会有两种表级锁: 表锁以及元数据锁
2.1 表锁
表锁的语法是这样的: lock tables … read/write,它是显式的,同样也是通过unlock tables 主动释放锁;当然,客户端断开或者异常时也会释放。
mysql> lock tables student read,course read;
mysql> SELECT count(1) FROM student;
mysql> SELECT count(1) FROM course;
mysql> unlock tables;
需要注意一点: lock tables 除了会限制别的线程读写以外,也限定了本线程接下来操作的对象。举个栗子:
线程 A 执行 lock tables student read,course write; 语句,其他线程读 student、读写 course 都会被阻塞。同时,线程 A 在执行 unlock tables 之后,也只能读 student、读写 course;不能访问其他表。整个表格更直观:
student 表 | course 表 | 其他表 | |
---|---|---|---|
线程A | 读 | 读写 | 不允许 |
其他线程 | 阻塞 | 阻塞 | 随便 |
PS:在没有更细粒度的年代,表锁是最常用与处理并发的方式。但是对于 InnDB 来说,一般不使用 lock tables 控制并发,因为粒度太大了。
2.2 MDL 元数据锁
MDL不需要记命令,它是隐式使用的,访问表会自动加上。它的主要作用是防止DDL(改表结构)和DML(CRUD表数据)并发的冲突。
举个栗子,线程 A 遍历查询表数据,这期间线程 B 删了表的某一列,这时 A 拿到的数据就跟表结构对不上,MySQL 不允许这种事发生,所以在 5.5 版本引入了 MDL。
它的逻辑很简单,对表进行 CRUD 操作,加 MDL 读锁;对表结构下手时,加 MDL 写锁。因此:
- 读读不互斥,可以多线程对一张表增删改查
- 读写互斥、写写互斥,保证对表的结构下手时只能有一个线程 操作,另一个进入阻塞。
2.21 加个字段就搞跨数据库?
我们知道MDL默认时系统加的,对表结构下手时(加字段、改字段、加索引等等),需要全表扫描。对大表进行操作时,肯定要选择业务悠闲的时候进行,以免遭到投诉。但不只是大表,有时候对小表进行操作时,也会有这样的问题。比如下面的例子:
前提:注意,我这里的事务是手动开启和提交的。而 MDL 锁是语句开始时申请,事务提交才释放。所以,如果是自动提交就不会出现下面的问题。
- T1、T2 时刻 session A 事务启动,加个 MDL 读锁,然后执行 select 语句。注意:这时事务并没有提交;
- T3 时刻 session B 也是读操作,可以共享 MDL 读锁,顺利执行;
- T4 时刻 session C 不讲武德,对表执行 DDL (改表结构)操作,需要的是 MDL 写锁,所以被阻塞;
- T5 时刻 session D 也是读操作,按道理说 session C 阻塞应该没影响。
但是 MySQL 有一个队列会根据时间先后决定哪个 Session 先执行。所以,不管是 D 还是之后的 session 都会被 C 阻塞。而恰巧 student 又是访问频率很高的表,如此这个库的线程数很快就打满了。
此时,数据库完全不能读写,甚至导致宕机,在用户界面看来就是没响应了。
3. 行锁
mysql 的行锁是在引擎实现的,但并不是所有引擎都支持行锁,不支持行锁的引擎只能使用表锁。
行锁比较容易理解:行锁就是针对数据表中行记录的锁。比如:事务 A 先更新一行,同时事务 B 也要更新同一行,则必须等事务 A 的操作完成后才能进行更新。
3.1 两阶段提交
先举个栗子:事务 A 和 B 对 student 中的记录进行操作。
其中事务 A 先启动,在这个事务中更新两条数据;事务 B 后启动,更新 id = 1 的数据。由于 A 更新的也是 id = 1 的数据,所以事务 B 的 update 语句从事务 A 开始就会被阻塞,直到事务 A 执行 commit 之后,事务 B 才能继续执行。
在事务期间,事务 A 实际上持有 id = 1 和 id = 2 这两行的行锁。如果事务 B 更新的是 id = 2 的数据,那么它阻塞的时间就是从 A 更新 id = 2 这行开始(事务 A 更新 id = 1 时,它并没有阻塞),到事务 A 提交结束,比更新 id = 1 数据阻塞的时间要短。PS:理解这句话很重要。
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。锁的添加与释放分到两个阶段进行,之间不允许交叉加锁和释放锁。
4. 间隙锁
4.1 什么是间隙锁?
当我们采用范围条件查询数据时,比如有 id 为:1、3、5、7 的 4 条数据,我们查找 1-7 范围的数据。那么 1-7 都会被加上锁。2、4、6 也在 1-7 的范围中,但是不存在这些数据记录,这些 2、4、6 就被称为间隙。
4.2 间隙锁的危害
范围查找时,会把整个范围的数据全部锁定住,即使这个范围内不存在的一些数据,也会被无辜的锁定住,比如我要在 1、3、5、7 中插入 2,这个时候 1-7 都被锁定住了,根本无法插入 2。在某些场景下会对性能产生很大的影响。