根据加锁的范围,MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类。
全局锁就是对整个数据库实例加锁。MySQL提供了一个加全局锁的方法,命令是Flush tables with read lock(FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句、数据定义语句和更新事务的提交语句。全局锁的典型使用场景是做全库逻辑备份,也就是把整库每个表都select出来存成文本。
表级别的锁有两种:一种是表锁,一种是元数据锁。
表锁的语法是lock tables... read/write。与FTWRL类似,可以用unlock tables主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables语法除了会限制别的线程读写外,也限定了本县城接下来的操作对象。
元数据锁是MDL,不需要显式使用,在访问一个表的时候会被自动动加上。MDL的作用是,保证读写的正确性。当一个表做增删改查操作的时候,加MDL读锁;当要对表结构变更操作的时候,加MDL写锁。读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。读写锁之间、写锁之间是互斥的,用来保证变更结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
虽然MDL锁是系统默认会加的,但却是你不能忽略的一个机制。比如下面这个例子,经常有人掉到这个坑里:给一个小表价格字段,导致整个库挂了。
那么如何安全地给小表加字段?首先我们要解决长事务,事务不提交,就会一直占着MDL锁。在MySQL的information_schema库的innodb_trx表中,你可以查到当前执行中的事务。如果你要做DDL变更的表刚好有长事务在执行,要考虑先暂停DDL,或者kill掉这个长事务。但有时候kill未必管用,因为新的请求马上就来了。比较理想的机制是,在alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者DBA再通过重试命令重复这个过程。