我们先来看第一个阶段,MySQL慢的诊断思路,一般我们会从三个方向来做:
第一个方向是MySQL内部的观测
第二个方向是外部资源的观测
第三个方向是外部需求的改造
1.1 MySQL 内部观测
我们来看MySQL内部的观测,常用的观测手段是这样的,从上往下看,第一部分是Processlist,看一下哪个SQL压力不太正常,第二步是explain,解释一下它的执行计划,第三步我们要做Profilling,如果这个SQL能再执行一次的话, 就做一个Profilling,然后高级的DBA会直接动用performance_schema ,MySQL 5.7 以后直接动用sys_schema,sys_schema是一个视图,里面有便捷的各类信息,帮助大家来诊断性能。再高级一点,我们会动用innodb_metrics进行一个对引擎的诊断。
除了这些手段以外,大家还提出了一些乱七八糟的手段,我就不列在这了,这些是常规的一个MySQL的内部的状态观测的思路。除了这些以外,MySQL还陆陆续续提供了一些暴露自己状态的方案,但是这些方案并没有在实践中形成套路,原因是学习成本比较高。
1.2 外部资源观测
外部资源观测这部分,我引用了一篇文章,这篇文章的二维码我贴在上面了。这篇文章是国外的一个神写的,标题是:60秒的快速巡检,我们来看一下它在60秒之内对服务器到底做了一个什么样的巡检。一共十条命令,这是前五条,我们一条一条来看。
1.uptime,uptime告诉我们这个机器活了多久,以及它的平均的负载是多少。
2.dmesg -T | tail,告诉我们系统日志里边有没有什么报错。
3.vmstat 1,告诉我们虚拟内存的状态,页的换进换出有没有问题,swap有没有使用。
4. mpstat -P ALL,告诉我们CPU压力在各个核上是不是均匀的。
5.pidstat 1,告诉我们各个进程的对资源的占用大概是什么样子。
我们来看一下后五条:
首先是iostat-xz 1,查看IO的问题,然后是free-m内存使用率,之后两个sar,按设备网卡设备的维度,看一下网络的消耗状态,以及总体看TCP的使用率和错误率是多少。最后一条命令top,看一下大概的进程和线程的问题。
这个就是对于外部资源的诊断,这十条命令揭示了应该去诊断哪些外部资源。
1.3 外部需求改造
第三个诊断思路是外部的需求改造,我在这里引用了一篇文档,这篇文档是MySQL的官方文档中的一章,这一章叫Examples of Common Queries,文档中介绍了常规的SQL怎么写, 给出了一些例子。文章的链接二维码在slide上。
我们来看一下它其中提到的一个例子。
它做的事情是从一个表里边去选取,这张表有三列,article、dealer、price,选取每个作者的最贵的商品列在结果集中,这是它的最原始的SQL,非常符合业务的写法,但是它是个关联子查询。
关联子查询成本是很贵的,所以上面的文档会教你快速地把它转成一个非关联子查询,大家可以看到中间的子查询和外边的查询之间是没有关联性的。
第三步,会教大家直接把子查询拿掉,然后转成这样一个SQL,这个就叫业务改造,前后三个SQL的成本都不一样,把关联子查询拆掉的成本,拆掉以后SQL会跑得非常好,但这个SQL已经不能良好表义了,只有在诊断到SQL成本比较高的情况下才建议大家使用这种方式。
为什么它能够把一个关联子查询拆掉呢?
这背后的原理是关系代数,所有的SQL都可以被表达成等价的关系代数式,关系代数式之间有等价关系,这个等价关系通过变换可以把关联子查询拆掉。
上面的这篇文档是一个大学的教材,它从头教了关于代数和SQL之间的关系。然后一步步推导怎么去简化这句SQL。
第一,MySQL本身提供了很多命令来观察MySQL自身的各类状态,大家从上往下检一般能检到SQL的问题或者服务器的问题。
第二,从服务器的角度,我们从巡检的脚本角度入手,服务器的资源就这几种,观测手法也就那么几种,我们把服务器的资源全部都观察一圈就可以了。
第三,如果实在搞不定,需求方一定要按照数据库容易接受的方式去写SQL,这个成本会下降的非常快,这个是常规的MySQL慢的诊断思路。
有人删了千万级的数据,结果导致频繁的慢查询。
线上收到大量慢查询告警,于是检查慢查询的SQL,发现不是啥复杂SQL,这些SQL主要针对一个表,基本都是单行查询,看起来应该不会有慢查询。这种SQL基本上都是直接根据索引查找出来的,性能应该极高。
是否可能慢查询不是SQL问题,而是MySQL生产服务器的问题?特殊情况下,MySQL出现慢查询还真不是SQL问题,而是他自己生产服务器的负载太高,导致SQL语句执行慢。比如现在MySQL服务器的
磁盘I/O负载高,每秒执行大量高负载的随机I/O,但磁盘本身每秒能执行的随机I/O有限,导致正常SQL在磁盘执行时,若跑一些随机IO,你的磁盘太忙,顾不上你了,导致你本来很快的一个SQL,要等很久才能执行完毕,这时就可能导致正常SQL也变成慢查询。
也许网络负载高,导致你一个SQL语句要发到MySQL,光是等待获取一个和MySQL的连接,都很难,要等很久或MySQL自己网络负载太高,带宽打满,带宽打满后,你一个SQL也许执行很快,但其查出来的数据返回给你,网络都送不出去,也会变成慢查询。
若CPU负载过高,也会导致CPU过于繁忙去执行别的任务,没时间执行你的SQL。
所以慢查询不一定是SQL本身导致,若觉得SQL不应该会慢查询,结果他那个时间段跑这个SQL 就是慢,应排查当时MySQL服务器的负载,尤其看看磁盘、网络及 CPU 的负载,是否正常。
当某个离线作业瞬间大批量把数据往MySQL里灌入的时,他一瞬间服务器磁盘、网络以及CPU的负载会超高。
此时你一个正常SQL执行下去,短时间内一定会慢查询,类似问题,优化手段更多是控制你导致MySQL负载过高的那些行为,比如灌入大量数据,最好在业务低峰期灌入,别影响高峰期的线上系统运行。
但看了下MySQL服务器的磁盘、网络以及CPU负载,一切正常,似乎也不是这问题导致。看起来无解了?
慢 SQL 的头两步排查手段:
这两种办法都不奏效之后,第三步:用MySQL profilling工具去细致的分析SQL语句的执行过程和耗时。
这个工具可以对SQL语句的执行耗时进行非常深入和细致的分析
打开profiling,使用
接着MySQL就会自动记录查询语句的profiling信息。此时若执行show profiles,就会给你列出各种查询语句的profiling信息,会记录下来每个查询语句的query id,所以你要针对你需要分析的query找到对他的query id,我们当时就是针对慢查询的那个SQL语句找到了query id。
然后针对单个查询语句,看其profiling信息,使用show profile cpu, block io for query xx,这里的xx是数字,此时就可以看到具体的profile信息。
除了cpu以及block io以外,还能指定去看这个SQL语句执行时候的其他各项负载和耗时。
会给你展示出来SQL语句执行时候的各种耗时,比如磁盘IO的耗时,CPU等待耗时,发送数据耗时,拷贝数据到临时表的耗时等,SQL执行过程中的各种耗时都会展示。
检查该SQL语句的profiling信息后,发现问题,其Sending Data耗时最高,几乎使用1s,占据SQL执行耗时的99%!其他环节耗时低可以理解,毕竟这种简单SQL执行速度真的很快,基本就是10ms级别,结果跑成1s,那肯定Sending Data就是问题根源!
这Sending Data在干啥呢?
MySQL官方释义:为一个SELECT语句读取和处理数据行,同时发送数据给客户端的过程,简单来说就是为你的SELECT语句把数据读出来,同时发送给客户端。
但这过程为啥这么慢?profiling确实是提供给我们更多的线索了,但似乎还是没法解决问题。但已经捕获到异常关键点,就是Sending Data的耗时很高!
接着:
看innodb存储引擎的一些状态,此时发现一个奇怪的指标:history list length,值特别高,达到上万。
MVCC就是多个事务在对同一个数据, 有人写,有人读,此时可以有多种隔离级别,对一个数据有个多版本快照链条,才能实现MVCC和各种隔离级别。
所以当你有大量事务执行时,就会构建这种undo多版本快照链条,此时history list length就会很高。然后在事务提交后,会有一个多版本快照链条的自动purge清理机制,清理了,该值就会降低。一般该值不应过高,所以注意到第二个线索:history list length过高,即大量的undo多版本链条数据没有清理。推测可能有的事务长时间运行,所以其多版本快照不能被purge清理,进而导致history list length过高。
经过这俩线索推测,在大量简单SQL变成慢查询时,SQL因为Sending Data环节异常,耗时过高;同时此时出现一些长事务长时间运行,大量的频繁更新数据,导致有大量undo多版本快照链条,还无法purge清理。
因为发现有大量的更新语句在活跃,而且有那种长期活跃的长事务一直在跑而没有结束,问了下系统负责人,在后台跑了个定时任务:他居然开了一个事务,然后在一个事务里删除上千万数据,导致该事务一直在运行。
这种长事务的运行会导致你删除时,仅只是对数据加了一个删除标记,事实上并没有彻底删除。此时你若和长事务同时运行的其它事务里再查询,他在查询时可能会把那上千万被标记为删除的数据都扫描一遍。因为每次扫描到一批数据,都发现标记为删除了,接着就会再继续往下扫描,所以才导致一些查询语句很慢。
那为何你启动一个事务,在事务里查询,凭什么就要去扫描之前那个长事务标记为删除状态的上千万的垃圾数据?讲道理,那些数据都被删了,跟你没关系了呀,你可以不去扫描他们 嘛!
而问题症结在于,那个 删除千万级数据的事务是个长事务 !即当你启动新事务查询时,那个删除千万级数据的长事务一直在运行,它是活跃的!结合MVCC的Read View机制,当你启动一个新事务查询时,会生成一个Read View。你的新事务查询时,会根据ReadView去判断哪些数据可见及可见的数据版本号,因为每个数据都有个版本链条,有时你能可见的仅是这个数据的一个 历史 版本。
所以正是因为该长事务一直在运行,还在删除大量数据,而且这些数据仅是逻辑删除,所以此时你新开事务的查询还是会读到所有逻辑删除数据,也就会出现千万级的数据扫描,导致了慢查询!
所以禁止在业务高峰期运行那种删除大量数据的语句,因为这可能导致一些正常的SQL都变慢查询,因为那些SQL也许会不断扫描你标记为删除的大量数据,好不容易扫描到一批数据,结果发现是标记为删除的,于是继续扫描下去,导致慢查询!
直接kill那个正在删除千万级数据的长事务,所有SQL很快恢复正常。此后,大量数据清理全部放在凌晨执行,那个时候就没什么人使用系统了,所以查询也很少。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)