今天我要跟你聊聊 MySQL 的锁。数据库锁设计的初衷是处理并发问题。做为多用户共享的资源,当出现并发访问的时候,数据库须要合理地控制资源的访问规则。而锁就是用来
实现这些访问规则的重要数据结构。mysql
根据加锁的范围,MySQL 里面的锁大体能够分红全局锁、表级锁和行锁三类。今天这篇文章,我会和你分享全局锁和表级锁。而关于行锁的内容,我会留着在下一篇文章中再和
你详细介绍。sql
这里须要说明的是,锁的设计比较复杂,这两篇文章不会涉及锁的具体实现细节,主要介绍的是碰到锁时的现象和其背后的原理。数据库
一、什么是全局锁安全
顾名思义,全局锁就是对整个数据库实例加锁。
MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你须要让整个库处于只读状态的时候,可使用这个命令,
以后其余线程的如下语句会被阻塞:bash
二、全局锁的典型使用场景?session
全局锁的典型使用场景是,作全库逻辑备份、也就是把整库每一个表都 select 出来存成文本。数据结构
三、加全局锁不太好并发
之前有一种作法,是经过 FTWRL 确保不会有其余线程对数据库作更新,而后对整个库作备份。注意,在备份过程当中整个库彻底处于只读状态。
可是让整库都只读,听上去就很危险:工具
看来加全局锁不太好。可是细想一下,备份为何要加锁呢?咱们来看一下不加锁会有什么问题。spa
四、备份为何要加锁?不加锁会有什么问题?
假设你如今要维护“极客时间”的购买系统,关注的是用户帐户余额表和用户课程表。如今发起一个逻辑备份。假设备份期间,有一个用户,他购买了一门课程,业务逻辑里就
要扣掉他的余额,而后往已购课程里面加上一门课。
若是时间顺序上是先备份帐户余额表 (u_account),而后用户购买,而后备份用户课程表(u_course),会怎么样呢?你能够看一下这个图:
能够看到,这个备份结果里,用户 A 的数据状态是“帐户余额没扣,可是用户课程表里面已经多了一门课”。若是后面用这个备份来恢复数据的话,用户 A 就发现,本身赚了。
做为用户可别以为这样可真好啊,你能够试想一下:若是备份表的顺序反过来,先备份用户课程表再备份帐户余额表,又可能会出现什么结果?
也就是说,不加锁的话,备份系统备份的获得的库不是一个逻辑时间点,这个视图是逻辑不一致的。
五、mysqldump备份过程当中数据是能够正常更新的
说到视图你确定想起来了,咱们在前面讲事务隔离的时候,实际上是有一个方法可以拿到一致性视图的,对吧?
是的,就是在可重复读隔离级别下开启一个事务。
备注:若是你对事务隔离级别的概念不是很清晰的话,能够再回顾一下第 3篇文章《事务隔离:为何你改了我还看不见?》中的相关内容。
官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction的时候,导数据以前就会启动一个事务,来确保拿到一致性视图。而因为 MVCC 的支持,
这个过程当中数据是能够正常更新的。
六、有了mysqldump这个功能,为何还须要 FTWRL 呢?
一致性读是好,但前提是引擎要支持这个隔离级别。好比,对于 MyISAM 这种不支持事务的引擎,若是备份过程当中有更新,老是只能取到最新的数据,
那么就破坏了备份的一致性。这时,咱们就须要使用FTWRL 命令了。
因此,single-transaction 方法只适用于全部的表使用事务引擎的库。若是有的表使用了不支持事务的引擎,那么备份就只能经过 FTWRL 方法。
这每每是 DBA 要求业务开发人员使用 InnoDB 替代 MyISAM 的缘由之一。
七、既然要全库只读,为何不使用 set global readonly=true 的方式呢?
你也许会问,既然要全库只读,为何不使用 set global readonly=true 的方式呢?确实 readonly 方式也可让全库进入只读状态,但我仍是会建议你用 FTWRL 方式,主要
有两个缘由:
业务的更新不仅是增删改数据(DML),还有多是加字段等修改表结构的操做(DDL)。不管是哪一种方法,一个库被全局锁上之后,你要对里面任何一个表作加字段操做,都是会被锁住的。
可是,即便没有被全局锁住,加字段也不是就能一路顺风的,由于你还会碰到接下来咱们要介绍的表级锁。
MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)
表锁的语法是 lock tables … read/write。与 FTWRL 相似,能够用 unlock tables 主动释放锁,也能够在客户端断开的时候自动释放。须要注意,lock tables 语法除了会限制别
的线程的读写外,也限定了本线程接下来的操做对象。
举个例子, 若是在某个线程 A 中执行 lock tables t1 read, t2 write; 这个语句,则其余线程写 t一、读写 t2 的语句都会被阻塞。同时,线程 A 在执行 unlock tables 以前,也只能执
行读 t一、读写 t2 的操做。连写 t1 都不容许,天然也不能访问其余表。
在尚未出现更细粒度的锁的时候,表锁是最经常使用的处理并发的方式。而对于 InnoDB 这种支持行锁的引擎,通常不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面
仍是太大。
另外一类表级的锁是 MDL(metadata lock)。MDL 不须要显式使用,在访问一个表的时
候会被自动加上。MDL 的做用是,保证读写的正确性。你能够想象一下,若是一个查询正在遍历一个表中的数据,而执行期间另外一个线程对这个表结构作变动,删了一列,那么查
询线程拿到的结果跟表结构对不上,确定是不行的。
所以,在 MySQL 5.5 版本中引入了 MDL,当对一个表作增删改查操做的时候,加 MDL读锁;当要对表作结构变动操做的时候,加 MDL 写锁。
虽然 MDL 锁是系统默认会加的,但倒是你不能忽略的一个机制。好比下面这个例子,我常常看到有人掉到这个坑里:给一个小表加个字段,致使整个库挂了。
你确定知道,给一个表加字段,或者修改字段,或者加索引,须要扫描全表的数据。在对大表操做的时候,你确定会特别当心,以避免对线上服务形成影响。而实际上,即便是小
表,操做不慎也会出问题。咱们来看一下下面的操做序列,假设表 t 是一个小表。
备注:这里的实验环境是 MySQL 5.6。
咱们能够看到 session A 先启动,这时候会对表 t 加一个 MDL 读锁。因为 session B 须要的也是 MDL 读锁,所以能够正常执行。
以后 session C 会被 blocked,是由于 session A 的 MDL 读锁尚未释放,而 sessionC 须要 MDL 写锁,所以只能被阻塞。
若是只有 session C 本身被阻塞还没什么关系,可是以后全部要在表 t 上新申请 MDL 读锁的请求也会被 session C 阻塞。前面咱们说了,全部对表的增删改查操做都须要先申请
MDL 读锁,就都被锁住,等于这个表如今彻底不可读写了。
若是某个表上的查询语句频繁,并且客户端有重试机制,也就是说超时后会再起一个新session 再请求的话,这个库的线程很快就会爆满。
你如今应该知道了,事务中的 MDL 锁,在语句执行开始时申请,可是语句结束后并不会立刻释放,而会等到整个事务提交后再释放。
基于上面的分析,咱们来讨论一个问题,如何安全地给小表加字段?
首先咱们要解决长事务,事务不提交,就会一直占着 MDL 锁。在 MySQL 的information_schema 库的 innodb_trx 表中,你能够查到当前执行中的事务。若是你要
作 DDL 变动的表恰好有长事务在执行,要考虑先暂停 DDL,或者 kill 掉这个长事务。
但考虑一下这个场景。若是你要变动的表是一个热点表,虽然数据量不大,可是上面的请求很频繁,而你不得不加个字段,你该怎么作呢?
这时候 kill 可能未必管用,由于新的请求立刻就来了。比较理想的机制是,在 alter table语句里面设定等待时间,若是在这个指定的等待时间里面可以拿到 MDL 写锁最好,拿不
到也不要阻塞后面的业务语句,先放弃。以后开发人员或者 DBA 再经过重试命令重复这个过程。
MariaDB 已经合并了 AliSQL 的这个功能,因此这两个开源分支目前都支持 DDLNOWAIT/WAIT n 这个语法。
ALTER TABLE tbl_name NOWAIT add column ... ALTER TABLE tbl_name WAIT N add column ...
今天,我跟你介绍了 MySQL 的全局锁和表级锁。
全局锁主要用在逻辑备份过程当中。对于所有是 InnoDB 引擎的库,我建议你选择使用–single-transaction 参数,对应用会更友好。
表锁通常是在数据库引擎不支持行锁的时候才会被用到的。若是你发现你的应用程序里有lock tables 这样的语句,你须要追查一下,比较可能的状况是:
MDL 会直到事务提交才释放,在作表结构变动的时候,你必定要当心不要致使锁住线上查询和更新。
最后,我给你留一个问题吧。备份通常都会在备库上执行,你在用–single-transaction 方法作逻辑备份的过程当中,若是主库上的一个小表作了一个 DDL,好比给一个表上加了一
列。这时候,从备库上会看到什么现象呢?
你能够把你的思考和观点写在留言区里,我会在下一篇文章的末尾和你讨论这个问题。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一块儿阅读。
说明:这篇文章没有介绍到物理备份,物理备份会有一篇单独的文章。
上期的问题是关于对联合主键索引和 InnoDB 索引组织表的理解。我直接贴 @老杨同志 的回复略做修改以下(我修改的部分用橙色标出):表记录
–a--|–b--|–c--|–d-- 1 2 3 d 1 3 2 d 1 4 3 d 2 1 3 d 2 2 2 d 2 3 4 d
主键 a,b 的聚簇索引组织顺序至关于 order by a,b ,也就是先按 a 排序,再按 b 排序,c 无序。
索引 ca 的组织是先按 c 排序,再按 a 排序,同时记录主键–c--|–a--|–主键部分b-- (注意,这里不是 ab,而是只有 b)
2 1 3 2 2 2 3 1 2 3 1 4 3 2 1 4 2 3
这个跟索引 c 的数据是如出一辙的。
索引 cb 的组织是先按 c 排序,在按 b 排序,同时记录主键–c--|–b--|–主键部分a-- (同上)
2 2 2 2 3 1 3 1 2 3 2 1 3 4 1 4 3 2
因此,结论是 ca 能够去掉,cb 须要保留。