有时候,在业务高峰期,生产环境的MySQL压力太大,没法正常响应,需要短期内、临时性地提升一些性能。本文就来讲讲一些临时方法,并着重说它们可能存在的风险。
短连接风暴
正常的短连接模式是连接到数据库后,执行很少的SQL语句就断开,下次需要的时候再重连。如果使用的是短连接,在业务高峰期时,可能出现连接数突然暴涨的情况。
MySQL建立连接的过程成本很高,在数据库压力比较小的时候,这些额外成本并不明显。
短连接模型存在一个风险,就是一旦数据库处理慢一些,连接数就会暴涨。max_connections参数用来控制一个MySQL实例同时存在的连接数的上限,超过这个值,系统就会拒绝接下来的连接请求并报错。对于被拒绝连接的请求来说,从业务角度看就是数据库不可用。
当机器负载较高,处理现有请求时间变长,每个连接保持的时间也更长,这时来的新连接很可能就超过数量限制。此时如果调高参数,那么系统负载可能进一步加大,大量的资源耗费在权限验证等逻辑上,结果可能更差。
这里还有两种方法,但都是有损的。
方法一:先处理掉那些占着连接但是不工作的线程
对于不需要保持的连接,可以通过kill connection主动剔除,这个行为和事先设置wait_timeout效果相同。wait_timeout参数表示线程空闲多少时间后会被MySQL直接断开连接。
在show processlist的结果里,踢掉显示为sleep的线程,可能是有损的:
在上面的流程中,如果断开session A的连接,由于事务还未提交,MySQL只能按照回滚事务来处理;如果断开session B的连接,就没有大影响。因此按照优先级来说,应该先断开像session B这样的事务外的空闲连接。
如何判断哪些是事务外空闲的呢?session C执行show processlist看到的结果为:
id=4,id=5两个会话都是sleep状态,而要看事务具体状态的话,可以查information_schema库的innodb_trx表:
trx_mysql_thread_id=4,表示id=4的线程还处在事务中。
因此,如果连接数过多,可以优先断开事务外空闲久的连接;如果还是太多,再考虑断开事务内空闲久的连接。
服务端断开连接用的是kill connection+id的命令,处于sleep状态的客户端连接被服务端主动断开后,客户端并不会马上知道,直到客户端在发起下一个请求时,才会收到报错“Lost connection to MySQL server during query"。
方法二:减少连接过程的消耗
一种可能的做法是让数据库跳过权限验证阶段,方法是重启数据库,并使用-skip-grant-tables参数启动,这样整个MySQL会跳过所有权限验证阶段,包括连接过程和语句执行过程在内。
但该方法风险极高,并不建议使用。在MySQL 8.0版本中,如果启用该参数,MySQL会默认把--skip-networking参数打开,表示这时候数据库只能被本地客户端连接,不可外网访问。
慢查询性能问题
在MySQL中,会引发性能问题的慢查询大体分为三种可能:
- 索引没有设计好;
- SQL语句没写好;
- MySQL选错索引。
接下来就具体分析这三种可能。
索引没有设计好
这种场景一般就是通过紧急创建索引来解决。在MySQL 5.6版本之后,创建索引已支持Online DDL,对于高峰期数据库被语句打挂了的情况,最高效的做法就是直接执行alter table语句。
理想情况是能在备库先执行。假设现在有主库A和备库B,该方案的大致流程为:
- 在备考B上执行set sql_log_bin=off,即不写binlog,然后执行alter table去加索引;
- 执行主备切换;
- 在A上执行set sql_log_bin=off,然后执行alter table加索引。
平时变更时,应该考虑gh-ost这样的方案更为稳妥,但紧急处理时上面的方案效率最高。
SQL语句没写好
有时候语句没写好,会导致语句没有使用上索引。这时可以通过改写SQL语句来处理。MySQL 5.7提供了query_rewrite功能,可以把输入的一种语句改写成另一种模式。比如语句被错误写成select * from t where id+1=10000,可以通过下面的方式进行改写:- mysql> insert into query_rewrite.rewrite_rules(pattern, replacement, pattern_database) values ("select * from t where id + 1 = ?", "select * from t where id = ? - 1", "db1");
- call query_rewrite.flush_rewrite_rules();
复制代码 call query_rewrite.flush_rewrite_rules()的存储过程是让插入的新规则生效,可以用下面的方法来确认改写规则是否生效:
MySQL选错索引
这时应急方案就是给语句加上force index。
上面的三种情况,实际上出现最多的是前两种。为了避免这两种情况,可以通过下面的过程预先发现问题:
- 上线前,在测试环境打开慢查询日志,且把long_query_time设为0,确保每个语句都会被记录入慢查询日志;
- 在测试表里插入模拟线上的数据,做一遍回归测试;
- 观察日志里 每类语句的输出,特别留意Rows_examined字段是否与预期一致。
如果新增SQL语句不多,手动跑一下就可以。如果是新项目或修改了原项目的表结构设计,全量回归测试都是必要的,这是可以借助开源工具pt-query-digest。
QPS突增问题
有时候由于业务突然出现高峰,或者应用程序bug,导致某个语句的QPS突然暴涨,也可能导致MySQL压力过大,影响服务。
对于功能bug,最理想的情况是让业务把这个功能下掉,下掉一个功能,从数据库端处理的话,对于不同的背景,有不同的方法可用:
- 由全新业务的bug导致。假设你的DB运维是比较规范的,也就是说白名单是一个个加的。这种情况下,如果能够确定业务方会下掉这个功能,只是时间上没那么快,那么就可以从数据库端直接把白名单去掉;
- 如果功能使用的是单独的数据库用户,可以删除这个用户然后断开现有连接;
- 如果新增功能跟主体功能部署在一起,只能通过处理语句来限制,如把压力最大的SQL语句直接重写成select 1。这个操作风险很高,可能存在两个副作用:
- 如果别的功能也用到了这个SQL语句模板,会有误伤;
- 很多业务不是只靠一个语句完成,如果单独把这个语句重写,可能导致后面的业务逻辑一起失败。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |