Mysql 的全局锁和表级锁
数据库锁设计的初衷是处理并发问题。作为多用户共享的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则。而锁就是用来实现这些访问规则的重要数据结构。根据加锁的范围,MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类。行级锁比较复杂,这篇文章就先分享全局锁和表级锁。还有就是需要说明的是,锁的设计比较复杂,文章中基本不会出现锁的具体实现细节,主要介绍的是碰到锁时的现象和其背后的原理。
当涉及到MySQL数据库的并发控制和多个客户端同时访问数据库时,全局锁是一种常见的锁机制。全局锁是一种在整个数据库实例上施加的锁,用于限制对整个数据库的访问,从而保护数据库在某些操作期间的一致性。然而,使用全局锁需要谨慎,因为它会对整个数据库的性能产生严重影响。
全局锁的使用场景
全局锁通常用于执行一些需要对整个数据库进行维护或更改的操作,以确保在此期间其他客户端不会对数据库进行修改。一些典型的使用场景包括:
- 备份操作: 在执行数据库备份期间,可以使用全局锁来确保备份的一致性,防止其他操作干扰备份过程。
- 恢复操作: 当需要进行数据库恢复操作时,可以使用全局锁来防止其他客户端对数据库进行写操作。
- 表维护: 在执行一些需要对整个表进行维护的操作,如
ALTER TABLE
语句,可以使用全局锁来确保操作的原子性。使用全局锁的方法
使用全局锁通常需要执行以下步骤:
获取全局锁: 使用
FLUSH TABLES WITH READ LOCK (FTWRL)
语句来获取全局锁。这将会对所有表施加一个只读锁,阻止其他客户端的写操作。让整库都只读,听上去就很危险:如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟。看来加全局锁不太好。但是细想一下,备份为什么要加锁呢?
有两张表。 一张 心愿单(t_wish_list) 表, 一张 购物车 (t_cart)表
用户可以从心愿单中把物品添加到购物车中
下面的例子就是在不加
FTWRL
的时候会出现的一种情况刚开始用户的心愿单中有两本书 book1 和 book2 购物车中有一件T恤
然后开始备份心愿单表,过程中用户把book1 添加到了购物车中,最后再去备份购物车表,会出现数据不一致的问题
也就是说,不加锁的话,备份系统备份的得到的库不是一个逻辑时间点,这个视图是逻辑不一致的
但是你会说了,我平时
mysqldump
的时候也没这么麻烦啊官方自带的逻辑备份工具是 mysqldump,当 mysqldump 使用参数 –single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。
而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
那为什么还需要 FTWRL 呢?
一致性读是好,但前提是引擎要支持这个隔离级别。比如,对于 MyISAM 这种不支持事务的引擎,如果备份过程中有更新,总是只能取到最新的数据,那么就破坏了备份的一致性。这时,我们就需要使用 FTWRL 命令了。
所以,single-transaction 方法只适用于所有的表使用事务引擎的库。如果有的表使用了不支持事务的引擎,那么备份就只能通过 FTWRL 方法。这往往是 DBA 要求业务开发人员使用 InnoDB 替代 MyISAM 的原因之一。
执行维护操作: 在获取全局锁后,可以执行需要的维护操作,如备份、恢复或表维护。
释放全局锁: 在维护操作完成后,使用
UNLOCK TABLES
语句释放全局锁,允许其他客户端继续访问数据库。注意事项和潜在风险
虽然全局锁在特定场景下很有用,但是它也有一些潜在的问题和风险:
- 性能影响: 获取全局锁会导致数据库实例的性能下降,因为其他客户端被限制在只读操作。
- 长时间锁定: 如果获取全局锁后的维护操作耗时很长,会导致其他客户端长时间地无法进行写操作,影响应用的正常运行。
- 锁冲突: 如果有多个操作需要获取全局锁,可能会出现锁冲突,导致某些操作无法执行。
- 数据一致性: 虽然全局锁可以确保维护操作的一致性,但是在释放锁之前,其他操作可能会被阻塞,导致数据访问的延迟。
全局锁是MySQL中的一种重要锁机制,用于在执行维护操作时保护数据库的一致性。然而,由于全局锁会对数据库的性能产生影响,并可能导致一些潜在问题,因此在使用全局锁时需要谨慎考虑,确保在适当的情况下使用,同时尽量减少锁的持有时间,以便降低影响。
表级锁
MySQL 里面表级别的锁有两种:
表锁
表锁的语法是
lock tables … read/write
。与FTWRL
类似,可以用unlock tables
主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
就像如果在某个线程 A 中执行 lock tables t1 read, t2 write; 这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许,自然也不能访问其他表。
而对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还是太大。
元数据锁(meta data lock,MDL)
MDL 是 MySQL 数据库内部的锁机制,用于管理和保护数据库对象的元数据。元数据锁定是用来控制对数据库对象的并发操作,以确保数据库的一致性和完整性。MDL 的主要作用是控制并发操作,防止多个客户端对同一数据库对象进行不一致的操作。例如,当一个客户端正在修改表结构时,另一个客户端不能同时修改相同的表结构,以防止数据不一致或损坏。
MySQL 中的 MDL 有多种类型,分别用于不同的元数据对象,如表、锁、函数等。每种类型的 MDL 都有不同的作用和范围,用于控制不同级别的并发操作。客户端在需要访问元数据对象时,会请求获取相应的 MDL。如果其他客户端已经持有了相同类型的 MDL,请求的客户端可能会被阻塞,直到现有的 MDL 被释放。一旦操作完成,客户端会释放其持有的 MDL。
当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。
读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
但是在实际过程中需要注意以下的问题:
- 锁等待:如果多个客户端争夺相同的 MDL,会导致一些客户端等待。因此,在设计数据库架构时,应考虑如何减少锁争用,以避免性能问题。
- 长事务:长事务可能会持有 MDL 很长时间,从而阻塞其他事务的操作。因此,应该尽量避免长事务,或者在必要时释放 MDL。
- 死锁:不当的 MDL 使用可能导致死锁,因此在应用程序中应该避免在一个事务中同时获取多个 MDL。