了解MySQL的锁

MySQL中可以把锁大致分成全局锁,表锁以及行锁三类。

全局锁

全局锁会锁住整个数据库实例。可以通过下面命令(FTWRL)给数据库加全局读锁:

Flush tables with read lock;

解锁通过 unlock tables;

使用此命令后,数据更新、数据定义语句以及更新类事务的提交语句会被阻塞住。全局锁的典型场所是做全库的逻辑备份(备份过程中整个库处于完全只读状态)。但是整个库只读会造成一些危险可能:

  • 如果在主库备份,备份期间不能更新,业务不能正常进行
  • 从库备份,备份期间不能同步binlog,会导致主从延迟

但在支持事务的存储引擎下,通常会使用一致性读进行备份。官方自带的逻辑备份工具为mysqldump,使用参数-single-transcation时,导数据前就会启动一个事务,确保拿到一致性视图。由于MVCC的支持,此过程中可以正常更新。

还可以通过set global readonly=true的方式使数据库只读,但是通常不建议使用此方式。

  • readonly值可能用来做其他逻辑使用,如判断从库还是主库,因此不建议修改
  • FTWRL命令之后如果客户端发生异常断开,会自动释放全局锁,数据库能回到正常更新状态。而如果设置为readonly,如果发生了异常,仍然会保持整个库处于readonly状态,影响业务。
  • 超级权限下,readonly无效

业务的更新不只是对数据进行修改(DML),也有可能对修改字段等修改表的操作(DDL)。不论哪种,全局锁之后,对任何一个表加字段都会被锁住。

即使没有全局锁住,加字段也不会一帆风顺,因为还有表级锁。

表级锁

MySQL中表级锁有两种:表锁和元数据锁(MDL)。

表锁

表锁的语法为:

lock tables xxx read/write

我们也可以通过 unlock tables 主动释放锁,也可以在客户端断开时自动释放。lock tables语法除了限制别的线程读写外,也会限制本线程接下来的操作对象。

如:线程A执行了 lock tables t1 read,t2 write;,其他线程则写t1,读t2时都会被阻塞。同时,线程A自己在解锁之前,也只能执行读t1和读写t2操作。

在Navicat中,MySQL5.7.35下,写锁情况下,本线程也不能读,只能写,原因貌似有MDL锁。

MySQL自带客户端下正常。

MDL

在MySQL5.5时引入了MDL。MDL不需要显示的使用,在访问一个表时会被自动加上。

MDL的作用是保证读写的正确性。

当对一个表做增删改操作时(DML),加MDL读锁,当对表做结构表更操作时(DDL)加MDL写锁。

  • 读锁之间不互斥,因此可以多个线程对同一张表进行增删改查。
  • 写锁与读锁,写锁互斥,用于保证结构变更的安全性。

行锁

全局锁和表级锁是在Server层实现,行锁是由引擎自己实现。

两阶段协议:行锁在需要的时候加上,但是会等在事务结束时才释放。

因此,如果事物中需要锁多个行,把可能造成冲突,最有可能影响并发度的锁放在后面。

死锁和死锁检测

死锁即事务之间互相等待对方资源释放,造成的一种循环关系的无限等待状态。出现死锁后:

  • 设置等待超时,可以通过innodb_lock_wait_timeout(默认为50s)设置
  • 死锁检测,发现思索后主动混滚死锁链条中的某一条事务,通过参数innodb_deadlock_detect设置为on开启

通常会采用死锁检测,因为不同的业务,可以接受的锁等待时间不同。但是死锁检测需要消耗大量CPU资源。因此应该在确保业务不会出现死锁的情况下,临时关闭死锁检测。

最后修改:2021 年 09 月 28 日
如果觉得我的文章对你有用,请随意赞赏