为什么要加锁
锁的出现,主要是为了解决并发问题。在多用户同时访问的时候,数据库需要合理的控制这部分共享资源的访问策略。锁就是实现这些访问策略的数据结构。
分类
根据锁的范围,大致可分为:全局锁、表锁和行锁。这篇文章先介绍全局锁和表锁,行锁会在后续文章单独介绍。
全局锁
全局锁就是对整个数据库实例进行加锁,有什么用呢?
将整个数据库设置为只读状态,用来做全库的逻辑备份(就是把整个数据库里面的每个表都select出来,存成文本)。Mysql提供了一种加全局读锁的方法:Flush tables with read lock(FTWRL)。整个数据库状态为只读,听起来就很危险:
- 如果在主库上做备份,在备份期间主库不能执行任何更新,业务基本停摆;
- 如果在备库上做备份,在备份期间从库不能执行主库同步过来的 binlog,导致主从延迟。
说明:一主一从、一主多从架构主要用来防止单点故障引起的业务不可用。
我们来看看,若是在备份期间,不加锁的情况下,会有什么问题:假设某用户A,有一张账户表account,有一张课程表course。
- 情形一:某个时刻,账户表account的余额1000块,已经被select出来保存起来,课程表还没备份。此时,用户A买了一门课程,账户余额减100,课程表增加一门课程。之后再备份课程表,最终备份的结果就是:用户A的账户还是1000块,课程表已经加了一门课。如果后面用这个备份来恢复数据,那么A用户就发现自己赚了
- 情形二:与情形一相反,先备份课程表,用户再花钱买课,再备份账户表,就会发现:账户已经扣钱了,但是没买到课。如果后面用这个备份来恢复数据,那么A用户就发现自己血亏。
也就是说,如果不加锁,备份系统备份得到的库有问题。原因是备份系统的视图的不一致导致的。说到视图,在可重复读隔离级别下,是可以拿到一致性视图的。一致性视图是好,但是前提是引擎得支持可重复读隔离级别。对于Myisam这种不支持事务的引擎来说,如果备份的过程中有更新,就破坏了备份的一致性,只能使用FTWRL命令了。
表锁
Mysql的表锁有三种:表锁、元数据锁(Meta Data Lock, MDL)、意向锁。
表锁:上锁语法:lock tables 表名 read/write。 解锁语法:unlock tables
可以看到,当加了读锁之后客户端都可以读,但是都不可以改,注意自己也不能改。
可以看到,当加了写锁之后的客户端可读可写,其他客户端读写都会被阻塞。
小结:读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户的读,又会阻塞其他客户端的写。
元数据锁:MDL是在访问一张表的时候会自动加上,无需显式使用。用来解决增删改查(DML)语句和修改表结构的语句的冲突问题,保证数据读写的正确性。在Mysql 5.5中引入了MDL。在对一张表做增删改查的时候会加MDL读锁,修改表结构时会加MDL写锁。
看下面这个例子:
- session A开启事务,读取一行,加MDL读锁;
- session B再读取一行,这条语句可以正常读取,因为MDL读锁之间不互斥;
- session C想增加字段,需要加MDL写锁,读写锁互斥,所以session C阻塞等待;
- session D想继续读取,因为session C自己阻塞还不要紧,还会导致后面的读、写操作都会阻塞;
从业务看就是整张表都不可读写。若是这张表上刚好有频繁的查询请求,若是长时间不返回,客户端有重试机制,也就是超时之后会再起session请求,导致这个库的线程很快爆满!
补充说明,给一张表增加字段、修改字段或者增加索引,都需要走全表扫描。为什么呢?因为索引要根据每一行的记录值来创建。增加字段和修改字段都需要修改对应列的数据,所以也会走全表扫描。
小结:事务中的MDL锁,是在语句执行的时候申请,但是语句执行结束后不会立马释放,而是等事务提交了才释放。所以,我们在给表添加字段的时候,应该尽可能的避免长事务,事务不提交就会一直占着MDL锁。
在Mysql information_schema 库的 innodb_trx 表,你可以查到当前执行中的事务,如果你要做DDL的表刚好有长事务在执行,考虑先暂停 DDL 或者 kill 掉这个长事务。
思考题:假设你要做DDL的表是一个热点表,虽然数据量不大,但是这张表上的请求很频繁,你又不得不做DDL,怎么做?
这个时候kill未必有用了,因为kill了,又会有新的请求过来。理想的做法是在alter table 语句中加上超时时间,如果在这个时间内拿到写锁最好,拿不到不要阻塞后面的语句。
意向锁:分为意向共享锁(IS)和意向排它锁(IX)。主要是InnoDB引擎为了实现多粒度锁共存,从而引入了意向锁。
意向共享锁,事务想要给数据库某些行加共享锁,需要先加上意向共享锁
意向互斥锁,事务想要给数据库某些行加互斥锁,需要先加上意向互斥锁
举例: 假设我们事务A执行以下语句:
mysql> update stu set age=55 where id = 1;
InnoDB除了在这行数据上加X锁,还对所在的表stu加上了意向排它锁。
这个时候,如果这时另外一个事务B,对表stu有表级别的操作,在执行alter table、drop table、lock tables等这些操作前,都会先检查表stu是否存在意向排他锁O(1)时间复杂度,如果有,则阻塞等待。如果没有意向锁,需要一行一行的扫描全表看是否有锁冲突,判断时间复杂度O(n)。
笔记参考于极客时间《MySQL实战45讲》