你了解MySQL的加锁规则吗——全局锁,表级锁

我们知道,不同存储引擎中所支持的锁也是不一样的。InnoDB 支持行锁和表锁,MyISAM只支持表锁,BDB存储引擎支持页锁和表锁。
在这里插入图片描述
数据锁的设计主要用来解决并发带来的问题。当一个业务场景中出现多用户共享同一资源,当出现并发访问的时候,数据库需要合理的控制资源的访问规则,锁就是用来控制这些访问规则的。

根据加锁的范围,MySQL里的锁大致可以划分为全局锁,表级锁和行锁三类。

全局锁(FTWRL)

全局锁是对整个数据库实例加锁。MySQL提供了一个加全局锁的方法,命令是:

Flush tables with read lock;

释放全局锁的命令:

UNLOCK TABLES;

当需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程执行以下操作都会被阻塞:
1、数据更新语句(DML):包括增删改操作。
2、数据定义语句(DDL):包括建表,修改表结构等。
3、更新类事务的提交语句。

全局锁应用场景

全局锁的应用场景:做全库逻辑备份,即把整个库中每个表都select出来存成文本。

加全局锁会出现问题
如果加了全局锁,其他线程就不能对数据库做更新,在备份过程中整个库完全处于只读状态,会出现一些问题。
1、如果在主库上备份,那么备份期间都不能执行更新,业务基本上停摆。
2、如果在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。

既然加了全局锁会出现上面这些问题,不加锁行不行?
举个例子:
业务背景:某用户A在极客时间上购买一门课程,作为开发人员你需要关注就是用户账号余额表和用户课程表。
假设发起一个逻辑备份,在备份期间,用户A购买了一门课程,业务逻辑里就要扣减账户余额,然后在课程表中添加一门课程。
如果时间顺序上是先备份账户余额表,再备份用户课程表,整个过程如下:
在这里插入图片描述
可以看到备份之后的结果,用户账户余额没减,但是课程表中却多了一门课程,用户A发现自己赚了。如果备份顺序反过来,先备份课程表,再备份账户余额表,结果就会变成课程表中是NULL,但是账户余额却少了,意思是花了钱没买上课,也是不行的。

表级锁

MySQL里表级锁分为两种:表锁,元数据锁(meta data lock ,MDL)。

(1)表锁
加表锁的命令:

lock tablesread/write

释放表锁的命令:

unlock tables

注意:lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
举个例子:
如果在某个线程A执行lock tables t1 read,t2 write;这个语句,则其他线程写 t1,读写 t2 的语句都会被阻塞。同时,线程A在执行unlock tables 之前,也只能执行读 t1,读写 t2 操作。

(2)元数据锁(metadata lock ,MDL)
元数据锁不需要显式使用,在访问一个表的时候会被自动加上。元数据锁的作用是,防止DDL和DML并发的冲突。

如果没有元数据锁,当一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果和表结构对不上,这样肯定不行的。

在MySQL5.5版本中引入了MDL,当对一个表做增删改查操作的时候,加MDL读锁;当要对表结构做变更操作的时候,加MDL写锁。其中读锁和写锁具备以下特点:
1、读锁之间不互斥。因此可以有多个线程同时对一张表增删改查
2、读写锁之间,写锁之间是互斥的。用来保证变更表结构操作的安全性。所以,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

典型的误操作:给一个表添加字段或修改字段类型,导致整个库挂了

在给一个表加字段,或者修改字段,或者加索引,都需要扫描全表的数据。在对大表操作的时候需要格外小心,以免对线上服务造成影响。不过即使是小表也会存在操作不当挂掉整个库。

举个例子:
在这里插入图片描述
首先开启了一个事务, session A先启动,这时会对表 t 加 MDL 读锁,session B需要的也是MDL读锁,MDL读锁之间是不互斥的,所以可以正常运行。session C 修改的是表结构需要加MDL 写锁,由于MDL读写锁是互斥的,所以session C会被阻塞。session D同样也被阻塞了。

从这个过程中,可以得出结论,事务中的MDL锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。

那如何安全的给表添加或修改字段呢?

首先要考虑是否有长事务,有则kill掉长事务,因为事务不提交,就会一直占着DML锁。在MySQL的information_schema库的Innodb_trx表中,可以查到当前执行中的事务。如果要做DDL变更的表中刚好有长事务在执行,要考虑先暂停DDL,kill掉这个长事务。

考虑一个场景,如果要变更的表是一个热点表,虽然数据量不大,但请求非常频繁,而你不得不加个字段,这个时候该怎么办?
kill可能不就管用了,因为你刚kill掉一个事务新的请求马上就来了。比较理想的解决方案是在alter table语句里设定等待时间,如果在这个等待时间里能拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者DBA再通过重试命令重复这个过程。

猜你喜欢

转载自blog.csdn.net/Sophia_0331/article/details/106985353