关于mysql:案例回顾春节一次较波折的MySQL调优

2次阅读

共计 2859 个字符,预计需要花费 8 分钟才能阅读完成。

春节长假某日,阳光明媚,春暖花开,恰逢冬奥会揭幕,想着肯定是一个黄道吉日,必能逆风逆水。没想到却遇到一个有点小挫折的客户报障。

故障起因

故障起因是客户前一天从自建 MySQL 迁徙到云上 RDS,在执行某个并发较高的业务时呈现了大量锁期待,客户过后降级了实例到最高规格,但故障仍旧。客户反馈降级后的实例规格比自建实例高了一倍,自建实例上从未产生过相似状况。后客户依据过后的业务故障模拟了现场,次要是并发执行如下存储过程的时候性能很差:

初步诊断

从存储过程的逻辑看,比较简单,次要波及两个 SQL,一个从表 t(暗藏了实在表名)中 meeting_id 依据传入参数值查问,具体的入参由字符型变量 p_meeting_id 带入;另外一个依据 meeting_id 和刚查出的 phone_id 去更新 t 中的 phone_id 为 phone_id+3。表 t 数据量约 40w 左右。

第一感觉这是个简略问题,预计两个 SQL 的 meeting_id 索引没有失效,查问表上索引后果然发现 meeting_id 和 phone_id 上没有索引,倡议客户在两个字段上别离创立了索引,且 meeting_id 为主键。此时用户执行模仿的并发脚本反馈速度有了显著晋升,200 个并发最高执行工夫 40s 左右,但模仿 500 个并发的时候,超过了 8 分钟还没有执行完。用户反馈在自建 MySQL 上并发 500 执行都是秒级实现。此时在控制台看,这个存储过程在慢查问日志中批量呈现,且扫描行数微小,客户端曾经齐全 hang 住:

进一步优化

尽管优化有了初步的成果,但间隔客户自建环境性能形容还差距很大,因为并发高,从监控看测试期间 CPU 到了 100%,狐疑参数 innodb_thread_concurrency 的设置可能不当。此参数的作用是管制 InnoDB 的并发线程下限。也就是说,一旦并发线程数达到这个值,InnoDB 在接管到新申请的时候,就会进入期待状态,直到有线程退出。RDS 默认值为 0,也就是没有限度下限,在高并发的场景下可能会产生较多的上下文切换,导致 CPU 升高。和客户征询了一下,他们自建环境的值设置为 32,倡议他们将 RDS 的值也改为 32 再看看成果。客户很快反馈,批改后确实有成果,500 个并发在 3 分钟内实现,没有再产生 hang 住不动的状况,性能有了进一步的晋升。但参数 innodb_thread_concurrency 进一步调整成果不显著。

加 trace 诊断

客户看到性能一直晋升也很有信念,但和自建环境差距还是很大,还有哪里可能有问题?忽然想到,创立索引后,在控制台的慢查问列表中看到很多存储过程的调用 sql,且扫描记录数微小,如果是走 meeting_id 惟一索引,应该扫描很少的记录数才对,难道没有走索引?或者没有走 meeting_id 主键索引?分割客户,心愿提供测试环境登陆测试。

在测试环境,首先心愿验证一下两个 SQL 的执行打算到底是怎么样的。登陆实例后,别离对两个存储过程中的 SQL 执行 explain,发现走的的确是主键(meeting_id):

为了进一步确认 SQL 在存储过程中的理论执行打算,批改了一下测试的存储过程逻辑,退出了 SQL 执行的 explain 后果和理论执行的 trace,过程中次要减少的代码如下:

执行打算后果如下:

从后果看,两个 SQL 竟然真的没有走主键 meeting_id 索引,而是都走了 phone_id 这个一般的二级索引,其中第一个查问 SQL 走的索引全扫描,扫描记录数 rows 为 397399,和表的记录数统一,显然走了全索引扫描,尽管比全表扫描好一些,但效率依然低下;另外一个 update 的 SQL 走了失常的索引扫描,rows 只有 2,性能高效。为什么两个 SQL 没有走 meeting_id 这个主键索引呢?看 trace 打印的局部内容:

trace 显示两个 SQL 在优化器剖析时,将 meeting_id 做了隐式转换,转换函数为 convert (‘meeting_id’ using utf8mb4),也就是将 meeting_id 做了字符集的转换,相熟索引机制的同学都分明,这种状况下优化器是不会走 meeting_id 索引的。这也能够解释了客户第一次创立索引的时候为啥有性能晋升,但成果并不显著,起因就是只有 update 语句真正用到了索引带来的性能晋升,而且是 phone_id 索引带来的晋升,不是性能更高的主键 meeting_id。

水落石出

当初聚焦到最要害的问题,meeting_id 为啥要做字符集的隐式转换?查看了一下实例相干字符集的设置:

表和列的字符集都为 utf8;
表所在库的字符集为 utf8mb4;
server 字符集((character_set_server))为 utf8
character_set_client/character_set_connection/character_set_results 为 utf8mb4
果然,server、database、table 的字符集不完全一致,猜测一下理论流程应该是这样的:存储过程中传入的字符参数字符集为 utf8mb4,和表中字符集为 utf8 的字段 meeting_id 比拟时,meeting_id 做了字符集的隐式转换,转换为 utf8mb4 后再和输出参数比拟,从而导致 meeting_id 上的索引无奈应用。

依据这个猜想,倡议用户将表的字符集更改为 utf8mb4,这样应该能够防止字符集的转换。因为这个性能还未上线,用户间接对 表做了字符集的批改:

alter table zm_meeting convert to character set utf8mb4;
批改后让用户再次测试,预期成果终于呈现,并发 500 测试在秒级实现,trace 查看执行打算,都走了 meeting_id 的主键索引,隐式转换也随之隐没,性能问题失去了彻底解决。

后续思考

存储过程的入参为啥应用了 utf8mb4?这是本次案例的外围,查阅 mysql 文档,存储过程介绍外面有一段形容:

简略说,就是存储过程的字符型参数,如果没有显式指定字符集,默认将会应用所在数据库的字符集,而本案例中表所在的数据库字符集为 utf8mb4,所以参数默认应用了 utf8mb4,导致了匹配过程的隐式转换。存储过程外间接写 SQL 为什么没有这种状况产生,我猜想比拟的字符串应该会主动匹配‘=’右边表字段的字符集。

既然这样,实践上间接批改参数的字符集应该也能够达到同样后果,简略测试下,将存储过程参数加上表上的字符集属性:

CREATE PROCEDURE zm_sp_next_phone_id(IN p_meeting_id VARCHAR(36) character set utf8)
测试后果如咱们预期,不会产生隐式转换,执行打算正确。

问题尽管解决了,起因也找到了,但反思一下整个过程,如果用户的 server、库、表字符集可能保持一致,将齐全能够防止这个故障。与字符集相干的相似故障也能够大概率防止,所以客户侧还是要有肯定的设计规范;产品侧如果有肯定的查看规定能够帮客户发现相似的隐患,对晋升客户体验也是一种很有价值的服务。

作者:翟振兴

正文完
 0