了解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资源。因此应该在确保业务不会出现死锁的情况下,临时关闭死锁检测。