MySQL的锁机制之全局锁和表锁的实现
作者:恒者走天下
前言
对mysql锁的总结学习,本文将围绕,加锁的概念,加锁的应用场景和优化,以及不加锁会导致的问题这些方向进行总结学习。mysql的全局锁和表锁是本文的重点
一、全局锁
全局锁的介绍以及使用
全局锁就是对整个数据库实例进行加锁。
MySQL提供了一个加全局读锁的方法,如下:
全局读锁定:
FLUSH TABLES WITH READ LOCK ;
执行了命令之后所有库所有表都被锁定只读,解锁:
UNLOCK TABLES ;
加了全局读锁之后,整个数据库都处于只读的状态,当其他线程有数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新事务的提交语句都将会被阻塞。
全局锁的应用场景
了解了全局锁的基本概念后,心想全局锁的锁粒度这么大,效率肯定特别低,应该很少使用吧。但是开发者设计出来这个,肯定有它的使用场景啊。接下来我们看下:
通过学习了解到,全局锁最经典最常用的场景是用做全库逻辑备份 (就是把整个库的每个表select出来存放成文本)
把整个数据库都锁住,不能有更新操作,想想都危险,锁的粒度太大了。
比如:
如果在用户访问高峰期,这期间需要数据库的有关表进行更新操作。这是如果你备份整个库是在主库上备份,那么备份期间都不能执行更新操作,这是业务基本上就得停摆,影响用户体验。而如果你选择在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。
此时,你肯定想那加锁这么麻烦危险,那还不如不加锁呢。如果不加锁会导致什么后果呢
不加锁导致的危害
这里我举个简单的购物例子,你就瞬间清楚了。
比如,购物。我在某网站上买了一件商品,同时维护这网站的也准备发起一个数据库的逻辑备份。
如果时间顺序上是先备份我们用户的账号余额表,然后用户购买,然后备份用户商品表。
此时,会出现什么问题呢。结果会发现,我们用户的账号余额没减少,但是多了件购买的商品。这也是不是感觉挺好,我们用户赚大了啊。不过如果反过来呢,那我们岂不是亏大了。
反观,会出现这么一个现象的原因是什么,其实就是备份的库,备份的库中的表不是一个逻辑时间点的,即前后没有一致性。
提到一致性,我们会想到那不直接在可重读隔离级别下开启一个事务,进行数据逻辑备份不就好了吗,那岂不是比加锁好。
想法挺好的,但是也需要系统支持啊。比如像MyISAM这种不支持事务的引擎,只能通过FTWRL方法了。
加锁和其他方法对比
通过上面可以知道,整个库进行备份,就是先把整个库通过加全局读锁,把整个库设置成只读状态。
此时,你肯定会想,把整个库设置成只读状态,我还知道使用如下命令进行设置啊,不必要加锁啊
set global readonly=true;
确实,readonly这种方式也可以让全库进入只读状态。但是对于一些特殊情况,如在异常处理机制上,如果执行FTWRL命令之后,客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态,而将这个库设置为readonly之后,如果客户端发生异常,则数据库就会一直保持readonly状态,这样会导致整个库长时间处于不可写状态;所以还是建议使用全局锁比较合适
了解完了全局锁,接下来我们再来学习以下表锁
二、表锁
表锁的介绍以及使用
MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。
对于表锁,加锁的语句是:
LOCK TABLES tbl_name ; #不影响其他表的写操作
解锁的语句是:
UNLOCK TABLES ;
另一类表级的锁是 MDL(metadata lock) 。MDL不需要显式使用,在访问一个表的时候会被自动加上。
表锁的应用场景
针对表锁,在MySQL发展初级阶段,没有设计出更细粒度的锁时,表锁经常被用于处理并发(InnoDB支持行锁所以一般不会使用表锁)。举个例子,如果在某个线程A中执行lock tables t1 read, t2 write;
这个语句,则其他线程写t1、读写t2的语句都会被阻塞。同时,线程A在执行unlock table之前,也只能执行读t1、读写t2的操作。连写t1都不允许,自然也不能访问其他表。
对于MDL锁,是访问一个表的时候自动加上的。为什么呢,这也是因为时间逻辑点的不同。比如,如果一个查询正在遍历一个表中的数据,而在此执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对不上,就会出错。为此,为了解决这些问题。当对一个表做增删改查操作的时候,加MDL读锁;要对表做结构变更操作的时候,加MDL写锁。
为此需要注意的还有一点,并不是系统默认为每一访问表的操作自动添加了MDL锁就会万事大吉。比如,对于一个表t执行如下的操作:
session A先启动,对表t加一个MDL读锁
因为session B需要的也是读锁,可以正常执行
因为session A的MDL读锁还没有释放,而session C需要MDL写锁,因此会被阻塞
session C之后的所有要对表申请的读锁页会被session C阻塞,最终导致这个表完全不可读写
此时如果对这个表的查询比较频繁,并且客户端也有重试机制,那么这个库的线程很快就会爆满
造成这个问题的原因是,事务中的MDL锁,在执行语句的时候开始申请,但是语句结束后并不会马上释放,而是等到整个事务提交后再释放。
因此,了解了这个问题,以及这个问题出现的原因。
那么我们如何解决安全的给表加字段呢
首先要解决长事务,毕竟事务不提交,就会一直占着MDL锁。。因此如果要做DDL变更表的时候,查到刚好有长事务在执行,可以考虑暂停DDL操作或者kill掉长事务
如果要操作的表是个热点表,请求比较频繁,此时如果采用kill,那么新的请求立马就来了,未必管用。因此比较合适的操作是:在alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后再通过重试命令重复这个过程。
到此这篇关于MySQL的锁机制之全局锁和表锁的实现的文章就介绍到这了,更多相关MySQL 全局锁和表锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!