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

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

锁的设计是非常复杂的,专栏主要介绍的是碰到锁时的现象和其背后的原理。

全局锁

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

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

以前有一种做法,是通过FTWRL确保不会有其他线程对数据库做更新,然后对整个库做备份。注意,在备份过程中整个库完全处于只读状态。

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

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆
  • 如果从库上备份,那么从库不能执行主库同步过来的binlog,会导致主从延迟

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

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

如果从时间顺序上是先备份账户余额表(u_account),然后用户购买,然后备份用户课程表(u_course)会怎么样呢?

在备份结果中显示,余额不变,但是物品加一。这是因为备份系统备份到的库不在一个逻辑时间点,视图逻辑不一致而获取到视图逻辑一致的方法是在可重复调读隔离级别下开启一个事务。

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

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

所以,才更喜欢用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,MDL不需要显示使用,在访问一个表的时候就自动加上,作用是保证读写的正确性,防止进行脏读,这是很重要的。

因此,当对一个表进行crud的时候加MDL读锁,当对表做结构变更操作的时候,加MDL写锁。

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

但是MDL也是有坑的:当给一个表加字段,修改字段或者加索引的时候,需要扫描整张表。不管是大表还小表都会造成一定的问题,假设表t是一个小表。
在这里插入图片描述
我们可以看到session A先启动,这时候会对表t加一个MDL读锁。由于session B需要的也是MDL锁,因此可以正常运行。

之后session C会被blocked,因为session A的MDL锁还没有被释放,而session C需要MDL写锁,因此只能被阻塞。

如果只有session C被阻塞还没有什么关系,但是之后所有表要在表t上新申请MDL读锁的请求也会被session C阻塞。前面说了,所有对表的增删改查操作都需要先申请MDL读锁,就都被锁住等于这个表现在完全不可读写了。

如果某个表上的查询语句频繁,而且客户端有重试机制,就是说超时后会再起一个新session再请求的话,这个库的线程很快就会爆满。

现在应该知道了,事务中的MDL锁是等待事务提交之后才会被释放的。

那么如何安全地给小表加字段?

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

因为请求很频繁,所以理想的机制是:在alter table语句里面设定等待时间,希望在这个指定的等待时间里面拿到MDL锁,拿不到也不能阻塞后面的业务,先放弃。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值