MySQL全局锁与表锁:表添字段时的那些阻碍

3周前发布 gsjqwyl
12 0 0

文章标题:

MySQL全局锁与表锁:表增字段时的阻碍分析

文章内容:按照加锁的范围来划分,MySQL里的锁大体能分为全局锁、表级锁以及行锁这三类,本文先对前两种展开讲解。

全局锁

全局锁是对整个数据库实例进行加锁,MySQL提供的添加全局读锁的命令是Flush tables with read lock(下文简称为FTWRL)。当需要让整个数据库处于只读状态时,就可以运用这个命令,之后其他线程的如下语句会被阻塞:数据的增删改操作、数据定义语句(比如表的创建与修改等)、更新类事务的提交语句。

全局锁的典型应用场景是全库逻辑备份,也就是把库中每一张表都通过select操作提取出来并保存成文本形式。

让整个数据库处于只读状态存在一些弊端:
* 要是在主库上进行备份,那么在备份期间无法执行更新操作,业务会停止运行;
* 要是在从库上进行备份,那么备份期间从库不能执行主库同步过来的binlog,从而引发主从延迟。

既然备份加锁有诸多不利之处,那是否可以不施加锁呢?
答案是不行的,举个例子,有用户余额表和订单表两张表,当用户进行一次购买时,需要在用户余额表中扣减余额,同时在订单表中添加一笔订单。要是顺序是先备份用户余额表,然后用户进行购买,再备份订单表,很明显会出现数据不一致的情况,反过来也是一样。

官方自带的逻辑备份工具是mysqldump,当mysqldump使用参数single-transaction时,在导出数据之前就会开启一个事务,获取到一致性的视图。由于有MVCC的支持,在这个备份过程中数据能够正常地进行更新。

那既然官方有这个工具,为什么还需要FTWRL呢?原因在于有些引擎不支持可重复读的隔离级别,这时候就必须使用FTWRL命令。所以,只有所有表都采用事务引擎的数据库才能够使用single-transaction

还有一种方式是通过set global readonly=true来让全库只读,但还是建议使用FTWRL,原因如下:
* 在一些系统中,readonly的值会被用于其他逻辑,比如用来判断数据库是主库还是从库。
* 在异常处理方面,如果执行FTWRL命令后客户端出现异常断开,那么MySQL会自动释放这个全局锁,整个数据库恢复到可以正常更新的状态。而把整个数据库设置为readonly后,如果客户端出现异常,那么数据库会一直保持该状态,导致整个数据库长时间无法写入,风险比较高。

表级锁

这里介绍两种表级别的锁,分别是表锁和元数据锁。

表锁的语法是lock tables … read/write,可以使用unlock tables主动释放锁,也能在客户端断开时自动释放锁。表锁不仅会限制其他线程的读写操作,还会限制本线程后续的操作

在没有出现更细粒度的锁时,表锁是最常用的处理并发的方式。对于像InnoDB这种支持行锁的引擎,一般不会用lock tables来控制并发。

元数据锁(MDL)不需要显式使用,在访问一张表时会自动被加上。它的作用是保证读写的正确性。MDL在MySQL 5.5版本引入,当对一张表进行增删改查操作时,会加上MDL读锁;当要对表进行结构变更操作时,会加上MDL写锁。
* 读锁不互斥,所以可以有多个线程同时对一张表进行增删改查操作。
* 读写锁、写锁之间相互互斥,以此保证变更表结构操作的正确性。

需要注意的是,事务中的MDL锁,在语句执行开始时申请,但语句结束后不会立刻释放,而是要等到整个事务提交后才会释放。看下面这个事例:

首先开启事务,然后session A会对表t加上一个MDL读锁,由于session B需要的也是读锁,第二条查询语句也能够正常执行。但之后session C会被阻塞,因为前面的读锁还没有释放,而session C需要写锁。当session C被阻塞后,之后只需要读锁的请求也都会被阻塞,相当于这张表此时完全不能进行读写操作。

如果某张表上的查询语句很频繁,而客户端有重试机制,也就是超时后重新开启session发起请求,那么在上述情况下数据库的线程很快就会被占满。

基于以上分析,设想一个场景,假设要给一张小表添加字段,这张表数据量不大,但是请求很频繁,该怎么处理呢?
此时因为请求频繁,不能简单地kill事务。比较好的办法是在alter table语句里设置等待时间,如果在等待时间内能够拿到MDL写锁就进行修改,拿不到也不阻塞后面的业务语句,先放弃修改,之后由开发人员人工处理。

目前已有相应的语句:

ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ... 
© 版权声明

相关文章

没有相关内容!

暂无评论

none
暂无评论...