从这一节开始,我们进入专栏的第三个MySQL知识大类:MySQL锁。
在上一章(MySQL索引)中,我们介绍了索引原理、需要添加索引的场景、一些常见索引类型的区别等,以及分享了有些场景MySQL会选错索引及选错索引时的处理方式等。通过这些学习,我们知道了提高查询效率的方法。但是,数据库往往是多个用户或者客户端在连接使用的。这时,我们需要考虑一个新的问题:
如何保证数据并发访问的一致性、有效性呢?
MySQL中,锁就是协调多个用户或者客户端并发访问某一资源的机制,保证数据并发访问时的一致性和有效性。
本章就来介绍一下不同场景下的锁机制。
根据加锁的范围,MySQL中的锁可分为三类:
全局锁表级锁行锁本节来重点讲解一下全局锁和表锁。
1全局锁
MySQL全局锁会关闭所有打开的表,并使用全局读锁锁定所有表。其命令为:FLUSHTABLESWITHREADLOCK;
简称:FTWRL,可以使用下面命令解锁:UNLOCKTABLES;
我们来通过实验理解一下全局锁:
首先创建测试表,并写入数据:
usetest;
droptableifexistst14;
CREATETABLE`t14`(
`id`int(11)NOTNULLAUTO_INCREMENT,
`a`int(11)NOTNULL,
`b`int(11)NOTNULL,
PRIMARYKEY(`id`),
KEY`idx_a`(`a`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;
insertintot14(a,b)values(1,1);
进行FTWRL实验:
上面的实验中,当session1执行FTWRL后,本线程session1和其它线程session2都可以查询,本线程和其它线程都不能更新。
原因是:当执行FTWRL后,所有的表都变成只读状态,数据更新或者字段更新将会被阻塞。
那么全局锁一般什么时候会用到呢?
全局锁一般用在整个库(包含非事务引擎表)做备份(mysqldump或者xtrabackup)时。也就是说,在整个备份过程中,整个库都是只读的,其实这样风险挺大的。如果是在主库备份,会导致业务不能修改数据;而如果是在从库备份,就会导致主从延迟。
好在mysqldump包含一个参数--single-transaction,可以在一个事务中创建一致性快照,然后进行所有表的备份。因此增加这个参数的情况下,备份期间可以进行数据修改。但是需要所有表都是事务引擎表。所以这也是建议使用InnoDB存储引擎的原因之一。
而对于xtrabackup,可以分开备份InnoDB和MyISAM,或者不执行--master-data,可以避免使用全局锁。
2表级锁
表级锁有两种:表锁和元数据锁。
2.1表锁
表锁使用场景:事务需要更新某张大表的大部分或全部数据。如果使用默认的行锁,不仅事务执行效率低,而且可能造成其它事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高事务执行速度;事务涉及多个表,比较复杂,可能会引起死锁,导致大量事务回滚,可以考虑表锁避免死锁。其中表锁又分为表读锁和表写锁,命令分别是:
表读锁:locktablest14read;
表写锁:locktablest14write;
下面我们分别用实验验证表读锁和表写锁。
表读锁实验:
从上面的实验我们可以看出,在session1中对表t14加表读锁,session1和session2都可以查询表t14的数据;而session1执行更新会报错,session2执行更新会等待(直到session1解锁后才更新成功)。
总结:对表执行locktablesxxxread(表读锁)时,本线程和其它线程可以读,本线程写会报错,其它线程写会等待。
我们再来看一下表写锁实验:
总结:对表执行locktablesxxxwrite(表写锁)时,本线程可以读写,其它线程读写都会阻塞。
2.2元数据锁
在MySQL中,DDL是不属于事务范畴的。如果事务和DDL并行执行同一张表时,可能会出现事务特性被破坏、binlog顺序错乱等bug。为了解决这类问题,从MySQL5.5.3开始,引入了元数据锁(MetadataLocking,简称:MDL锁)。
从上面我们知道,MDL锁的出现解决了同一张表上事务和DDL并行执行时可能导致数据不一致的问题。
但是,我们在工作中,很多情况需要考虑MDL的存在,否则可能导致长时间锁等待甚至连接被打满的情况。如下例:
上面的实验中,我们在session1查询了表t14的数据,其中使用了sleep(),表示在秒后才会返回结果;然后在session2执行DDL操作时会等待(原因是session1执行期间会对表t14加一个MDL,而session2又会跟session1争抢MDL);而session3执行查询时也会继续等待。因此如果session1的语句一直没结束,其它所有的查询都会等待。
这种情况下,如果这张表查询比较频繁,很可能短时间把数据库的连接数打满,导致新的连接无法建立而报错,如果是正式业务,影响是非常恐怖的。
当然如果出现这种情况,假如你还有session连着数据库,可以kill掉session1中的语句或者终止session2中的DDL操作,可以让业务恢复。但是出现这种情况的根源其实是:session1中有长时间未提交的事务。
因此对于开发来说,在工作中应该尽量避免慢查询、尽量保证事务及时提交、避免大事务等,当然对于DBA来说,也应该尽量避免在业务高峰执行DDL操作。