MySQL中的全局锁和表级锁

全局锁和表锁

数据库锁设计的初衷是解决并发出现的一些问题。当出现并发访问的时候,数据库需要合理的控制资源的访问规则。而锁就是访问规则的重要数据结构。

根据锁的范围,分为全局锁、表级锁和行级锁三类。

全局锁

全局锁就是对整个数据库实例加锁。MySQL提供而一个全局读锁的方法。命令是

Flush tables with read lock

当你需要让整个库处于只读的状态时,可以使用这个命令,之后其他线程的数据更新语句、数据定义语句(建表、修改表等)和更新事务的提交语句等都会被阻塞。

全局表的典型使用场景是作为全局逻辑备份
也就是把整个库的每个表都 select出来 存成文本。以前的做法是通过执行声明的语句确保不会有其他线程对数据库做更新操作,然后对整个库做备份。注意,在备份过程中整个库完全处于只读状态。

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

看来加全局锁问题会很多,其实备份为什么要加锁呢?

如果不加锁会产生哪些问题?

在支付的场景下,生成订单和扣款两个操作是有先后顺序的。如果是先备份账户余额表再生成订单表, 中间的这个时间段可能会出现一些问题,如果账户余额已经扣了但是生成订单表失败,那么用户会看到自己已经付款了但是还没有生成订单信息。也就是说不加锁的情况下备份系统得到的数据库不是一个逻辑时间点,视图的逻辑不一致。

既然要读全库,为什么不使用set global readonly = true 的方式呢?

确实readonly也可以让钱库进入只读状态,但是建议使用

Flush tables with read lock

主要原因有两个:

  • 在有些系统中readonly的值会被用来做其他逻辑,比如用来判断一个库是主库还是从库。因此在修改global变量的方式影响会更大,不建议使用。
  • 在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为readonly后,如果发生异常,数据库不会释放锁,这样导致数据库长时间处于不可写的状态,风险较高。

业务的更新不只是增删改数据(DML),还有可能是加字段等修改表结构的操作(DDL)。不论是哪种方法,一个库被全局锁上后,你要对立面的任何一个表做加字段的操作,都是会被锁住的。

但是即使没有被全局锁住,加字段也不是就能一帆风顺的,因为你还会碰上表级锁。

表级锁

MySQL表级别的锁有两种:一种是表锁,一种是元数据锁(MDL)

  • 表锁的语法是lock tables ... read/write 。与FTWRL类似,可以用unlock tables主动释放锁,也可以在客户端断开的时候自动释放锁。需要注意的是:lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来要处理的事。

  • 另一类表锁是MDL。MDL不需要显示使用,在访问一个表的时候回自动加上。MDL的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间的另一个线程对这个表的结果做了更改,删了一列,那么查询线程拿到的结果和原来的结构对不上肯定是不行的。

因此在5.5版本里面引入了元数据锁,当对于一个表做增删改查的时候,加MDL读锁;当要对表结构做变更操作的时候加MDL写锁。

  • 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查
  • 读写锁之间、写锁与写锁之间是互斥的,用于保证变更表结构操作的安全性。因此如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完之后才能开始执行。

下面需要注意的是
在这里插入图片描述

sessionA先启动,这时候会对表 t 加一个MDL读锁。由于sessionB需要的也是读锁因此可以正常执行。
但是sessionC会被阻塞,因为sessionC是需要写锁的,读锁和写锁之间是会被阻塞的。

这时候后面又来了sessionD,sessionD需要读锁,但是前面的sessionC还没获取到写锁所以一直阻塞到那里,他也跟着阻塞。如果某个表上的查询语句很频繁,而且客户端有重试机制,也就是说超时后会再起一个新的session请求,这样整个库的线程就会被堆满。

解决方法:
在MySQL的information_schema库的innodb_trx表中,你可以查到当前执行中的事务。如果你要做DDL变更的表,刚好有长事务在执行,要考虑先暂停DDL,或者直接kill掉事务。

如果更新表的请求很频繁的话,kill未必管用,因为新的请求会很快到来。比较理想的机制是,在alter table语句里面设置等待时间,如果超过了这个时间就先放弃不在阻塞,之后开发人员再重试这个命令。

MariaDB一个数据库引擎已经合并了AliSQL的这个功能,所以这两个开源分支目前都支持DDL,设置等待时间的语法。


ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...

猜你喜欢

转载自blog.csdn.net/qq_43672652/article/details/106159534