01 前言
小胖真的让人不省心。继上次小胖误删数据之后,这次这货直接给我把整个表锁住了。页面无响应,用户疯狂投诉,我特么脸都绿了。。。
事情是这样的,线上有个数据库几十万的数据,由于一开始没做好规划并没有给热点字段加索引。我就让小胖有空加个索引,没想到这货在用户使用高峰期加。。。
知道原因,我还是比较淡定的。毕竟最近都在研究 MySQL,对于 MySQL 锁的问题解决起来还是得心应手。小胖见我三两下就解决了问题,客户也给出了卧槽,牛逼的肯定,忙问我怎么解决的,我点燃手中 82 年的华子深深吸了一口,花了几个小时写了这篇文章给它。
全文 6618 字,从下午两点写到晚上九点,先上张思维导图镇楼:
1.1 往期精彩
02 全局锁
全局锁是对整个数据库实例加锁,让其处于只读状态。MySQL 可以通过 Flush tables with read lock (FTWRL) 实现, PS:unlock tables 可以解除只读状态。
执行该命令之后,数据更新语句(DML)数据的增删改操作以及数据操纵语句(DDL)修改表结构等操作将被阻塞。
2.1 全局锁的应用场景
最典型的要数全库逻辑备份,就是把整个库的所有表都 select 出来存成文本。
假设现在我的数据库是读写分离的:主写从读。有一种思路是使用 FTWRL 定时备份 + binlog 恢复增量数据。
用 FTWRL 确保备份期间不会有其他线程对数据库做更新操作,然后整库备份。这时数据库实例会处于只读状态,就会造成两个问题:
- 在主库备份,备份期间不能写入,业务就会收到严重影响。
- 在从库备份,备份期间不能执行主库同步的过来的 binlog(锁住了,不能写入),就会导致主从延迟,业务也会受到影响。
如果非要用这种方式,那么建议是在一个月黑风高,系统最少用户在使用的时候。
2.2 为什么要加锁?
上面说了,利用全局锁备份会造成两个问题。那不加锁行吗?废话,肯定是不行的。不加锁,你养我呀(备份出问题被开除)?
不加锁同样会出现意想不到的问题:举个栗子,看电影买票,系统有个余额表和用户已购票表。
锁?
现在我要备份,期间有人买票。逻辑上:余额表减掉相应金额,已购票表加上一张票。备份就会出现两个问题:
- 先备份余额表,用户购买,再备份用户表。这是会怎样呢?方便理解,我画张图:
从上图,我们也大概知道发生了啥。我来捋一捋:
T1 时刻是备份前两个表的数据状态;T2 时刻开始备份,只备份了余额表;T3 时刻,由于没有加锁,用户买票;T4 时刻是买完票后的状态;T5 时刻备份到已购票表。
看最终的备份状态你发现没有???用户钱没少,票却多了一张(用户窃喜,程序员苦逼)。
以上就是不加锁的下场,它会导致数据前后不一致。这还是先备份余额表后备份已购票表的情况出现的问题。
如果,备份的顺序颠倒一下就会出现:用户钱少了,票却没增加(你指定被投诉,程序员还是苦逼)。
通过上面分析知道,不加锁的话。备份得到的库不是同一个逻辑时间点,才会造成这种后果。那怎么保证是同一逻辑时间点呢?
这时候就要引入上篇文章提到的一致性视图。
2.3 一致性视图备份
上篇说到在可重读隔离级别下开启一个事务,会创建一致性视图。
PS:不了解事务,看这里肯定一脸懵。建议看这篇:《MySQL 事务与 MVCC 原理》
你可能会问:狗狗你说得,我都知道。问题是怎么在备份的时候开启事务呢?
是这样,MySQL 自带的逻辑备份工具是 mysqldump 。它使用参数 -single-transaction 可以启动一个事务,从而确保拿到一致性视图。并且由于 MVCC 的支持,备份期间数据库仍可以写入。比如像这样:
// 详细参数见:cnblogs.com/markLogZhu/p/11398028.html // 格式:mysqldump [选项] --数据库名 [选项 表名] > 脚本名 mysqldump -uroot -p test -single-transaction > /backup/mysqldump/test.db
这时好学的朋友可能会说:既然有了这功能,那不用 FTWRL 命令行不行呀?
答案是:可以的,前提是你的数据库引擎要支持可重复读隔离级别,比如:InnDB;如果是 MyLSAM,那么很抱歉,你还是得用 FTWRL,不然备份拿到的视图还是不一致。就会出现上面数据不一致的问题。
2.4 readonly = 1 的方式行么?
提到全库只读你可能想到这个命令:
mysql> set global read_only=1;
能使用它来让全库只读么?不行或者说是不建议,主要原因有三点:
- 影响业务逻辑;set global read_only=1 可能会用于一些业务判断,比如:主从的判断,从库只读。
- 异常不释放状态;FTRWL 命令在异常发生时,会自动释放全局锁;而 set global read_only=1 在异常时,数据库会一直保持只读状态,这时候业务就完犊子了。
- set global read_only=1 这个命令对超级管理员角色无效;备份期间,超管更新数据库还是会导致数据不一致问题。