全局锁和表级锁

根据锁的范围,MySQL的锁大致可以分为,全局锁、表级锁、行级锁

全局锁

Flush tables with read lock

缺点:

  • 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
  • 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟。备份为什么要加锁
    例: 假设你现在要维护“极客时间”的购买系统,关注的是用户账户余额表和用户课程表。 现在发起一个逻辑备份。假设备份期间,有一个用户,他购买了一门课程,业务逻辑里就要扣掉他的余额,然后往已购课程里面加上一门课。 如果时间顺序上是先备份账户余额表 (u_account),然后用户购买,然后备份用户课程表 (u_course),会怎么样呢?你可以看一下这个图:备份加锁.png
  • 可以看到,这个备份结果里,用户 A 的数据状态是“账户余额没扣,但是用户课程表里面已经多了一门课”。 如果后面用这个备份来恢复数据的话,用户 A 就发现,自己赚了。 作为用户可别觉得这样可真好啊,你可以试想一下:如果备份表的顺序反过来,先备份用户课程表再备份账户余额表,又可能会出现什么结果? 也就是说,不加锁的话,备份系统备份的得到的库不是一个逻辑时间点,这个视图是逻辑不一致的。推荐数据库备份方法
    • 官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
    • 你一定在疑惑,有了这个功能,为什么还需要 FTWRL 呢?
    • 一致性读是好,但前提是引擎要支持这个隔离级别。比如,对于 MyISAM 这种不支持事务的引擎,如果备份过程中有更新,总是只能取到最新的数据,那么就破坏了备份的一致性。
    • 这时,我们就需要使用 FTWRL 命令了。所以,single-transaction 方法只适用于所有的表使用事务引擎的库。如果有的表使用了不支持事务的引擎,那么备份就只能通过 FTWRL 方法。 这往往是 DBA 要求业务开发人员使用 InnoDB 替代 MyISAM 的原因之一。
    • 你也许会问,既然要全库只读,为什么不使用 set global readonly=true 的方式呢?
      • 确实 readonly 方式也可以让全库进入只读状态,但我还是会建议你用 FTWRL 方式,主要有两个原因:
      • 一是,在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,我不建议你使用。
      • 二是,在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。
      • 而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

表级锁

表锁

  • 表锁的语法是 lock tables … read/write。与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
    • 例:
      1. 举个例子, 如果在某个线程 A 中执行 lock tables t1 read, t2 write; 这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许,自然也不能访问其他表。
    • 在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式。而对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还是太大。

元数据锁(MDL)

  • MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。
  • 因此,在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。
    • 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
    • 读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
    • 虽然 MDL 锁是系统默认会加的,我们在给表新加字段的时候尤其要小心,尤其是那些访问频繁的表,一个MDL写锁会挡住所有的标的查询更新请求,查询频繁可能会拖垮整个库

如何安全给小表加个字段