SQL优化:话说一次线上事故

时间:2022-09-20 22:12:43

毕业后进入“Common Log Team”,主要对服务器日志(用户上网日志)进行统计、存储及查询,主要使用SQLite,按天切分表,也就是一天一个文件。后来想把这个设计搬到AWS上,对很多服务器日志进行处理。期间用到了mysql数据库,随着数据量越来越大,对mysql的性能要求越来越高。因此优化SQL也是一项亟需提高的技能。本文尝试对SQL优化进行讨论,也算对SQL优化进行学习总结。

一次事故:mysql被搞死了

背景:AWS上有一套系统,我是唯一的Ops(同时也是Dev)。某天下午,leader突然找我说,系统登陆不上。一时吓坏,因为出了事故要被很多人围观的。

首先,leader说他在用phpadmin对某个mysql数据库进行统计,然后突然就感觉出了问题,phpadmin不显示结果了,接着发现系统的页面非常慢。

然后,通过AWS的cloudwatch页面查看mysql服务器的性能指标,发现某台机器的write IOPS非常高。但是为什么这么高呢?接着用命令行登陆mysql,通过show processlist,看到有个正在执行的SQL,状态是Sending data。

(1) Mysql的Write IOPS曲线:

SQL优化:话说一次线上事故

(2) show process:

mysql> show processlist;
+----+----------+--------------------+----------+---------+------+--------------+-----------------------------------------------+
| Id | User     | Host               | db       | Command | Time | State        | Info                                          |
+----+----------+--------------------+----------+---------+------+--------------+-----------------------------------------------+
| 11 | root     | 172.31.10.47:58055 | Profiles | Query   |    0 | init         | show processlist                              |
| 36 | root     | 172.31.10.47:58064 | Profiles | Query   |  767 | Sending data | select * from a,b,c order by c.last_heartbeat |
+----+----------+--------------------+----------+---------+------+--------------+-----------------------------------------------+

文档说Sending data意思是在SELECT语句中读取、处理记录,并将数据发送给客户端。因为处于这个状态的操作往往是在执行大量的disk访问(读取),通常在一次给定查询的整个生命周期中,这是最长的状态。

看到这个Sending data状态的查询,猜测应该是罪魁祸首!如何中断,2个方法:(1)kill Id(show processlist结果中的第一列);(2)重启mysql服务器。

为什么元凶是它?

在processlist可以看到,select * from a,b,c order by c.last_heartbeat这个SQL消耗了大量Write操作,为什么这样:

(1)首先explain一下:

mysql> explain select * from a,b,c order by c.last_heartbeat;
+----+-------------+------------+------+---------------+------+---------+------+------+---------------------------------------+
| id | select_type | table      | type | possible_keys | key  | key_len | ref  | rows | Extra                                 |
+----+-------------+------------+------+---------------+------+---------+------+------+---------------------------------------+
|  1 | SIMPLE      | a          | ALL  | NULL          | NULL | NULL    | NULL | 1030 | Using temporary; Using filesort       |
|  1 | SIMPLE      | b          | ALL  | NULL          | NULL | NULL    | NULL | 2402 | Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | c          | ALL  | NULL          | NULL | NULL    | NULL | 3624 | Using join buffer (Block Nested Loop) |
+----+-------------+------------+------+---------------+------+---------+------+------+---------------------------------------+
  • 对三张表进行全表遍历
  • 需要用临时表存储结果(using temporary),需要对结果进行排序(using filesort)

执行结果会产生多少条数据?表关联的结果是所有记录的笛卡尔积,使用where条件进行筛选。这里没有where条件,这意味着结果是abc的笛卡尔积,共有1030x2402x3624约为60亿条结果。所以结果集很大,需要将结果写入临时表中,那么可想而知要把结果写入临时表中,需要花费大量的磁盘写入操作

所以最好在生产环境执行SQL语句时,特别是有表关联的操作,或者排序操作时,先通过explain看一下mysql的执行流程,最是保险。接下来会对explain的输出内容进行简单的介绍,这些输出非常有用,可以让我们知道SELECT语句是否是慢查询,index是否被正确的使用,是否使用了临时表,是否需要排序等。而临时表、排序、扫全表(没有使用index)都是慢查询的几种情况。