最近Python关于协程和元编程我实在看不下去了,就来补一下MySQL的坑,第二章关于基准测试的部分先放着,放到最后再说。
先说重点吧,3-6掌主要是讲优化的。我看了一遍只能说懵懂懵懂,就借着写blog的机会再次学习一下。
我们常遇到的三个性能相关的服务请求是:如何确认服务器是否达到了性能最佳状态,找出某条语句为什么执行不够快,以及诊断被用户描述成“停顿”,“堆积”或者“卡死”的某些间歇性疑难故障。
性能优化简介
关于性能的描述,基于不同的环境每个人的理解可能都不同。这里我们将性能规定一下,将其定义为完成某件任务所需要的时间度量,换句话说,性能即响应时间。数据库服务器的性能用查询的响应时间来度量,单位是每个查询花费的时间。查询的响应时间更能体现性能是否变得更好。如果把性能优化仅仅看成是提升每秒查询量,其实这只是吞吐量的优化。吞吐量的提示可以看作性能优化的副产品。对查询的优化可以让服务器每秒执行更多的查询,因为每条查询执行的时间更短了。
所以如果目标是降低响应时间,那么就需要理解为什么服务器执行查询需要那么多时间,然后去减少一些不必要的工作。很多人在优化的时候,都将精力放在修改一些东西上,却很少去进行精确的测量。如果测量了系统中完整而且正确的数据,性能问题一般都能暴露出来,对症下药的解决方案就会比较明了。
那么我们便需要合适的测量范围,这是什么意思?合适的测量范围是说只测量需要优化的活动。有两种比较常见的情况会导致不合适的测量:
- 在错误的时间启动和停止测量。
- 测量的是聚合后的信息,而不是目标活动本身。
例如一个常见的错误是先查看慢查询,然后去排查整个服务器的情况。如果确认有慢查询,那么就应该测量慢查询,而不是整个服务器。
完成一项任务所需要的时间可以分为两个部分:执行时间和等待时间。
优化执行时间最好的策略是通过测量定位不同子任务花费的时间,然后优化去掉一些子任务,降低子任务的执行频率或者提升子任务的效率。
优化等待时间则要复杂一些,因为可能是某些因素间接造成的。
刚才说子任务优化,但是一些运行不频繁或者很短的子任务对整体的影响很小,通常可以忽略不计。那么我们就需要判断哪些任务是需要优化的,这时性能剖析就可以派上用场了。
通过性能剖析进行优化
性能剖析一般有两个步骤:测量任务所花费的时间;然后对结果进行统计和排序,将重要的任务排到前面。性能剖析工具的工作方式基本相同,在任务开始时启动计时器,在任务结束时停止计时器,然后用结束时间减去启动时间得到响应时间。(实际上在分析MySQL的时候,经常指的是查询)
我们讨论两种类型的性能剖析:基于执行时间的分析和基于等待的分析。基于执行时间的分析研究的是什么任务的执行时间最长,而基于等待分析则是判断任务在什么地方被阻塞。
如果任务执行长是因为消耗了太多的资源并且大部分时间花费在执行上,等待的时间不多,这种情况下基于等待的分析作用就不大。反之亦然,如果任务一直在等待,没有消耗什么资源,就去分析时间就不会有什么结果的。如果不能确定是基于什么的,那么两个都要试。
理想情况的性能优化需要依赖更多的测量点,但是即使系统没有提供测量点也是可以有其他办法展开优化工作。可以从外部测量或者基于对系统的了解做出猜测。但是一定要记住,不论是外部测量或者是猜测,数据都不是百分之百准确的。
理解性能剖析
值得优化的查询
性能剖析不会自动给出哪些查询值得花时间去优化。所以要说几点,第一,一些只占总响应时间比重很小的查询是不值得优化的(根据阿木达尔定律,约5%)。第二,如果花费了1000块去优化一个任务,但收入没有增加,那么可以说反而导致了逆优化1000块,如果成本大于收益,因当停止优化。
异常情况
某些任务即使没有出现在性能剖析里也需要优化。比如,某些任务执行次数很少,但每次都很慢,严重影响体验。
未知的未知
即使性能剖析可能没有发现丢失时间,也需要考虑这类问题的存在的可能性,这样才不会错过重要的信息。
被掩藏的细节
性能剖析无法显示所有响应时间的分布。只相信平均值是非常危险的,它会隐藏很多信息,而且无法表达全部情况。
对应用程序进行性能剖析
对任何需要消耗时间的任务都可以做性能剖析,当然也包括应用程序。虽然性能问题大多和数据库相关,但应用导致的性能问题也不少。性能的瓶颈可能有很多因素:
- 外部资源,比如调用了Web服务或者搜索引擎。
- 应用需要处理大量的数据,比如分析一个超大的XML文件。
- 在循环中执行昂贵的操作,比如滥用正则。
- 使用了低效的算法,比如使用暴力搜索算法等等。
幸运的是MySQL的问题没有那么复制,只需要一个应用程序的剖析工具即可。
(这里作者举了个测试PHP程序的例子,因为我不用PHP,暂时跳过一下)
剖析MySQL查询
剖析服务器负载
服务器端的剖析很有价值,因为在服务器端可以有效地审计效率低下的查询。定位和优化“坏”查询能够显著地提升应用的性能,也能解决某些特定的难题。当然,如果只是需要剖析并找出代价高的查询,就不需要如此复杂。有一个工具就可以帮助我们,就是慢查询日志。
在MySQL中,慢查询日志最初只是捕获比较“慢”的查询,而性能剖析却需要针对所有的查询。在5.0以及以前的版本中,单位是s,现在已经是微秒级了。
慢查询是目前开销较低,精度最高查询时间的工具,慢查询带来的开销几乎可以不计,更需要担心的是日志可能消耗掉大量磁盘空间
如果是Percona Server的慢查询日志,则比MySQL官方版本记录了更多细节且有价值的信息,如查询执行计划,锁,I/O活动等。这些特性都是随着处理各种不同的优化场景的需求而慢慢加进来的。总的来说,慢查询日志是一种轻量而且功能全面的性能剖析工具,是服务器查询的利器。
如果因为权限不足等原因,无法在服务器上查询。那么有两种技术可以替代,第一是通过 --processlist 选项不断查看 SHOW FULL PROCESSLIST 的输出,记录查询第一次出现的时间和消失时间。第二个方法是抓包,然后根据MySQL的客户端/服务器通信协议进行解析。
分析查询日志
首先应该生成一个剖析报告,如果需要,则可以再查看日志中需要特别关注的部分。(输出的报告细节详细,绝对可以让生活变得更美好)
一个pt-query-digest输出报告的例子
Rank | Query ID | Response time | Calls | R/Call | V/M | Item |
=== | ============== | ============ | ==== | ==== | === | ============= |
1 | 0xBFCF8E3F293F6466 | 11256.3618 68.1% | 78069 | 0.1442 | 0.21 | SELECT InvitesNew? |
首先每个查询都有一个ID,这是对查询语句计算出的哈希值指纹,计算时去掉了查询条件中的文本值的所有空格。
报告中的V/M列提供了方差均值比的详细数据,方差均值比也就是常说的离差指数。离差指数高的查询对应的执行时间的变化较大,而这类查询通常都是值得去优化的。
还有一个详细版本的,这里暂时就不放了,当你确定需要优化查询时,可以利用这个报告迅速地检查查询的执行情况。
通过慢查询日志记录查询或者使用pt-query-digest分析tcpdump的结果,是可以找到的最好的两个方式。
剖析单条查询
在定位到需要优化的单条语句后,可以针对此查询“钻取”更多的信息,确认为什么会花费这么长的时间执行,以及需要如何去优化。
使用show profile
首先我们需要 set profiling = 1 开启
然后在服务器上执行的所有语句,都会测量其耗费的时间和其他一些查询执行状态变更相关的数据。当一条查询提交给服务器时,此工具会记录剖析信息到一张临时表,并且给查询赋予一个1开始的整数标识
先执行一条查询,然后看一下show profiles
Query_ID | Duration | Query |
1 | 0.00019600 | select *from employee |
因为我只有一条语句,所以查询的速度比较快,花费时间大概是0.000196s
执行一下show profile for query 1,可以查看一下各步骤花费时间:
Status | Duration |
+----------------------+----------+
| starting | 0.000041 |
| checking permissions | 0.000004 |
| Opening tables | 0.000014 |
| init | 0.000012 |
| System lock | 0.000014 |
| optimizing | 0.000009 |
| statistics | 0.000010 |
| preparing | 0.000007 |
| executing | 0.000001 |
| Sending data | 0.000030 |
| end | 0.000002 |
| query end | 0.000005 |
| closing tables | 0.000004 |
| freeing items | 0.000038 |
| cleaning up | 0.000006|
很遗憾,现在还是不能通过Order by 命令排序,但是如果去查INFORMATION_SCHEMA中的profiling表,则可以按需要格式化输出。
使用 show status
MySQL 的 show status命令返回了一些计数器。既有服务器级别的全局计数器,也有“基于某个连接的会话级的计数器”
| Variable_name | Value |
+----------------------------+--------+
| Created_tmp_disk_tables | 0 |
| Created_tmp_files | 5 |
| Created_tmp_tables | 10 |
| Handler_commit | 8 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 24 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 7 |
| Handler_read_key | 7 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 204140 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 1698
可以看到该查询使用了三个临时表,其中两个是磁盘表,并且有很多的没有用到索引的读操作。还有一个要注意的地方,show status 本身也会创建一个临时表
使用Performance Schema
在Performance Schema中有许多关于event的表(但是这些表我还没完全弄明白是干什么的,其中有个
events_waits_summary_by_host_by_event_name 表有一些数据方面的信息可以作为参考
使用性能剖析
当获得服务器或者查询的剖析报告后,怎么使用?好的剖析报告能够将潜在的问题显示出来,但是最终的解决方案还是需要用户来决定。优化查询时,用户需要对服务器如何执行查询有比较深入的了解
诊断间歇性查询
间歇性的问题比如系统偶尔停顿或者慢查询,很难诊断。有些幻影问题只在没有注意到的时候才发生,而且无法确定如何重现,诊断这样的问题往往要花费很多时间,有时候甚至是需要好几个月。
如果一时无法定位问题,可能是测量的方式不正确,或者测量的选择点有误,或者使用工具不合适,但是不要尝试偶发试错。
在很多实际案例中,有些问题可能是数据库导致的,也有可能不是数据库导致的,只有在发生问题的地方通过观察资源情况,尽可能地测量出数据,才能避免没有必要的消耗。
单条查询问题还是服务器问题
首先要确认的是单条问题,还是服务器问题。如果服务器上所有的查询都突然变慢了,又突然变好了,那可能就是服务器的原因。反过来说,如果只有一条出现问题,其他都正常,那应该就是这条出了问题。
使用show global status
这方法实际上就是以较高的频率比如一秒执行一次show global status 命令捕获数据,问题出现时,则可以通过某些计数器的“尖刺”或者“凹陷”来发现。这个命令每秒输出一行,可以运行几小时或者几天,然后将结果绘制成图形,这样就可以方便地发现是否有趋势的突变。如果问题确实是间歇性的,发生的频率又较低,也可以根据需要尽可能长时间地运行此命令,直到发现问题再回头来看输出结果。大部分情况下都能准确定位问题所在。
使用show processlist
用于观察线程是否处于正常状态。也可以直接查询INFORMATION_SCHEMA中的processlist表。
使用查询日志
如果要通过查询日志发现问题,需要开启慢查询日志并在全局级别设置long_query_time为0,并且要确认所有的连接都采用了新的设置。如果因为某些原因,不能设置慢查询日记记录所有的查询,也可以通过tcpdump和pt-query-digest工具来模拟替代。要注意找到吞吐量突然下降的时间点的日志。查询是在完成阶段才写入到慢查询日志的,所以堆积会造成大量查询处于完成阶段,直到阻塞其他查询的资源占用者释放资源后,其他的查询才能执行完成。
捕获诊断数据
当出现间歇性问题时,需要尽可能多收集所有数据,而不只是问题出现时的数据。
在开始前,需要搞清楚两个事:
- 一个可靠且实时的“触发器”,也就是能区分出什么时候问题出现的方法。
- 一个收集诊断数据的工具
触发器非常重要,有两个常见的情况可能导致无法达到预期:误报和漏检。所以通常来说,在开始收集前多花点时间确认触发器能够真正的识别问题是划算的。
那么好的触发器标准是什么?比方说Threads_running的趋势在出现问题时会比较敏感,而没有问题则比较平稳。另外show processlist中的线程异常也是一个指标。
选择一个合适的阈值很重要,既要要求高,以确保在正常时不会被触发;又不能太高,要确保问题发生时不会错过。
所以我们需要利用一种工具来监控服务器。Percona Toolkit中的pt-stalk就是为这种情况设计的。
需要收集什么样的数据?
就像之前说的,尽可能收集全部数据,需要收集的是在诊断时会用到的(比方说,系统状态,CPU利用率,磁盘使用空间和可用空间,ps的输出采样,内存利用率,以及从MySQL中获取的数据,如 show status,show processlist,show InnoDB status等)
执行时间包括用于工作的时间和等待时间。当一个未知问题发生时,一般来说有两种可能,服务器需要做大量的工作,从而导致大量消耗CPU,或者在等待某些资源被释放。
杂谈(非正式向)
实不相瞒,这里这些工具我自己还没用过,里面涉及的库表我基本都看过了,这个怎么说呢,还是需要经常使用才能牢记其中的东西。
这节主要还是阐述了如何去定位问题,找到问题的所在,之后会说一些具体的优化方式。