全局锁和表锁 :给表加个字段怎么有这么多阻碍?

不论是我们平时接触到的synchronized还是ReentrantLock等等锁,都是为了解决业务中并发的问题,而数据库中也是如此,mysql里面的锁大致分为三种,全局锁,表级锁和行锁三类。今天这篇文章,将会和你分享全局锁和表级锁。

锁的设计是很复杂的,专栏会主要围绕碰到锁的现象和其背后的原理。

全局锁

全局锁就是整个数据库实例枷锁。Mysql提供了一个加全局读锁的方法,命令是Flush tables with read lock。当你需要让整个库处于只读状态的时候,就可以使用这个命令,之后其他线程的以下语句就会被阻塞。数据的增删改,create表结构和update表结构,还有事务类的提交语句

全局锁的使用场景是:全库逻辑备份。也就是把每个表select出来存成文本。

以前有一种做法,是通过FTWRL确保不会有其他线程对数据库做更新,然后对整个库做备份。当然,做备份的时候,就是只读状态。

但是让整个数据库都只读,是很危险的:

  • 如果在主库上备份,那么再备份期间都不能执行更新,业务就得停摆
  • 如果从从库上进行备份,那么从库就无法和主库进行同步,就会导致主从延迟

那么,我们又为什么要加全局锁呢?

如果我们要维护一个购买系统,那么关注的就是用户余额表和用户课程表,现在发起一个逻辑备份,假设备份期间,有一个用户,购买了东西,那么业务逻辑就要扣掉余额,然后给购买物品。

如果从时间顺序上是先备份账户余额表(user_account),然后用户购买,然后备份用户购买物品表(user_sth)会如何呢?

在备份的结果中会显示,余额不变,但是物品+1。这是因为备份系统备份到的库不在一个逻辑时间点,视图逻辑不一致

而获取到试图逻辑一致的方法是在可重复读隔离级别下开启一个事务。

官方在这方面自带了一个工具,是mysqldump,当使用参数-single-transaction的时候,导数据之前就会启动一个事务,来保证获得一致性逻辑视图,由于支持MVCC,这个过程中数据是可以正常更新的。

那么既然我们可以使用可重复读来避免视图逻辑不一致,那我们还为啥要用全局锁呢?一致性读是好,但是前提是引擎要支持这个隔离级别,比如My了SAM就不支持,那么就得使用全局锁了。

所以,我们才更喜欢用InnoDB啊。

既然全库只读,那么为啥不使用set global readonly = true呢? 毕竟也可以让数据库进入只读状态。好,原因如下;

  1. 在有些系统中,readonly的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改global变量的方式影响更大,不建议使用。

  2. 异常处理机制上,使用全局锁遇见问题会中断,然后释放全局锁。但是设置readonly,遇见问题会停滞,导致数据库处于长期不可写的状态。

业务的更新包括两方面:DML(增删改数据),DDL(加字段,修改创建表结构)。不论是哪种行为,一个库被全局锁上以后,要对任何一个表加字段都会被锁住。即使没有被锁,过程也很简单,所以就来介绍一个表级锁。

表级锁

mysql里面的表级别锁有两种:一是表锁,二是元数据锁

表锁

表锁的语法是lock tables …read/write。与全局锁语法类似,可以用unlock tables主动释放锁,也可以在client端断开的时候自动释放。需要很在意的是,lock tables语法除了会限制别的线程读写之外,也限定了本县城接下来的操作对象。

举例来说,如果在线程A中执行lock tables t1 read,t2 write 这个语句,则其他线程写t1,读t2都会被阻塞。同时,线程A在释放锁之前,也只能进行读t1和读写t2,连写t1都不允许,自然也不能访问其他表。

在没有出现粒度更细的锁之前,表锁是最常用的处理并发的方式。但是对于InnoDB这种支持行锁的引擎,一般不适用lock tables命令来控制并发,毕竟锁住整个表的影响面还是太大。

元数据锁

metadata lock,简称MDL,MDK不需要显示使用,在访问一个表的时候就自动加上了,作用是保证读写的正确性,防止进行脏读,这是很重要的。

因此,党对一个表进行crud的时候加MDL读锁,当对表结构进行修改的时候,加MDL写锁。

  • 读写不互斥,因此可以有多个线程同时对一张表crud。
  • 读写锁之间,写锁之间是互斥的,用来保证变更表结构操作的安全性。这样就保证了有序性。

但是MDL也是有坑的

当我们给一个表加字段,修改字段或者加索引的时候,需要扫描整张表。而不管这张表是大表还是小表,都会产生一定的问题,假设t是一个小表。
在这里插入图片描述

我们可以看出来是session A先启动,这时候会对表t加一个MDL读锁。由于session B需要的也是MDL读锁,因此可以正常执行。

之后session C会被阻塞,因为sessionA的MDL读锁还没有释放。

如果只有sessionC被阻塞还可以,但是之后在表t上新申请一个MDL读锁的请求也会被sessionC阻塞。前面还说了,所有对表的增删改查操作都需要先申请MDL读锁,那么现在就会成为完全无法读写状态。

如果某个表上的查询语句频繁,而且客户端有重试机制,那么超时后会再起一个新session,但是又会被阻塞,就会堆栈溢出。

那么我们现在应该也知道了,MDL锁是等待事务提交之后才会被释放的

那么,我们如何给小表安全的加字段呢?

首先我们需要解决长事务,事务不提交,就会一直占着MDL锁,在Mysql的information_schema库的innodb_trx表中,可以查询到当前执行的事务。如果要做DDL任务,可以先kill这个长事务。但是如果变更的表是一个热点表,虽然数据量不大,但是上面的请求很频繁,我们该怎么做呢?

因为请求很频繁,所以理想的机制是,在alter table语句中添加等待时间,希望在这个等待时间里面拿到MDL锁,拿不到也不能对后面的事务进行阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值