文章目录
- 前言
- 一、全局锁
- 全局锁的介绍以及使用
- 全局锁的应用场景
- 不加锁导致的危害
- 加锁和其他方法对比
- 二、表锁
- 表锁的介绍以及使用
- 表锁的应用场景
前言
对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写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后再通过重试命令重复这个过程。