短连接风暴
正常的短连接模式是连接到数据库后,执行很少的SQL语句就断开,下次需要的时候再重连。使用短连接,在业务高峰期的时候,可能出现连接数突然暴涨的情况。MySQL建立连接的过程成本很高,除了网络连接三次握手外,还要做登录权限判断和获取该连接的读取权限。
max_connextions参数,控制一个MySQL实例同时存在的连接数的上限,超过这个值,系统就会拒绝接下来的连接请求,并报错提示“Too many connections”,对业务来说就是数据库不可用。
负载比较高时,单纯调高max_connections的值,让更多的连接进来,会让系统的负载进一步加大,资源耗费在权限验证逻辑上,适得其反,导致已连接线程拿不到CPU资源去执行业务的SQL请求。
1.先处理掉那些占着连接但是不工作的线程
max_connections的计算,不是看谁在running,只要连着就占用一个计数位置。对于不需要保持的连接,可以通过kill connection主动踢掉。
wait_timeout参数,表示一个线程空闲wait_timeout这么多秒之后,就会被MySQL直接断开连接。
sessionA | sessionB | sessionC | |
---|---|---|---|
T | begin;insert into t values(1,1); | select * from t where id=1; | |
T+30s | show processlist |
id | user | host | db | command | time | state | info |
---|---|---|---|---|---|---|---|
4 | root | localhost:xxxxx | test | sleep | 30 | null | |
5 | root | localhost:yyyyy | test | sleep | 30 | null | |
6 | root | localhost:zzzzz | test | query | 0 | starting | show processlist |
会话4、5都是sleep状态,查询information_schema库的innodb_trx表查看事务具体状态:
select * from information_schema.innodb_trx;
其中trx_mysql_thread_id=4;表示id=4的线程处在事务中。还未提交不宜断开,会导致对应事务回滚。
如果连接数过多,优先断开事务外空闲太久的连接;如果还不够,再考虑断开事务内空闲太久的连接。
使用kill connection+id命令从服务器断开连接。一个客户端处于sleep状态时,它的连接被服务器主动断开后,这个客户端不会马上知道,知道客户端发起下一个请求时,才会收到报错”ERROR 2013(HY000):lost connection to MySQL server during query”。
从数据库端主动断开连接可能是有损的,若应用端接收到错误后,不重新连接,而是直接用这个已经不能用的句柄重试查询,导致从应用端看上去”MySQL一直没恢复”。(链接异常断开是常有的事,业务开发的应用代码需要有正确的重连并重试的机制。)
2.减少连接过程的消耗
让数据库跳过权限验证阶段:重启数据库,并使用-skip-grant-tables参数启动。整个数据库会跳过所有的权限验证阶段,包括连接过程和语句执行过程。
跳过权限验证风险极高,MySQL8.0使用时会默认把–skip-networking参数打开,让数据库只能被本地的客户端连接。
慢查询性能问题
- 上线前,在测试环境,把慢查询日志(slow log)打开slow_query_log(默认关闭),并且把long_query_time设置成0(默认值为10),确保每个语句都会被记录人慢查询日志;
- 在测试表里插入模拟线上的数据,做一遍回归测试;
- 观察慢查询日志里每类语句的输出,特别留意Rows_examined字段(server层统计值)是否与预期一致。
开源工具pt-query-digest分析。
索引没有设计好
通过紧急创建索引解决。(MySQL5.6版本以后,创建索引支持Online DDL,可以直接执行alter table 语句)。
主库A、备库B:
- 在备库B上执行set sql_log_bin=off,也就是不写binlog,然后执行alter table语句加上索引;
- 执行主备切换;主库B、备库A。
- 在A上执行set sql_log_bin=off,然后执行alter table语句加上索引。
使用gh-ost在线ddl变更。
SQL语句没有写好
MySQL-相同逻辑,不同SQL,性能差异
条件字段函数操作、隐式类型转换、隐式字符编码转换等原因,导致语句没有使用上索引。
通过改写SQL语句来处理。MySQL5.7提供了query_rewrite功能,可以把输入的一种语句改写成另外一种模式。
1 | insert into query_rewrite.rewrite_rules(pattern,replacement,pattern_database) values ("select * from t where id + 1 = ?","select * from t where id = ? - 1","db1"); |
查询重写
Query ‘select * from t where id + 1 = 10000 ‘ rewritten to ‘select * from t where id = 10000 - 1’ by a query rewrite plugin;
MySQL选错了索引
应急方案给这个语句加上force index。
使用查询重写功能,给原来的语句佳航force index。
QPS突增问题
下掉一个功能,从数据库端处理:
- 由全新业务的bug导致的,确定业务方会下掉这个功能,从数据库端直接把白名单去掉。
- 新功能使用的单独的数据库用户,用管理员账号把这个用户删掉,然后断开现有连接。这样新功能的连接不成功,由它引发的QPS就会变成0。
方案1、2依赖于规范的运维提下:虚拟化、白名单机制、业务账号分离。
- 新功能跟主体功能部署在一起,只能通过处理语句来限制,使用查询重写功能,把压力最大的SQL语句直接重写成”select 1”返回。
如果别的功能也用到了这个SQL语句模板,会有误伤。
很多业务并不是靠这一个语句就能完成逻辑的,单独改这一个语句,可能会导致后面的业务逻辑一起失败。