『MySQL 实战 45 讲』22 - MySQL 有哪些“饮鸩止渴”提高性能的方法?

MySQL 有哪些"饮鸩止渴"提高性能的方法?

  1. 需求:业务高峰期,生产环境的 MySQL 压力太大,没法正常响应,需要短期内、临时性地提升一些性能

短连接风暴

  1. 短连接模式:执行很少的 SQL 语句就断开,下次需要的时候再重连。如果使用的是短连接,在业务高峰期的时候,就可能出现连接数突然暴涨的情况
  2. max_connections 参数,用来控制一个 MySQL 实例同时存在的连接数的上限,被拒绝的连接的请求,从业务角度就是数据库不可用
  3. 机器负载比较高时,处理现有请求的时间变长,每个连接保持的时间也更长,就有可能超过 max_connections 参数
  4. 一个比较自然的想法,就是调高 max_connections 的值,但是系统的负载可能进一步加大,并且量的资源耗费在权限验证等逻辑上

第一种方法:先处理掉那些占着连接但是不工作的线程

  1. 对于不需要保持的连接,可以通过 kill connection 主动踢掉,相当于设置 wait_timeout 参数效果一样
  • wait_timeout 参数:一个线程空闲 wait_timeout 这么多秒之后,就会被 MySQL 直接断开连接
  1. 例如下面例子
  • 可以用过 SELECT CONNECTION_ID(); 查询当前会话 id
  • 通过 show processlist; 查看进程结果,其中 109 为 A,110 为 B,112 为 C,从而可以看到哪些会话是 Sleep 状态
  • 通过 select * from information_schema.innodb_trx\G 可以看到事务具体状态
  • 其中 trx_mysql_thread_id=109 表示 id = 109 A 线程还在事务中
  • 从服务端断开连接使用的是 kill connection + id
  • 它的连接被服务端主动断开后,这个客户端并不会马上知道。直到客户端在发起下一个请求的时候,才会收到这样的报错
  • 注意:从数据库端主动断开连接可能是有损的,尤其是有的应用端收到这个错误后,不重新连接,而是直接用这个已经不能用的句柄重试查询。这会导致从应用端看上去,"MySQL 一直没恢复"。

第二种方法:减少连接过程的消耗

  1. 有的业务代码会在短时间内先大量申请数据库连接做备用,从而导致服务打挂,那么一种可能的做法,是让数据库跳过权限验证阶段
  2. 跳过权限验证的方法:
  • 重启数据库,并使用 --skip-grant-tables 参数启动
  • 注意:风险极高,特别是外网访问
  • 若确定开启该参数,MYSQL8 会默认把 --skip-networking 参数打开,表示这时候数据库只能被本地的客户端连接

慢查询性能问题

  1. 大体有以下三种可能
  • 索引没有设计好
  • SQL 语句没写好
  • MySQL 选错了索引

导致慢查询的第一种可能是,索引没有设计好

  1. 这种场景一般就是通过紧急创建索引来解决
  • MySQL 5.6 版本以后,创建索引都支持 Online DDL
  • 对于那种高峰期数据库已经被这个语句打挂了的情况,最高效的做法就是直接执行 alter table 语句
  1. 比较理想的是能够在备库先执行。假设你现在的服务是一主一备,主库 A、备库 B,大致流程是这样的
  • 在备库 B 上执行 set sql_log_bin=off,也就是不写 binlog,然后执行 alter table 语句加上索引
  • 执行主备切换
  • 这时候主库是 B,备库是 A。在 A 上执行 set sql_log_bin=off,然后执行 alter table 语句加上索引
  1. 这是一个"古老"的 DDL 方案。平时在做变更的时候,你应该考虑类似 gh-ost 这样的方案,更加稳妥。但是在需要紧急处理时,上面这个方案的效率是最高的

导致慢查询的第二种可能是,语句没写好

  1. 我们可以通过改写 SQL 语句来处理。MySQL 5.7 提供了 query_rewrite 功能,可以把输入的一种语句改写成另外一种模式
  2. MYSQL 8 安装 install_rewriter 插件,需要从 share 目录找到 install_rewriter.sql 脚本
sql 复制代码
# 登录
mysql -uroot -p 
# 执行脚本
source install_rewriter.sql
# 查看是否有 Rewriter 插件
show plugins
# 查看是否改写开启
show variables like '%rewrite%'
  1. 例如语句被错误地写出了 select * from t where id + 1 = 10000,可以通过下面方式改写规则
sql 复制代码
# 其中 testdb 是你的库
mysql> insert into query_rewrite.rewrite_rules(pattern, replacement, pattern_database) values ("select * from t where id + 1 = ?", "select * from t where id = ? - 1", "testdb");
# 这个存储过程,是让插入的新规则生效,也就是我们说的"查询重写
call query_rewrite.flush_rewrite_rules();
  • 通过 show warnings 可以看到是否生效

MySQL 选错了索引

  1. 应急方案是在语句加上 force index

总结

  1. 由慢查询导致性能问题的三种可能情况,实际上出现最多的是前两种
  2. 可以通过下面过程,预先发现问题
  • 上线前,把慢查询日志(slow log)打开,并且把 long_query_time 设置成 0,确保每个语句都会被记录入慢查询日志
  • 在测试表里插入模拟线上的数据,做一遍回归测试
  • 观察慢查询日志里每类语句的输出,特别留意 Rows_examined 字段是否与预期一致
  1. 新增的 SQL 语句不多,手动跑一下就可以。而如果是新项目的话,或者是修改了原有项目的表结构设计,全量回归测试都是必要的。这时候,你需要工具帮你检查所有的 SQL 语句的返回结果。比如,你可以使用开源工具 pt-query-digest

QPS 突增问题

  1. 有时候由于业务突然出现高峰,或者应用程序 bug,导致某个语句的 QPS 突然暴涨,也可能导致 MySQL 压力过大,影响服务
  • 理想的情况:让业务把这个功能下掉
  1. 如果从数据库端处理的话,对应于不同的背景,有不同的方法可用
  • 一种是由全新业务的 bug 导致的:如果 DB 运维是比较规范的,说白名单是一个个加的。这种情况下,如果你能够确定业务方会下掉这个功能,只是时间上没那么快,那么就可以从数据库端直接把白名单去掉
  • 这个新功能使用的是单独的数据库用户,可以用管理员账号把这个用户删掉,然后断开现有连接
  • 这是一个止血方案,最低优先级 )这个新增的功能跟主体功能是部署在一起的,那么我们只能通过处理语句来限制。这时,我们可以使用上面提到的查询重写功能,把压力最大的 SQL 语句直接重写成"select 1"返回
    • 注意:这个操作风险会很高
    • 如果别的功能里面也用到了这个 SQL 语句模板,会有误伤
    • 很多业务并不是靠这一个语句就能完成逻辑的,所以如果单独把这一个语句以 select 1 的结果返回的话,可能会导致后面的业务逻辑一起失败
  1. 前 2 个方案都要依赖于规范的运维体系:虚拟化、白名单机制、业务账号分离
相关推荐
gQ85v10Db19 分钟前
Redis分布式锁进阶第十六篇:番外高阶避坑篇 + 隐性埋点锁故障深挖 + 疑难杂症终极兜底方案
数据库·redis·分布式
S1998_1997111609•X26 分钟前
论恶意注入污染蜜罐进程函数值取仺⺋以集团犯罪获取数据爬虫的轮系依据
网络·数据库·爬虫·网络协议·百度
许彰午28 分钟前
# 从OOM到根治的完整过程——导出大数据的应急、根因分析与游标方案
java·大数据·数据库·系统架构
eLIN TECE42 分钟前
nacos2.3.0 接入pgsql或其他数据库
数据库
曾几何时`1 小时前
MySQL(七)索引
数据库·mysql
KmSH8umpK1 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第九篇
数据库·redis·分布式
悠悠121381 小时前
一条 SQL 从敲下回车,到在 MySQL 里“跑完一生”,中间到底经历了啥?
数据库·sql·mysql
秋92 小时前
MySQL 9.7.0 使用详解:新特性、实战与避坑指南
android·数据库·mysql
S1998_1997111609•X2 小时前
恶意注入污染蜜罐HDMI进程函数值进行封禁垃圾蠕虫仓蟲的轮系依据行为戆直㞢仓shell token
数据库·爬虫·网络协议·百度·开闭原则
Yupureki2 小时前
《Redis数据库》1.初识Redis
数据库·redis·缓存