1前言
注意:目前整个 MySQL 系列都是 8.0.X 版本
不过我会兼容 5.7、5.8版本 ,差异化的地方会有标出来(比如官方文档)
比如 上一篇: MySQL系列:索引(下)
这篇索引(下) 是从那天的下午14 左右一直淦到晚上 23点多... .
2目录
全局锁 表锁
3继续
锁,设计锁是为了处理并发问题。多用户共享资源出现并发时,就需要合理地控制资源的访问规则。而锁,就是用来实现这些访问规则的重要数据结构。
根据 MySQL 加锁的范围,可以大致分为全局锁
、表锁
、行锁
三类。
4全局锁
全局锁就是字面意思,对整个数据库实例加锁。MySQL 自身有个一个加全局锁的方法 Flush tables with read lock(FTWRL)
。如果需要整个库处于只读状态,那么就可以使用这个命令,之后其它线程的以下语句都会被阻塞:
数据库CRUD 语句 数据定义语句(建表、修改表结构等) 更新类事务的提交语句
全局锁的典型使用场景是,做全库逻辑备份 ,换句话说就是把整库每个表都 select 出来存成文本。
之前有一种做法通过 FLUSH TABLES WITH READ LOCK简称(FTWRL)
,该命令主要用于备份工具获取一致性备份(数据与binlog位点匹配)。由于FTWRL总共需要持有两把全局的MDL锁,并且还需要关闭所有表对象,因此这个命令的杀伤性很大,执行命令时容易导致库hang住。如果是主库,则业务无法正常访问;如果是备库,则会导致SQL线程卡住,主备延迟。
FTWRL主要包括3个步骤:
上全局读锁(lock_global_read_lock) 清理表缓存(close_cached_tables) 上全局COMMIT锁(make_global_read_lock_block_commit)
详情可参考 博客园 天士梦 的: https://www.cnblogs.com/cchust/p/4603599.html#:~:text=FLUSH TABLES WITH,READ LOCK简称 (FTWRL),该命令主要用于备份工具获取一致性备份 (数据与binlog位点匹配)。
但让整库都只读,第一想法那项目肯定都要死掉了:
如果你在主库上备份,那么备份期间都不能执行更新,业务基本上就得停罢; 如果你再从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟。
那么有个问题,全局加锁问题这么多,如果不加锁会发生什么。
假设你现在要维护“极客时间”的购买系统,关注的是用户账号余额表和用户课程表。
说到这里插一句:《Mysql比知比会》
,《高性能mysql》
,《MySQL技术内幕:InnoDB存储引擎》
,《mysql实战45讲》
,《mysql是怎样运行的从根上了解》
这 6 本通透的话,那么你就超凡入圣了。
还有一本被吃了。
目前我也就看了几遍 《mysql实战45讲》,和一遍《高性能mysql》,整个系列也都是以45 讲为基础,精读不写点总结啥的总感觉不是很通透。
继续,现在发起一个逻辑备份,假设备份期间有个用户他购买了一门课程,业务逻辑里就要扣掉他的余额,然后往已购课程列表里面加上这门课。
如果时间顺序上是先备份账号余额表(u_account),然后用户购买,再备份用户课程表(u_course),会发生什么?
可以看到这个备份结果里,用户 A 的数据状态是“账号余额没扣,但是用户课程表里面已经加上了这门课”。如果后面用这个备份来恢复数据的话,用户 A 就会发现,自己赚了。
那么如果备份表的顺序反过来,先备份用户课程表再备份账号余额表,也就是说不加锁的话,备份系统备份得到的库不是一个逻辑时间点,这个视图是逻辑不一致的。
MySQL 系列:事务隔离 有介绍在可重复读隔离级别下开启一个事物拿到一致性视图。
官方自带的逻辑备份工具是 MySQLdump。当MySQLdump使用参数–single-transaction
的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
官网MySQLdump
-- https://dev.mysql.com/doc/refman/8.0/en/mysqldump-sql-format.html
那么有了这个为什么还需要 FTWRL ?一致性读是好,但前提是引擎要支持这个隔离级别。
比如:对于MyISAM这种不支持事物的引擎,如果备份过程中有更新,总是只能取到最新的数据,那么就破坏了备份的一致性。这时,就需要使用 FTWRL 命令了。
–single-transaction
方法只适用于所有的表使用事务引擎的库。 如果有的表使用了不支持事务的引擎,那么备份只能通过FTWRL
方法。也是MyISAM 被 InnoDB 取代的原因之一。
全库只读,还有一种使用 set global readonly=true 的方式,但还是建议使用FTWRL
方式:
在有些系统中, readonly
的值会被用来做其他逻辑,比如 用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面会更大,一般不建议使用。在异常处理机制上有差异。 如果执行 FTWRL
命令后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库会回到可以正常更新的状态。 - 而如果将整个库设置为readonly
之后,如果客户端发生异常,则数据库就会一直保持readonly
状态,这样会导致整个库长时间处于不可写状态,风险非常高。
业务的更新不只是增删改数据(DML),还有可能是加字段等修改表结构的操作(DDL)。
无论哪种方法,一个库被全局锁上后,对任何一个表做加字段操作,都是会被锁住的。
即使没有被全局锁住,加字段也不是一帆风顺的,还会碰到表级锁。
5表级锁
MySQL 表级锁有两种:
表锁 元数据锁(meta data lock) MDL
表锁的语法是 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
命令来控制并发,毕竟锁住整个表的影响面还是太大。
另一类表级锁是 MDL(metadata lock). MDL 不需要显示使用,在访问一个表的时候会被自动加上。MDL 的作用是保证读写的正确性。
如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果肯定跟表结构对不上。
因此,在 MySQL5.5 版本中引入了 MDL,当对一个表做增删改查时,加 MDL 读锁;当要对表结构变更操作的时候,加 MDL 写锁。
读锁之间不互斥,所以你可以有多个线程同时对一张表增删改查。 读写锁之间、写锁之间是互斥、排他的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
给表加字段、修改字段、加索引,都需要扫描全表的数据。一不小心就会出问题:
可以看到 sessionA 先启动,这时会对表 t 加一个 MDL 读锁。由于sessionB 需要的也是 MDL 读锁,也能正常执行,上面说过,读锁之间不互斥。
之后 sessionC 会被 blocked,是因为 sessionA 的 MDL 读锁还没有释放,而 sessionC 需要 MDL 写锁,因此会阻塞。
如果只有 sessionC 自己被阻塞倒是问题不大,关键是再这之后所有要在表 t 上新申请 MDL 读锁的请求也会被 sessionC 阻塞。
所有对表的增删改查操作都需要先申请 MDL 读锁,如果都被锁住了,等于这个表 现在完全不可读写了。
如果某一个表上的查询语句频繁,而且客户端有重试机制,也就是说超时后会再起一个新session 再请求的话,这个库的线程很快就会饱满。
事务中的 MDL 锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而是等到整个事务提交后再释放。
那么有个问题 -- 长事务
,事务不提交,就会一直占着 MDL 锁。在 MySQL 的 information_schema 库的 innodb_trx 表中
可以查到当前执行中的事务。如果你要做 DLL 变更的表刚好有长事务在执行,要先考虑先暂停 DDL,或者 kill(干掉)长事务。
如果要变更的表是一个热点表,虽然数据量不大,但是请求频繁,又必须得加这个字段,那么《一键删库跑路》了解一波。
这时 kill 可能未必管用,因为新的请求马上就来了。比较理想的机制是,在 alter table
语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。
MariaDB 已经合并了 AliSQL 的这个功能,所以这两个开源分支目前都支持 DDL NOWAIT/WAIT n
这个语法。
ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...
Github开源地址:https://github.com/alibaba/AliSQL