Mysql全局锁和表锁

171 阅读6分钟

锁的初衷是为了解决在并发访问共享资源的背景下出现的一些安全问题,数据库通过锁可以合理的控制事务访问资源的顺序。

锁的分类

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

全局锁

全局锁即对整个数据库加锁。

数据库备份及加全局锁后可能存在的问题

典型的应用场景是做全库备份。以前有一种做法是——通过对数据库加全局的读锁,这时其他线程的更新语句都会被阻塞,并将库中的表select出来进行备份。Mysql提供了Flush tables with read lock(FTWRL)命令来实现加全局读锁。
虽然这种方式可以实现全库备份,但这种方式让整个库都变成只读会导致一系列的问题。 假设数据库为主从结构,如果要进行备份,则有两种选择,在主库上进行备份或者在从库上进行备份。假如在主库上进行备份,则在备份期间主库不能进行更新操作,必然会影响到业务的进行。此时会考虑到从库一般只用来进行读,所以选择从库进行备份,此时主库可以正常进行更新操作,业务也能正常进行。但是进行从库进行备份时,会导致从库从主库备份的binlog日志不能执行,从而导致主从延迟。

是否有不加全局锁的方式来实现数据的备份?

有! Mysql官方自带了一个逻辑备份工具mysqldum,当mysqldump失业参数-single-transaction时,导数据前会拿到一个一致性视图,同时由于MVCC的支持,数据库中数据可以正常更新。 但是这有一个前提,需要引擎支持这个隔离级别,对于MyISAM这种不支持事务的引擎,如果在备份过程中有更新操作,则会导致备份和实际数据的不一致,此时又只能采用FTWRL的方式。

除了FTWRL还有set global readonly = true的方式实现全库只读,为什么不用该方式来替代FTWRL呢?

这是因为set global readonly = true的方式主要存在以下两方面的问题。

  • 一是,在某些数据库系统,readonly被会用来做主库和从库的判断逻辑(一般主库readonlyfalse,从库为true)。
  • 二是,客户端在出现异常断开连接时,FTWRL会自动释放全局锁,数据库会回到正常更新状态,而readonly的方式则将整个库设置为只读,数据库会一致保持只读状态,如果没有及时发现该问题,会使得数据库长期不可写。

表级锁

Mysql种表级锁有两种:一种是表锁(增删改(DML)相关),一种是元数据锁(meta data lock(MDL))。
表锁的语法是lock tables T read/write。与FTWRL一样,可以通过unlock tables T释放, 也可以通过客户端断开连接自动释放锁。它的作用主要是防止多线程同时访问修改同一数据导致的并发异常。
MDL锁则不需要显示的使用,会在访问表的时候隐式的自动加上。它主要的作用是在多线程访问数据时,某线程对表结构进行更改导致其他线程获取到的数据与被更改后的表结构对不上。
Mysql5.5版本引入了MDL,对表进行增删改查时会加MDL读锁,对表结构进行修改时会加MDL写锁。读锁之间是不互斥的,即可以有多个线程同时获取到该锁,之后进行增删改查。而写锁之间及读锁和写锁是互斥的,多个线程不能同时获得,即如果有线程要对表结构进行修改,则其他线程必须等待。

根据MDL的加锁的特点需要避免因修改字段导致表完全不可读写的问题 假设此时在同一表上有三个事务分别有一条sql需要执行,第一个sql对表进行select操作,第二个sql对表进行alter修改表结构的操作,第三个sql对表执行select操作,根据MDL加锁特点,第一个sql执行时,会对该表加MDL读锁,此时第二个sql对表结构进行修改,但因为该表已经被加了MDL读锁,该sql获取MDL写锁时会被阻塞,此外第三个sql执行时会去获取MDL读锁,也会被阻塞,此时相当于该表完全的不可以读写了。

注意:以上事务是在未提交的情况下进行的

所以应该如何给表添加、修改或删除字段?
从根据以上分析可以发现,如果事务不是长事务,即很快就能执行完提交事务,就可以避免锁互斥导致完全不可读写的问题。
所以我们首先分析长事务,长事务在事务提交前长时一直占着MDL锁。此时我们有两种选择,一是先暂定DDL,让长事务先执行完,二是kill掉长事务。查询长事务可以通过information_schema库中的innodb_trx表中查询。
但是如果是虽然不是长事务,但该表上的查询或修改请求十分的频繁,但又不得不进行DDL的情况下,kill就可能没有办法解决该问题了。此时比较理想的办法是在alter table语句中设置等待时间,如果在指定的时间拿到MDL锁就执行,否则先放弃,不影响频繁的请求。之后开发人员再重复该过程即可。 具体的语法为

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

总结

全局锁主要用在逻辑备份过程中。对于全部是 InnoDB 引擎建议使用-single-transaction参数,而对于那些不支持事务的引擎,比如MyISAM则建议使用FTWRL,不建议使用set global readonly = true的方式。
表级锁有两种表锁和MDL锁,表锁一般是在数据库引擎不支持行锁的情况下使用,建议进行更换为Innodb引擎。MDL锁会在事务提交时才释放,在锁表结构变更的时候,需要注意避免锁住表,而出现该表不可进行查询和更新。