第十二节、全局锁和表锁
锁的目的
数据库锁涉及的初衷是处理并发问题。作为多用户共享的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则。而锁就是用来实现这些访问规则的重要数据结构。
锁的类型
根据加锁的范围,mysql里面的锁大致可以分成全局锁、表级锁和行锁三类。
全局锁
全局锁就是对整个数据库实例加锁,mysql提供了一个加全局读锁的方法,命令是Flush tables read lock(FTWRL)。当需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事物的提交语句。
使用场景
全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都select出来存成文本。
不加锁的话,备份系统备份得到的库不是一个逻辑时间点,这个视图是逻辑不一致的,事物隔离能拿到一致性视图(在可重复度隔离级别下开启一个事物)。
官方自带的逻辑备份工具是mysqldump。当mysqldump使用参数-single-transaction的时候,导数据之前就会启动一个事物,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。
表级锁
mysql里面表级别的锁有两种:一种是表锁,一种是元数据锁;
表锁的语法是lock tables... read/write。与FTWRL类似,可以用unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
另一类表级的锁是MDL(metadata lock)。MDLu需要显示使用,在访问一个表的时候会被自动加上。MDL的作用是,保证读写的正确性,你可以想象以下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定不行的。
在MySQL5.5中引入了MDL,当对一个表做增删改查操作的时候,加MDL读锁,当要对表做结构变更操作的时候,加MDL写锁。
读锁之间不互斥,可以有多个线程同时对一张表增删改查;
读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
如何安全地给小表加字段?
1、首先要解决长事物,事物不提交,就会一直占着MDL锁。在mysql的information_schema库的innodb_trx表中,可以查到当前执行中的事物。如果要做DDL变更的表刚好有长事物在执行,要考虑先暂停DDL,或者kill掉这个长事物。
2、如果要变更的表是一个热点表,上面请求很频繁,而又不得不加个字段,该怎么做尼?比较理想的机制是,在alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者DBA再通过重试命令重复这个过程。
总结
全局锁主要用在逻辑备份过程中,对于全部是InnoB引擎的库,建议选择使用-single-transaction参数,对应用会更友好。
表锁一般是再数据库引擎不支持行锁的时候才会被用到的,如果应用程序里有lock tables这样的语句,比较可能的情况是:
1、系统现在还在用MyISAM这类不支持事物的引擎,那么安排升级换引擎;
2、引擎审计了,但是代码还没升级,这类情况一般是业务开发把lock tables 和unlock tables 改成begin和commit,问题就解决了。
MDL会直到事物提交才释放,再做表结构变更的时候,一定要小型不要导致锁住线上查询和更新。