本文主要是介绍揭秘MySQL 主从环境中大事务的传奇事迹,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
- 或许某些人会说MySQL Low,而且乐此不疲,不过,我完全也可以说某些人压根就不会用MySQL,万事万物都具有两面性的,最终结果的好坏还得看是谁在用、怎么用。关于这个问题的争论,相信大家都喜闻乐见了,尤其是数据库界的"郭德纲"的言论尤其精彩,没事听听数据库界的"郭德纲"讲讲故事、讲讲段子,也是一件蛮有意思的事情。
- 说回到大事务,大家或多或少都不太喜欢它,在日常的工作中,或许会在开发规范里明令禁止大事务(操作数据行数过万的事务可以毛估估的算作是大事务),也或许会在开发规范里建议程序员们尽可能将大事务拆分为小事务,即便特殊情况不得不跑大事务,至少也要在会话级别将binlog格式改成statement。大事务对数据库的影响,相信各位都或多或少有些体会。我就不一一列举,在这里,我想说的是,既然大家都觉得跑大事务不好,那大事务在MySQL里跑起来到底长啥样呢?下面我们围绕这个话题,针对"在MySQL主从复制环境中跑一个大事务"的场景,剖析一下大事务在MySQL中的"传奇事迹"
1、环境信息
- 数据库版本:MySQL 5.7.27
- 数据库关键配置参数:
- 主从库:双一、long_query_time=1、binlog_rows_query_log_events=ON、binlog_format=row、slow_query_log=ON、innodb_buffer_pool_size=10G、max_binlog_size=512M
- 从库:双TABLE、log_slow_slave_statements=ON、slave_parallel_type=LOGICAL_CLOCK、slave_parallel_workers=16、slave_preserve_commit_order=ON、slave_rows_search_algorithms='INDEX_SCAN,HASH_SCAN'、log_slave_updates=ON
- 主从复制拓扑
- 主库:10.10.30.162
- 从库:10.10.30.163
- 服务器硬件配置(kvm)
- CPU:8 vcpus
- 内存:16G
- 磁盘:100G(LSI 1.6T FLASH卡)
- 其他工具版本
- sysbench:sysbench 1.0.9
- percona-toolkit:percona-toolkit-3.0.13-1.el7.x86_64
2、环境准备
-
主从库都启用所有的等待事件(这里使用了sys schema下的存储过程ps_setup_enable_instrument和ps_setup_enable_consumer进行快捷操作,代替使用UPDATE语句直接修改performance_schema系统库下的setup_instruments和setup_consumers表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
-
主库造数2张1000W的表
1 2 3 4 5 6 7 8 9 10 11 |
|
-
主库造数完成之后,查看从库复制状态,确认从库复制无延迟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
-
主从库各自清理performance_schema统计数据,并切换binlog,避免对后续的操作过程造成干扰
1 2 3 4 5 6 7 8 9 10 11 12 |
|
-
主从库分别执行一个加压辅助脚本,主库并行执行2个UPDATE语句(分别对sbtest1和sbtest2表发起UPDATE操作),从库并行执行2个SELECT语句(分别对sbtest1和sbtest2表发起SELECT操作)(脚本代码详见文末的下载链接)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
-
另起一个ssh终端会话,在从库运行一个每秒查询复制延迟的脚本(脚本代码详见文末的下载链接)
1 2 3 4 5 6 |
|
-
主从库各自先查看一下慢查询日志、解析binlog,并分析慢查询日志(这里的步骤是为了与后续执行完成大事务后的分析结果做对比,没有对比就没有伤害嘛)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
- pt-query_digest 解析结果截图
-
图1(主库)
-
图2(从库)
-
-
关于主从库binlog解析中,exec_time大于0秒的事务比对信息如下,通过这些信息可以看到,在从库回放binlog时,事务的执行时间被拉长了(exec_time这个属性的统计时间,是从每个事务修改的第一行记录开始计时,直到事务提交完成为止,期间,如果事务存在锁等待,则锁等待的时间也会被一并计算在内。但是要注意,在一个事务包含多个SQL语句时,或者中间夹杂着一些操作间隔时间,例如sleep函数等,那么这个值就不准确,但在我们的测试案例中,一个事务只修改一行记录,所以这个时间在这里是准确的,因此在这里可以认为该值为这个事务真正执行的总时间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
-
现在,准备工作已经就绪,在主从库中执行切换binlog,准备入场了
1 |
|
3、执行大事务
-
首先,在主从库中,各自执行一个按照指定的时间间隔循环打印数据库redo log、binlog、ibdata、undo、表数据文件写盘的吞吐统计脚本(脚本代码详见文末的下载链接)
1 2 3 4 5 |
|
- count_mysql_disk_file_io.sh脚本打印截图如下
-
图3(主库)
-
图4(从库)
-
- 从图3和图4的比对结果来看,主从库中都频繁抓取到redo log和binlog有0~30KB范围内的吞吐,说明此时,对于主库中的UPDATE事务产生的数据文件的吞吐量,在主从库中是一致的
-
现在,登录到主从数据库中,各自使用sys.io_global_by_file_by_bytes视图查询当前主从库中相关的数据文件总吞吐统计值,以便后续大事务执行完成之后,再次抓取该统计值进行对比
1 2 3 4 5 6 |
|
- sys.io_global_by_file_by_bytes视图的执行结果截图如下
-
图5(主库)
-
图6(从库)
-
- 从图5和图6的对比数据可以看到,主库的写流量主要在binlog上,而且读流量在binlog上也有几兆;从库的写流量主要在relay log上,读流量也主要在relay log;另外,proc.MYD文件的读流量主要是因为相关的辅助脚本持续调用相关的查询视图产生的。当然,这里的数据可能不准确(例如:从库中relay log在重放完成之后,如果启用了purge_relay_log参数,会被自动清理,在这个视图中也就看不到被清理的relay log文件相关的统计信息了),但不要紧,这里仅仅只是让我们知晓一个流量的量级,等到后续大事务执行完成之后,再次查询该视图中的数据,你将会发现大事务执行前后相关的数据文件在读写流量上存在量级上的差异
-
现在,登录到主库中,执行修改数据量级超过400W行的UPDATE语句(给定id值范围不要与exec_parallel_sql_master.sh脚本中给定的id值发生锁冲突),这里我们以操作sbtest1表为例执行大事务
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
-
在主库大事务未执行完成之前,可在主库反复执行show processlist查看线程状态信息(该步骤不是必须的)
1 2 3 4 5 6 7 8 9 10 |
|
-
查看主库中数据文件的流量吞吐情况(该信息为count_mysql_disk_file_io.sh脚本的打印日志),从下图中可以发现,事务未执行完成之前,除了binlog之外,表空间文件、共享表空间、undo、redo的磁盘流量都有不同程度的大幅增加,说明事务未提交时,这几种文件都会写盘且,binlog不写盘(以往没少听说过“事务提交之后,脏页在后台刷新”这种话吧?嘿,这话听起来挺有迷惑性的。事务提交之后,后台线程的确会做刷脏的事情,不过,这句话可没说事务未提交之前后台线程不刷脏。这样设计显然是合理的,否则大事务产生的脏页总量超过内存时,事务就无法执行了)
-
持续观察主库中的语句执行进度,直到执行完成(这一步可千万不要中断了,否则上述一系列步骤就要重新来过了)
1 2 3 4 5 |
|
-
待到主库大事务执行完成之后,迅速观察从库的复制延迟情况,并查看下show processlist;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
-
顺便看一眼从库的数据文件流量探测脚本的情况(这些信息来自脚本count_mysql_disk_file_io.sh的打印日志),从下图中标记的地方,对比下主库的数据文件流量打印日志,可以发现它们在量级上是差不多的
- 关注check_slave_delay.sh脚本的打印情况,当发现复制延迟降为0时,表示大事务已经在从库回放完成了,通过check_slave_delay.sh脚本打印的信息,我们可以非常直观地看到,从07分26秒开始出现5秒延迟,10分54秒延迟降为0,毛估估的算一下,这个大事务在从库中的执行时间为3分28秒(该事务在主库中的执行时长为3分4秒),如下
-
探测到开始大幅延迟的时间点
-
探测到延迟结束的时间点
-
- 此时,大事务在主从库都已经执行完成,可以将所有辅助脚本停止运行了
-
上述对于主从库数据文件的流量探测截图中(这里指的是count_mysql_disk_file_io.sh脚本打印日志截图)都没有截取到binlog磁盘流量大幅增加的信息,难道这个大事务没写binlog?非也,因为binlog不是持续写的,在事务持久化的两阶段提交中,事务持久化完成之后,写binlog就结束了,因为人为操作的截图,错过打印的时间点没有截取到,我们打开脚本日志找找看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
- 上述代码段可能不便阅读,这里将上述内容截图列出
-
图7(主库)
-
图8(从库)
-
4、分析大事务的影响
-
首先在主从库分别查看慢查询日志文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
- 顺便用pt-query-digest工具解析下主库的慢查询日志,截个图列出
-
图9(主库)
-
图10(从库)
-
-
现在,在主从库分别解析binlog日志查看,并使用pt-query-digest工具分析binlog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
- pt-query_digest 解析结果截图
-
图11(主库)
-
图12(从库)
-
- 从上面两个图的对比结果来看
- 大事务在主库binlog中逃脱了,因为row格式中有效的binlog记录的是数据的逐行变更数据,而非SQL语句文本,同理,在从库中也应该不会记录该大事务的语句
- 从库的binlog分析结果中,语句的总执行时间大幅增加。在上文中更早之前我们通过分析binlog已经知道,从库回放会拉长事务的执行时间,但是图12中从库binlog解析记录的SQL总执行时间为43853s,换算成小时,约12个小时,可我们在这里,整个操作过程不到一个小时,很显然,pt-query-digest工具的分析结果中,是将所有BEGIN语句的时间都加在一起了,又因为在测试环境中的从库启用了多线程复制,每个worker线程独立运行,也就是说,假设有N个worker线程同时在回放事务,而每个worker线程同时将执行时间拉长1S,那么,pt-query-digest工具的统计结果中的总延迟时间就变成了N*1S=NS,以此推理可得,大事务在执行过程中,小事务分配binlog event变慢了,又或者被阻塞了,so...
-
下面,我同样也将exec_time属性不为0的记录过滤出来瞧瞧
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
-
现在,主从库分别查看脚本日志(主库中查看exec_parallel_sql_master.sh脚本的执行日志,从库中查看exec_parallel_sql_slave.sh脚本的执行日志),看看主库是否有发生较长时间的写阻塞,从库是否有发生较长时间的读阻塞(注:以下代码段中为部分日志,完整日志请从文末的下载链接中获取)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
-
现在,登录到主从数据库中,各自使用sys.io_global_by_file_by_bytes视图查询相关数据文件的总吞吐统计数据
1 2 3 4 5 6 |
|
- sys.io_global_by_file_by_bytes视图的执行结果截图
-
图13(主库)
-
图14(从库)
-
- 从上述图13和图14中可以看到,吞吐统计数据都毫无悬念地大幅增加了,不过。。主库中出现了一个临时文件/data/mysqldata1/tmpdir/MLrxkcac,读取和写入的总流量都为1.78G,从库中也出现了一个临时文件/data/mysqldata1/tmpdir/MLirV7PX,读取和写入总流量也都为1.78G,额,还真是巧了,这个临时文件是做啥的呢?再仔细一看图13中主库的/data/mysqldata1/binlog/mysql-bin.000024文件的写入总流量也为1.78G,主从库中出现的临时文件会不会跟写binlog有啥关系呢?咋们来推导一下,大事务未提交时,binlog是不能落盘的,那整个事务的binlog日志量往哪里放呢?内存?那万一内存不够用怎么办?那写磁盘吧?往哪儿写呢?临时文件?会不会这里看到的这个临时文件就是用来暂存未提交大事务的binlog的呢?
-
查看一下磁盘上的binlog文件看看,从侧面证实下推导的结论
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
-
PS:对于ML开头的临时文件,事务未执行提交之前,在ps中无法被捕获到,但可以在操作系统层面使用lsof命令查看,如下(看到某个文件的字节数在不断增加的时候,就表示mysqld进程正在不断地使用临时文件存放一些临时数据,但是,当事务提交时,lsof命令捕获到的临时文件条目的字节数会被清零):
5、优化大事务的影响
-
主库参数优化,添加如下两个优化参数
1 2 3 4 5 6 7 8 9 10 11 12 |
|
- 现在,咋们将上述所有步骤重新跑一下,看看大事务是否会阻塞其他的小事务(过程略)
- 从测试结果来看,很遗憾,没有太大的优化效果
- 对于主库来说,小事务仍然会在大事务提交binlog阶段发生短暂阻塞(如果磁盘吞吐不太好,那这个问题可大可小了)
- 对于从库来说,复制延迟仍然存在,但通过pt-query-digest工具解析binlog结果来看,总执行时间减小了一些,但仍然与未优化前处于同一个量级
- 那是不是说这两个优化参数没啥用呢?不是的,测试结果只能证明,这两个参数的优化效果对大事务场景没太大用处,但对于其他一些特定场景就可能有用。那在什么场景下有用呢?这个话题,咱们日后再聊
- 现在回过头来继续聊大事务究竟怎么搞呢?当你没有办法去优化某件某个大事务的,最好的办法就是...消灭它,如何消灭大事务呢?回到文章开头那句话:“在开发规范里明令禁止大事务(单个事务操作的数据量不建议超过10000行),尽可能将大事务拆分为小事务,即便特殊情况不得不跑大事务,至少也要在会话级别将binlog格式改成statement。
- 最后
- 特别强调一下,对于MySQL InnoDB存储引擎来说,建议设置主键或唯一索引,再不济也要有二级索引,否则,那感觉一定很酸爽,你可以准备两张没有任何索引的表,用上述过程操作一遍试试看!
- 另外,喜欢用级联复制的同学们注意了,除非必须,否则不建议使用级联复制,以现在的服务器硬件配置来说,使用级联复制带来的好处还不如带来的问题多
6、附录
- 上文中使用到的相关脚本和相关日志文件下载链接如下
- 脚本: https://pan.baidu.com/s/11t-WbJns42jO8_s7YW5pDQ
- 日志: https://pan.baidu.com/s/1SvVhvt59peB1gHuhHqAA0g
- PS:
- 关于exec_time属性的详细解释,详情可参考 高鹏(重庆八怪)的《深入理解MySQL主从原理》专栏中的第八讲
-
关于大事务binlog未落盘之前,是否会写临时文件的详细解释,详情可参考 高鹏(重庆八怪)的《深入理解MySQL主从原理》专栏中的第十三、第十四讲。最后,附一张高清图(该图为第十四讲文末的高清图),留意左侧绿色方框内的内容
| 作者简介
罗小波·沃趣科技高级数据库技术专家
IT从业多年,主要负责MySQL 产品的数据库支撑与售后二线支撑。曾参与版本发布系统、轻量级监控系统、运维管理平台、数据库管理平台的设计与编写,熟悉MySQL体系结构,Innodb存储引擎,喜好专研开源技术,多次在公开场合做过线下线上数据库专题分享,发表过多篇数据库相关的研究文章。
这篇关于揭秘MySQL 主从环境中大事务的传奇事迹的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!