当前位置: 首页 > 科技观察

大量MySQL表导致服务变慢

时间:2023-03-14 00:03:52 科技观察

背景有一个业务需要分库1000个,每个库80个表,一共80000*2个文件。文件使用率相当高,大约60000*2。该业务采用的高可用架构是MMM。由于集群机器在硬件检查时发现问题,不得不更换。于是想到了一个比较简单,影响较小的方法来解决,就是找了另外两台机器迁移到那里。同时要求四台机器属于同一网段,VIP(虚拟IP地址)可以在机器之间漂移,这样可以在不修改IP地址的情况下进行业务迁移,相当于两台master-从切换过程。切换方案如图16.1所示。从图16.1可以看出,切换过程非常简单,如下三步。先将原来的写节点(db1)和一个新节点(db3)切换成一个集群,即将db2上的VIP(读流量)切换到db3上,然后db1和db3组成一个新的集群。然后将db1和db3的角色互换,让db3成为写节点,db1成为读节点。最后将db1的读节点上的VIP(读流量)切换到db4。此时新的集群是db3和db4,db3是写节点,已经完成切换。这种变化是在晚上进行的。做完之后又观察了一段时间,发现没什么问题(因为压力小),就觉得大功告成了,就去睡觉了。第二天上班,业务反映迁移后,数据库比以前慢了10倍(10倍!感觉不可思议)。询问了一下,说没有变化,迁移后就是这个样子。同时,经过观察,只有写库的读操作变慢了,而读库的读操作并没有变慢。最后业务实在受不了,要求换回去。幸运的是,db1仍在从db3复制,并且执行了回滚操作以挂起对db1的写入和对db3的读取。奇迹般地,问题解决了!嗯,先这样吧,出路不能回头,总得搬出去,所以先挂在新旧机器上,查明原因再切回来(这样就少了一个步)。以上是背景。问题分析环境写的时候对比db1,db1写不慢,读不慢,db3也不慢。db3是新硬件,db1是旧的、有问题的硬件。db3转写后,在慢查询文件中明显看到很多慢查询(使用同一条语句查询,原来是50ms,现在是500ms),与监控一致。db1和db3的配置文件不同,如图16.2所示(左边是db3的,右边是db1的)。其他方面,环境完全一样,业务没有变化,重现慢的现象,只需要切换即可。图16.3是切换过程中的监控画面。高的是流量切换到db3的情况,低的是切换到db1的情况。效果很明显,慢到马上就能看到。太奇妙了!原因分析通过对比我们知道db1是正常的(之前在本机上跑了很久,没有问题),但是db3不正常。这个业务目前读多写少,目前的现象是读慢。因为写的少,没发现慢,就不考虑了。然后是硬件上的差异。两者都是PCI-e卡。旧的更容易损坏。根据经验,新的会更好。这是一个疑点。但实际上,针对这个问题,找了新卡的技术人员分析,将写入切换到db3后,发现IO很小,能看到的监控指标都很正常。(他们也很疑惑。)除此之外,唯一的区别是两者的配置,但是从图16.3可以看出,没有一个参数可以影响数据库的响应时间是原来的10倍。但以上只是分析。硬件测试后没发现问题(也不能说不是硬件问题,一直挂在那里)。只剩下配置了,就从这里开始吧,希望能成功!然后,在夜深人静的时候再找一个晚上...破案首先要做的就是把db3的读流量切换到db1,然后配置完全替换掉db1的配置,重启数据库,然后再去在线的。此时db1为写节点,db3为读节点,最神奇的时刻来了。切换后,经过观察,没有问题。如果问题已经解决,说明问题仍然是上面列出的配置差异引起的。解决方案解决后,接下来的工作就是重复开始的工作,让db1下线,让db4上线。此时,之前的迁移工作已经完成,在线服务没有问题。但是……开发者,能不能给我半个小时看看是哪个参数引起的?我得到的回答是:“快点,就这一次,给你20分钟。”找到最有可能的参数出来,比如字符集(其实就是上面的每一个,我们觉得不会有太大的影响),考虑到字符集是一个不能动态修改的参数,所以改成这个第一的。重启,然后动态修改一项一项,业务重启重连等等,但是什么都没有发现。这些修改的参数包括:sql_mode、join_buffer_size、max_heap_size、sort_buffer_size,都没有任何作用。结果约定只有这一次,就这样吧,任务圆满完成,解题失败。然而,这个问题“只在手边,却在心头”,总有一件事情让人揪心,那就约吧。跟开发商商量后,我们想解决这个问题,知道原因,防止其他业务出现同样的问题。好吧,我再给你一次机会(很难得)。然后,在夜深人静的时候,再找一个晚上……这次的月亮,好像比上次圆了一些。这是美好一天的征兆吗?手术前,我做了一个简单的计划。以下是当时的一个计划步骤。^*黑体字是专门测试过的,没有效果。步骤如下。为了首先重现问题,您应该先用新的配置进行测试,以确定问题是否仍然存在。因为主要考虑是sql_mode引起的问题,所以第二次只把这个参数改成旧的配置,这样可以测试是其他配置组合有问题,还是没有问题,以及断定是sqlmode的问题。如果你还没有找到上面问题的原因,那么就是sql_mode以外的参数组合有问题(如果不是,见鬼去吧)。此时通过二分法测试,首先测试innodb_flush_log_at_trx_commit、innodb_open_files、sort_buffer_size这三个参数。如果发现上一步有问题,那就分成两步;如果没发现问题,再分成sync_binlog、join_buffer_size、tmp_table_size。能到这里,也是醉了。待会儿再聊。按照步骤开始逐步进行。先用有问题的配置,再测试一下,发现还是一样,还是有问题(幸好问题依然存在)。将除sql_mode之外的所有参数都改成新的,其他使用旧的配置,测试发现没有问题。搞定,没问题。搞定了,还是没问题。我喝醉了。此时,当事人已是一头雾水。难不成是两者的某种结合才会出现这样的问题?如果是这样,那情况就太多了。突然间,有了新的思路。在问题的基础上,将所有经测试对与db1相同的参数没有影响的动态修改参数全部改掉。这应该是可以影响性能的最少参数组合。此时分别在db1和db3实例上执行showvariables,导出所有变量,对比一下,发现参数有几处不同(左边是旧的db1,右边是新的db3),如图16.4所示。至此,我们做了最后的挣扎,只剩下这6个了,看来还是没有什么影响。有些已经试过了,所以让我们再试一次。二分查找,从以下三个参数开始,offline,restart,online……发现问题竟然奇迹般存在。这个时候只剩下三个参数了,其中一个是sync_binlog,有问题的是0,肯定不会影响的。我们只能找到剩下的两个。可以看到倒数第二个是performance_schema的一个参数。配置文件中没有设置,是默认的,可以忽略。所以,把问题定位到open_files_limit。此时,做最后一次,唯一不同的就是open_files_limit。结果还是有问题,说明是这个参数。回头想想,其实我最开始看区别的时候,这两个参数是在配置文件里面的。看到13万和15万相差不大就直接忽略了。好了,问题解决了,原因也找到了,天亮了,我们回家吧。发现是open_files_limit的原因。那么,为什么一个参数的差异如此之小,却会影响性能10倍呢?检查源代码!通过sysbench,创建60000张表,每张表10000行,在只读模式下,发现设置为130000时,QPS可以达到20000,设置为150000时,QPS只有4000左右。问题复现了,就简单多了。此外,还有一个额外的发现。如果设置为150000,重启数据库很慢,需要1分钟左右,设置为130000后,只需要10秒左右。查看慢进程中mysqld的线程。其中,在启动过程中,有一个线程基本长期处于同一个栈中。使用pstackmysqldpid查看,如图16.5所示。还有一个发现是当open_files_limit设置成一样的时候,performance_schema_max_file_instances参数也是一样的,这个参数没有设置。然后通过源码发现这个参数是通过open_files_limit的值设置的。如果open_files_limit的值设置的比较大(这样可以忽略其他影响条件,比如max_connection等),performance_schema_max_file_instances的值直接从open_files_limit/0.65获取(源码对应函数apply_load_factor)。这样,我们就知道130000/0.65正好是200000,150000/0.65正好是230770,与图16.4一致。另外,通过测试发现,如果将performance_schema_max_file_instances设置为不同的值,将open_files_limit设置为相同的值,性能还是有区别的。因此可以确定与open_files_limit参数无关,而是performance_schema_max_file_instances使用了默认值,其值来自于open_files_limit/0.65,间接影响了performance_schema_max_file_instances的值,顿时有种“打架”的感觉牛过山”。另一个发现是,如果将performance_schema_max_file_instances设置为200000、210000、220000、230000、240000、300000等,性能差不多,但是设置为230770是有问题的。仔细研究后发现performance_schema_max_file_instances最终影响的是performance_schema数据库中的file_instances表。这个表中的数据是通过一个HASH表缓存的,这个参数决定了HASH表的大小。后面做了一个很无聊的测试,就是performance_schema_max_file_instances的值和QPS的比较,如图16.6所示。至此,一切豁然开朗。结合上面的stack很慢,以及performance_schema_max_file_instances设置不同值的现象,可以确定问题最终是HASH算法的问题。当HASH桶的大小是一个比较好的值时,算法很快,但是如果是一个比较零散的值,算法就很慢,会导致响应时间是原来的10倍。总结一下这个问题,真的很奇怪。没想到竟然是一个差异这么小的变量(以至于一开始就忽略了)。这个问题比较少见,只有在表多的时候才会比较明显(因为表少的时候算法比较稳定)。这个问题,经查明,其实是算法中的一个BUG。一个简单的规避这个问题的方法是将performance_schema_max_file_instances的值设置为0,或者将performance_schema_max_file_instances设置为更好的值,或者将open_files_limit设置为0.65的整数倍,这样就不会有问题了。虽然这个问题的影响比较小,但是我们一定要有探索问题的精神,一定要清楚。这个问题到此结束。