12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
ADADADADAD
mysql数据库 时间:2024-12-03 12:13:30
作者:文/会员上传
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
1、故障现象某客户反馈,在利用binlog日志的解析内容做基于时间点的恢复时,出现hang死(超过12小时那种),恢复过程无法继续,而且100%重现基于时间点恢复的实现方式为:使用mysqlbinlog
以下为本文的正文内容,内容仅供参考!本站为公益性网站,复制本文以及下载DOC文档全部免费。
[root@localhost~]#psaux|grepmysqlbinlogroot278221.30.0245683204pts/2R+15:110:04mysqlbinlog--stop-datetime='2019-07-1416:30:00'mysql-bin.000014mysql-bin.000015mysql-bin.000016mysql-bin.000017mysql-bin.000018mysql-bin.000019......
然后,查看了当前正在运行的线程状态信息,发现一个sleep长达157269S的线程,这是什么鬼。。大事务未提交吗?窃喜!
admin@localhost:test02:18:27>showprocesslist;+----+-------+-----------+------+---------+--------+----------+------------------+|Id|User|Host|db|Command|Time|State|Info|+----+-------+-----------+------+---------+--------+----------+------------------+|14|admin|localhost|test|Query|0|starting|showprocesslist||15|admin|localhost|test|Sleep|157269||NULL|+----+-------+-----------+------+---------+--------+----------+------------------+2rowsinset(0.00sec)
接着,我们查看了事务和锁信息
#先查看事务信息admin@localhost:test03:02:36>select*frominformation_schema.innodb_trx\GEmptyset(0.00sec)#纳尼!!,居然不存在事务正在运行#然后查看锁信息admin@localhost:test03:30:25>select*frominformation_schema.innodb_locks;Emptyset,1warning(0.00sec)#WTF!!居然也没有锁信息至此,排查视乎陷入了僵局。既然不存在大事务,也没有锁信息,用于恢复数据的客户端连接也处于Sleep状态,那就表示此时数据库未做任何事情(该数据库是一个用于数据恢复的临时库,并没有任何其他访问业务,也并没有监控、心跳等,只有2个连接,一个是我们登录上去排查问题的连接,另外一个就是使用mysqlbinlog命令恢复数据的客户端连接),那么,为什么会hang?百思不得其解!
再接着,我们查看了一下系统负载,内存、网络、磁盘都几乎无负载,CPU也没啥负载,但却有一个奇怪的现象,有一个CPU核心的利用率为100%,如下
top-15:40:50up117days,8:09,5users,loadaverage:1.97,1.36,1.15Tasks:496total,2running,494sleeping,0stopped,0zombie%Cpu0:0.0us,0.0sy,0.0ni,100.0id,0.0wa,0.0hi,0.0si,0.0st%Cpu1:0.0us,0.0sy,0.0ni,100.0id,0.0wa,0.0hi,0.0si,0.0st#利用率为100%的CPU核心在这里%Cpu2:34.4us,65.6sy,0.0ni,0.0id,0.0wa,0.0hi,0.0si,0.0st%Cpu3:0.3us,0.3sy,0.0ni,99.3id,0.0wa,0.0hi,0.0si,0.0st%Cpu4:0.0us,0.0sy,0.0ni,100.0id,0.0wa,0.0hi,0.0si,0.0st%Cpu5:0.0us,0.0sy,0.0ni,100.0id,0.0wa,0.0hi,0.0si,0.0st%Cpu6:0.0us,0.0sy,0.0ni,100.0id,0.0wa,0.0hi,0.0si,0.0st%Cpu7:0.0us,0.0sy,0.0ni,100.0id,0.0wa,0.0hi,0.0si,0.0st......
最后,在恢复库中,我们查看了一下GTID信息,发现GTID号不连续,少了一个事务的GTID(3759)
admin@localhost:(none)03:52:08>showmasterstatus;+------------------+----------+--------------+------------------+---------------------------------------------+|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|+------------------+----------+--------------+------------------+---------------------------------------------+|mysql-bin.000013|500|||5de97c46-a496-11e9-824b-0025905b06da:1-3758,3760-3767|+------------------+----------+--------------+------------------+---------------------------------------------+1rowinset(0.00sec)
通过查看从库中的GTID(也就是恢复库的数据源对应的库),并没有少GTID号为3759的事务,为什么莫名在恢复库中会少,而不是连续的?带着好奇,我们使用该GTID号解析了从库中的所有被指定用于恢复的binlog file列表(这里指的就是mysql-bin.000014 mysql-bin.000015 mysql-bin.000016 mysql-bin.000017 mysql-bin.000018 mysql-bin.000019),从这些binlog中提取了GTID号为3759的事务的binlog日志内容
[root@localhostbinlog]#mysqlbinlogmysql-bin.000014mysql-bin.000015mysql-bin.000016mysql-bin.000017mysql-bin.000018mysql-bin.000019--include-gtids='5de97c46-a496-11e9-824b-0025905b06da:3759'>b.sql[root@localhostbinlog]#ls-lhb.sql-rw-r--r--1rootroot996MJul1415:59b.sql#MyGod,GTID号为3759的一个事务binlog日志量快接近一个G了#使用mysqlbinlog命令加-vv选项重新解析(使用-vv选项解析,会导致解析结果的大小翻3倍左右),查看事务的原始语句是怎样的(binlog_rows_query_log_events=ON才会记录事务的原始语句文本)[root@localhostbinlog]#mysqlbinlog-vvmysql-bin.000014mysql-bin.000015mysql-bin.000016mysql-bin.000017mysql-bin.000018mysql-bin.000019--include-gtids='5de97c46-a496-11e9-824b-0025905b06da:3759'>c.sql#vim打开c.sql文件查看[root@localhostbinlog]#ls-lhtotal12G-rw-r--r--1rootroot994MJul1416:38b.sql-rw-r--r--1rootroot2.7GJul1416:40c.sql......[root@localhostbinlog]#vimc.sql......#at336#19071416:29:10serverid3306102end_log_pos422CRC320x3b2c9b07Rows_query#发现一个400W行的大事务(经查看sbtest.sbtest1表中的数据量,确认该语句会往test.sbtest1表中插入400W行)#insertintosbtest1select*fromsbtest.sbtest1limit4000000......BINLOG'1ucqXRN2cjIAOQAAAN8BAAAAAHUAAAAAAAEABHRlc3QAB3NidGVzdDEABAMD/v4E7mj+tABkzbY11ucqXR52cjIADSAAAOwhAAAAAHUAAAAAAAAAAgAE//ABAAAAlyomAHcAMzMzNjgzMDQzMTQtMjYxMDIwNDA4ODktMTU4NzIxODA4NzYtMTU0NDIxOTgxNjQtNDYzOTM1NDIxMzEtMTQwODg3NzUzNTQtNzY4MDU0ODgyMTEtNzg0ODM1NTU5NjEtNjMyMDM5NjA0ODEtNDcxNjQ5MDg4MjY7NTA3NzUyOTM0MzctMjE4MzMzNTAzNzYtODc2MTE2NjU0NTYtNjI4NTU3NjAyOTItNDQ3Mjc0MzA4MTjwAwAAAA0zHQB3ADM4NjUyNzU5NDQwLTg4NjY4MjU5MDE3LTk0MDk4ODI4Nzc1LTYxMzMzNjEwMjg0LTYyODc3NDgxMTY3LTY1NzM3Mjg3NTExLTA4MDYwNzA5NTU1LTIzMTUyNjI5NTcxLTE2MDMzMDM2NDE5LTYyMjA0MDgxMzc0OzczOTE3OTQ0NjMzLTc0ODMwMjgwMjE3LTAxMTYzODkwMzkzLTU3NTEzNDA4MDY1LTMwNjgzOTA1MTQ08AUAAAAlNCYAdwAwMjU2MTIyODQ3MC05OTAwMzk1ODMyOC0zMDQ1OTgyMzQwNC0zMTY1MTgyNzE4OC02MDMxODU1MDA5OS03Njk5Njk5MTY3Mi02MTI1Nzg5NTU5MC03NDA3OTQzMDg4MC01NzMyMDA4MzY4NC0zNjAzMDY2NDE4NjszOTA4Nzk4NjM5NC02MjA0NjQ4MDk0Ny01NjQ0NTE4NzA3My0yODQxNDg5MzQyNC03OTYxOTMzMTg1N/AHAAAAn1MmAHcAODgxMzg4MjkxMjEtMDkxNTk1NDI1OTctNzc4ODUwODczMzMtMjA1MzE3NDM2MjktODE3NTQ0NDc2MjgtMjczNDMyMzQ2ODEt......#这就是这个大事务对应的BINLOG编码,整个b.sql文件的994MB的内容主要就是这些BINLOG编码分析到这里,总算是发现了导致MySQL 客户端hang死的直接原因(注意,是客户端hang死,不是服务端),即,MySQL客户端在应用一个400W行大事务的binlog时,导致MySQL客户端出现hang死。但是,问题又来了,一个400W行的大事务,为什么会导致客户端hang死长达12个小时?而且在此期间,数据库和操作系统的负载并不高,数据库的各项参数设定也并没有达到上限,而且,查看了数据库的错误日志和操作系统日志(/var/log/messages),也并没有发现任何有用信息。so..,接下来我们就需要进一步分析为何一个大事务会导致客户端hang死3、找到罪魁祸首上文中我们提到过,该故障现象可以100%重现,这为我们进一步分析该问题提供了便利。在开始进一步分析之前,我们先列出在不同磁盘设备的服务器环境中插入400W行大事务的时间参考数据(经验值,不精确)
使用strace工具查看登录MySQL并加载binlog解析文本过程中的一些输出信息(这里直接使用上文中提到的b.sql)
[root@localhost~]#stracemysql2>strace.txtWelcometotheMySQLmonitor.Commandsendwith;or\g.YourMySQLconnectionidis22Serverversion:5.7.26-logMySQLCommunityServer(GPL)Copyright(c)2000,2019,Oracleand/oritsaffiliates.Allrightsreserved.OracleisaregisteredtrademarkofOracleCorporationand/oritsaffiliates.Othernamesmaybetrademarksoftheirrespectiveowners.Type'help;'or'\h'forhelp.Type'\c'toclearthecurrentinputstatement.admin@localhost:(none)10:49:36>usetestDatabasechangedadmin@localhost:test10:49:38>source/data/mysqldata1/binlog/b.sql;......Charsetchanged......Warning(Code1681):Updating'collation_database'isdeprecated.Itwillbemaderead-onlyinafuturerelease.QueryOK,0rowsaffected(0.00sec)#卡在这里了,此时你可以先去干点别的,20分钟之后再回来看看导入成功了没有,不过,不管你等不等,这个客户端要留着后续排查用,不能断开了
现在,另起一个终端会话,查看strace.txt文件中的内容
[root@localhost~]#tailfstrace.txt......munmap(0x7f8d6607d000,58281984)=0read(4,"wMjE4ODQzLTE5MTYzMTE4NDk4LTQwNTA"...,4096)=4096mmap(NULL,58290176,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)=0x7f8d5f150000......#发现不断地打印类似下面这一段信息,貌似卡在内存分配这里了munmap(0x7f8d628e1000,58298368)=0read(4,"UxNzUtODUyMjQ1MTkxMDEtMTM4MTk3OD"...,4096)=4096mmap(NULL,58306560,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)=0x7f8d66077000......很无奈,我们使用strace工具在这里并没有发现实质性有用信息,需要使用其他的工具进行排查,现在,你可以关闭用于导数测试的客户端连接了(注意,使用了strace命令之后,客户端连接可以立即终止,如果没有使用strace命令,数据库的终端会话hang死了,无法终止数据库的导数操作,别说我没有等它回滚,我等了好几个小时...)
注意:如果你在跟随我们的复现过程玩耍,建议你查看一下show processlist的信息,你可能会发现之前导入b.sql文件的客户端连接在数据库中并没有被断开,如果发现此情况,则需要在数据库中手工执行kill操作
admin@localhost:(none)11:14:54>showprocesslist;+----+-------+-----------+------+---------+-------+----------------------------------+----------------------------------------------------------------------+|Id|User|Host|db|Command|Time|State|Info|+----+-------+-----------+------+---------+-------+----------------------------------+----------------------------------------------------------------------+|22|admin|localhost|test|Sleep|67545||NULL|......|32|admin|localhost|NULL|Query|0|starting|showprocesslist|+----+-------+-----------+------+---------+-------+----------------------------------+----------------------------------------------------------------------+admin@localhost:(none)11:14:55>kill22;QueryOK,0rowsaffected(0.00sec)3.2. pstack 工具排查
登录到MySQL并执行导入b.sql文件操作(不要用strace)
[root@localhost~]#mysql-uadmin-ppasswordmysql:[Warning]Usingapasswordonthecommandlineinterfacecanbeinsecure.WelcometotheMySQLmonitor.Commandsendwith;or\g.YourMySQLconnectionidis31Serverversion:5.7.26-logMySQLCommunityServer(GPL)Copyright(c)2000,2019,Oracleand/oritsaffiliates.Allrightsreserved.OracleisaregisteredtrademarkofOracleCorporationand/oritsaffiliates.Othernamesmaybetrademarksoftheirrespectiveowners.Type'help;'or'\h'forhelp.Type'\c'toclearthecurrentinputstatement.admin@localhost:(none)11:08:58>usetestDatabasechangedadmin@localhost:test11:09:00>source/data/mysqldata1/binlog/b.sql;......
使用pstack工具查看MySQL客户端连接的进程堆栈信息
#查看MySQL客户端连接进程号[root@localhost~]#psaux|grep'mysql-uadmin'|grep-vgreproot461481.20.017508041284pts/21R+11:180:20mysql-uadmin-pxxxxx#使用pstack工具查看[root@localhost~]#pstack4614|tee-apstack.txt#发现它卡在__memmove_ssse3_back()内存拷贝的阶段了。此时你可以看到系统的某一个CPU的idle是0%(与上文中发现的CPU负载诡异现象吻合),这个CPU被耗尽的CPU做的事情就是,持续申请内存、拷贝数据、释放内存,自然把一个CPU给用完了#00x00007f1009f5c315in__memmove_ssse3_back()from/lib64/libc.so.6#10x0000000000422adfinmy_realloc(key=<optimizedout>,ptr=0x7f0ffde45030,size=30197464,flags=<optimizedout>)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/mysys/my_malloc.c:100#20x000000000041a2bainString::mem_realloc(this=0xa45460<glob_buffer>,alloc_length=30197460,force_on_heap=<optimizedout>)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/sql-common/sql_string.cc:121#30x0000000000417922inadd_line(truncated=false,ml_comment=<optimizedout>,in_string=<optimizedout>,line_length=76,line=0x16b4309"NDQ4NDA0LTE5MjQ2NjgzMDgxLTY2MTA0Mjk0ODQ2LTYzNzk3MjcwMjU0LTQ3NjA2Nzk0MTY0LTEx\nODQwNjExOTY5OzY5NTQ1Mjc5MDA2LTM5NTgwMjUzMDEzLTgzMjQxNjU0MzQ1LTA4MDkxMDEzODk1\nLTk5NzMxMDYyMzU58B2TAwABKiYAdwAwNTEzMjEzNDUzNC"...,buffer=...)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/client/mysql.cc:2795#4read_and_execute(interactive=false)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/client/mysql.cc:2364#50x00000000004181efincom_source(buffer=<optimizedout>,line=<optimizedout>)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/client/mysql.cc:4709#60x0000000000417099inadd_line(truncated=false,ml_comment=<optimizedout>,in_string=<optimizedout>,line_length=37,line=0x16b2a10"source/data/mysqldata1/binlog/b.sql;",buffer=...)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/client/mysql.cc:2664#7read_and_execute(interactive=true)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/client/mysql.cc:2364#80x0000000000418c98inmain(argc=15,argv=0x160c348)at/export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/client/mysql.cc:1447开心,使用pstack工具发现了一个较为有用的信息,从这里我们知道了MySQL客户端在导入b.sql时,卡在了使用__memmove_ssse3_back ()函数做内存拷贝的阶段了那么问题又来了,为什么会卡在这里?结合上述获取到的信息,我们知道,MySQL客户端只是与服务端建立了一个连接,后续服务端没有收到任何信息(也就是整个客户端hang死期间,服务端没有做任何事情),so..,接下来我们必须要去翻阅源码来寻找答案了:为什么MySQL客户端会卡在 __memmove_ssse3_back ()函数内存拷贝阶段?3.3. 翻阅客户端命令mysql的源码排查
由于是在MySQL 5.7.26版本中发生的故障,所以我们首先查看了MySQL 5.7.26版本的客户端源码,MySQL客户端在读取一个文件时(或者说判断什么时候可以将一个操作发送给服务端时),碰到';'分号会直接将前面读取到的内容作为一整个数据包丢给服务端,对上文中提到的b.sql文件中的BINLOG编码,整个BINLOG编码只有一个分号,所以会等到整个BINLOG编码读取完成之后才会发送给服务端,在读取这个超大BINLOG编码的过程中,MySQL客户端会一行一行的读,并记录到global_buffer中。当global_buffer申请的空间不足时,就需要扩展内存空间,,扩展内存空间的代码如下:
//client/mysql.cc文件if(buffer.length()+length>=buffer.alloced_length())buffer.mem_realloc(buffer.length()+length+IO_SIZE);#每次除了扩展不够的内存以外(原有的内存长度+新读取的内容长度),额外只多扩展了4k的空间(也就是IO_SIZE,IO_SIZE变量的定义大小详见代码段后续的"include/my_global.h文件")//mysys/my_malloc.c文件new_ptr=my_malloc(key,size,flags);#扩展内存时先申请一段新的更大空间的内存(新申请的一段内存的大小为client/mysql.cc文件中描述的扩展之后的内存大小)if(likely(new_ptr!=NULL)){#ifndefDBUG_OFFmy_memory_header*new_mh=USER_TO_HEADER(new_ptr);#endifDBUG_ASSERT((new_mh->m_key==key)||(new_mh->m_key==PSI_NOT_INSTRUMENTED));DBUG_ASSERT(new_mh->m_magic==MAGIC);DBUG_ASSERT(new_mh->m_size==size);min_size=(old_size<size)?old_size:size;memcpy(new_ptr,ptr,min_size);#然后把数据拷贝过去my_free(ptr);returnnew_ptr;}//include/my_global.h文件#defineOS_FILE_LIMITUINT_MAX/*Iobuffersize;Mustbeapowerof2andamultipleof512.Maybesmallerwhatthediskpagesize.Thisinfluencesthespeedoftheisambtreelibrary.egtobigtoslow.*/#defineIO_SIZE4096#IO_SIZE变量的大小定义为4K通过翻阅源码中的相关代码,再结合b.sql中400W行大事务产生的BINLOG编码,我们可以得出如下结论:MySQL客户端读取b.sql文件中的BINLOG编码时,会把整个BINLOG编码当做一整个数据包对待,也就是说客户端先要将其完整读取并存放在内存中,然后再将其作为一整个数据包发送给服务端(包的大小由客户端和服务端的max_allowed_packet参数共同控制,以最小的为准),客户端在读取BINLOG编码的过程中,每读一行都会先判断原先分配的内存是否足够,如果内存不足就需要扩展一下,申请新的一段内存,并且把原来的内存中的数据拷贝过去(新申请的内存),自然就非常慢,看上去就好像客户端hang死一样,而客户端读取BINLOG编码的整个过程中,服务端都没有收到任何的操作,所以,从服务端的show processlist看到线程状态一直是Sleep状态,也没有事务,也没有锁信息。也就是说,这里可以排除掉mysqlbinlog命令解析binlog的嫌疑4、干掉罪魁祸首虽然,我们发现了问题的根本原因,是因为MySQL客户端读取大事务的BINLOG编码时分配内存的效率太低导致的,但是,怎么解决它呢?改代码?怎么改?改了出其他问题咋办?IO_SIZE变量是一个比较底层的变量,不同用途的buffer分配都会使用该变量作为一个基准参考,所以不能直接修改此变量的大小。因此,我们并没有急于去修改客户端代码,而是先尝试使用MySQL 8.0.16、MariaDB 10.1.40/10.2.25/10.3.16/10.4.6、Percona Server 5.7.26进行测试,发现如下现象
接下来,我们解决第二个问题,比对MySQL 5.7.26和MySQL 8.0.16版本中,关于客户端缓存读取文件数据这块的内存分配代码有什么不同?
#以下只列出MySQL8.0.16版本中的代码//client/mysql.cc文件if(buffer.length()+length>=buffer.alloced_length())buffer.mem_realloc(buffer.length()+length+batch_io_size);##8.0中每次扩内存时,额外分配的内存大小变更为了batch_io_size,在5.7中,batch_io_size位置是IO_SIZE//client/my_readline.h文件staticconstunsignedlongintbatch_io_size=16*1024*1024;##这里定义batch_io_size为16M,从4K变为16M,这就使得客户端在缓存读取的数据时,发现内存不够之后的内存分配效率提高几个数量级so...看起来,需要怎么修改MySQL的客户端代码已经很清晰了,只需要新定义一个batch_io_size变量,然后将'buffer.mem_realloc(buffer.length()+length+IO_SIZE)' 修改为'buffer.mem_realloc(buffer.length() + length + batch_io_size)'即可说干就干,我们按照此结论对MySQL的客户端mysql的源码进行了修改,重新编译,然后,将服务端和客户端的max_allowed_packet参数都设置为1G,重新导入400W行的大事务产生的row格式binlog(即,使用insert into sbtest1 select * from sbtest.sbtest1 limit 4000000 语句产生的row格式binlog解析文本进行测试),3分钟内完成了导入,至此,问题解决!
最后,多说一句,MariaDB虽然也同样解决了这个问题,但是解决方法完全不同(粗略看了一下代码,没细看),通过比对解析的BINLOG编码,发现MariaDB的BINLOG编码是存在一个个的空缺,所以猜测可能是采用了"mysqlbinlog的解析格式变更"配合"mysql客户端的解析逻辑变更"来解决该问题的(MariaDB在执行导入binlog解析内容时,show processlist查看线程状态也能发现导入数据的线程一直在干活,并不是一直处于Sleep状态),类似如下
MariaDB
* BINLOG编码格式
* show processlist状态
MySQL
* BINLOG编码格式
* show processlist状态
| 作者简介
罗小波·沃趣科技高级数据库技术专家
IT从业多年,主要负责MySQL 产品的数据库支撑与售后二线支撑。曾参与版本发布系统、轻量级监控系统、运维管理平台、数据库管理平台的设计与编写,熟悉MySQL体系结构,Innodb存储引擎,喜好专研开源技术,多次在公开场合做过线下线上数据库专题分享,发表过多篇数据库相关的研究文章。
11-20
11-19
11-20
11-20
11-20
11-19
11-20
11-20
11-19
11-20
11-19
11-19
11-19
11-19
11-19
11-19