阿钊的写字板

专注于 Java 技术栈与分布式系统实践,分享技术难题攻克、系统优化实战与信创项目开发经验。

【MySQL 实战 45 讲:第六章节(全局锁和表锁)】学习小节

数据库锁的设计初衷是用于解决并发问题的,因为当多个用户访问或修改同一资源的时候就会产生资源的互斥性。如果不对资源的访问进行并发限制,会导致大家都在争抢同一个资源,谁都没办法用了。为了解决这个问题就需要系统层面去合理的控制资源的访问顺序和规则,锁就是用于实现这种并发访问的一种机制。想象一下你去 ATM 机取钱,ATM 机一次只能允许一个用户去操作,如果你和别人争抢同一 ATM 机器,在谁都不避让的情况下那大家都没法去用了,如果排队去操作的话,大家遵守规则就都可以高效的完成,而 ATM 的大门就像是一把锁,进去的人通过给大门上锁来杜绝外面的人进来,等自己弄完了出去的时候再把门解锁,其他的人才能进来。

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

全局锁就是对整个数据库实例加上一把锁,如果需要加全局锁可以通过 flush tables with read lock(缩写就是FTWRL),这个命令可以让整个MySQL 的实例库处于只读状态,当其他用户线程去修改实例中的表和修改数据的时候都会被阻塞,导致不能提交事务操作。全局锁的这个特性可以让实例处于只读状态,此时可以通过 select 查询进行备份的操作。但是这种方式在主从集群的环境下就可能会产生问题,主从的设计是提高数据库的高可用性和数据库冗余同时提高数据库的吞吐量,如果一台数据库实例挂了,此时另一台数据库可以通过某些机制切换为主库正常的提供服务,同时通过数据库冗余进行数据的备份处理。但是如果让整个数据库都处于只读模式就可能导致以下的两种情况:

  1. 如果只在主库上进行备份的操作,那么在备份的时候主库都不能执行更新的操作,因为此时其他用户的修改操作都会被阻塞,导致事务不能正常的提交,那么此时业务系统的新数据就无法写入。
  2. 如果在从库上做备份,备份的操作在执行期间就不能执行主库同步过来的 binlog,会导致主从的同步出现延迟,同步出现延迟就会导致主从的数据不一致。
    那如果备份的时候不加锁会怎么样呢?首先如果备份期间不加锁,也就意味着在执行备份操作的时候,其他用户还是可以正常修改和提交事务的操作,那么可能会导致数据出现不一致的问题,因为有些业务可能要同时修改多个表,但事务的提交和备份的时间点可能是不同的,此时的数据备份也是无意义的。所以为了保证数据的完整性,必须加锁。

MySQL 的 mysqldump 工具是官方自带的逻辑备份工具,在使用的时候可以通过—single-transaction 的参数在导出数据之前就启动一个事务,因为MySQL 的 InnoDB 引擎是支持事务,但是 MyISAM 是不支持的。所以当数据库引擎是 InnoDB 的时候,可以通过 mysqldump 工具的 —single-transaction 参数启动一个事务来给数据库拍一个快照,此时所有的备份操作都是在这个事务中完成的,就不会影响其他用户的修改操作了。对于只支持MyISAM 的表就只能通过 fluash tables with read lcok 命令来进行锁库备份了。

使全库只读还有一种方式也能实现,就是设置 readonly 的全局属性值为 true,也可以让全库进入只读的状态。但是 readonly 的参数在主从环境下可能会被用来判断这个库是主库还是从库,影响的面积会比较大,遇到问题也不好排查。其次 flush tables with read lock 命令执行后如果客户发生异常断开后全局锁也会主动进行释放,但是如果通过设置 readonly 参数,在客户端断开后如果没有及时改变状态值,会导致全局依旧处于只读状态,风险会高很多。

表级锁有两种:表锁、元数据锁。

表锁的语法是 lock tables table_name 锁类型,锁类型有两种:read(共享锁,允许其他事务读取,但不允许修改),write(排他锁,禁止其他事务读取和修改),解锁的话可以通过unlock tables 完成,也可以在客户断开的时候自动释放。如果对 t1 进行了 read 锁,t2 进行了 write 锁,那么其他用户线程写t1 时会被阻塞掉,读写 t2 的时候也会被阻塞掉,直到客户端断开或者手动执行了unlock tables 释放锁后其他用户才可以进行读写。在unlock tables 解锁之前,也不能访问其他的表。

元数据锁(metadata lock,mdl)会在访问一个表的时候自动加上,作用是保证读写的正确性,通过对表做增、删、改、查操作的时候加读锁,对表结构做变更的时候加写锁。读锁时允许其他的用户线程读取但禁止写入,写锁时不允许其他的用户线程读和写。读锁之间不互斥,可以同时有多个用户线程对一张表增删改查。写锁互斥,如果有两个线程同时要改一张表的表结构,必须等到另一个线程执行完才能开始执行。所以如果需要给表加字段的情况下可以通过查询information_schema 库中的innodb_trx 表中有没有正在执行的事务,如果有事务正在执行,就要先考虑晚点再加,或者 kill 掉这个长事务。或者在 alter table 语句里面设定等待时间,如果在等待时间能拿到写锁最好,如果拿不到也不要阻塞后面的业务语句的执行,否则一旦阻塞就会导致整张表不能读写。

问题:在实际操作中,如何判断一个表是否适合使用 NOWAIT 或 WAIT N 语法?