18-MySQL DBA笔记-MySQL Server调优

摘要:
第18章MySQL服务器优化本章将向您介绍MySQL服务器优化,这是DBA最熟悉的领域之一。2) 如果启用了QueryCache,MySQL将检查QueryCache。如果命中,它将返回QueryCache中的结果集。否则,将执行下一步。3) MySQL解析器解析SQL,MySQL优化器生成执行计划。4) MySQL查询处理引擎执行此执行计划。MySQL性能优化显然旨在优化上述部分或全部链接,并最小化每个链接的时间以提高吞吐量。

第18章 MySQL Server调优
本章将为读者介绍针对MySQL Server的优化,这也是DBA最熟悉的领域之一。
首先我们介绍MySQL的主要参数,然后,讲述常见硬件资源的优化。
我们假设读者已经具备了足够的基础知识,所以,本章将更多的针对一些特定的主题进行叙述。

18.1 概述
衡量数据库性能的指标,一般衡量数据库的性能有两个指标:响应时间和吞吐率。
响应时间又包括等待时间和执行时间。
我们进行优化的主要目的是降低响应时间,提高吞吐率。
下面我们来看下MySQL是如何执行优化和查询的? 大致的步骤如下所示。
1)客户端发送SQL语句给服务器。
2)如果启用了Query Cache,那么MySQL将检查Query Cache,如果命中,就返回Query Cache里的结果集,否则,执行下一个步骤。
3)MySQL Parser解析SQL,MySQL优化器生成执行计划。
4)MySQL查询处理引擎执行此执行计划。
MySQL性能优化显然是要对以上的部分环节或所有环节进行优化,尽量降低各个环节的时间,以提高吞吐率。
对于性能的优化,正确的策略是衡量各个环节的开销,优化开销大的环节,而不是使用网上的一些所谓的参数调优和脚本调优,
因为他们并不是针对你的特定情况而进行的调优,只是一些泛泛的建议,往往帮助不大。
对于客户端来说,发送SQL语句的开销一般很小,如果是响应缓慢的网络,网络延时较高,那么可以考虑使用长连接或连接池等手段进行加速,
或者一次发送多条语句,或者使用存储过程等手段减少网络包的往返次数。
本书主要聚焦于后面的3个步骤,我们需要关注Query Cache是如何加速响应;如何进行查询优化,生成良好的执行计划;
实际查询处理过程中对于I/O、CPU、内存等资源的使用是怎样的。
我们需要尽量确保高效地利用资源,突破资源的限制。
之前的开发篇和运维篇章已经讲述了许多基础知识,这里不再赘述,本章我将主要从系统资源和MySQL参数设置的角度,讲述一些我们需要关注的优化点。

18.2 MySQL的主要参数
本节将列举一些主要的参数,下面将详细介绍各个参数。
1.innodb_buffer_pool_size
一个简单的策略是如果数据库很大,远远超过内存,那么应设置尽可能大的缓冲池(buffer pool)。
如果数据库较小,一般来说,缓冲池的大小设置为稍大于数据库的10%就可以了,大于10%是因为MySQL不只是缓存数据页,还有一些额外的开销。
如果我们使用ps命令检查MySQL实际占用的内存,就会发现实际分配的内存会比我们设定的内存要大一些。
更合适的策略是衡量你的热点数据的大小,如果设置的缓冲区能容纳绝大部分的热点数据,没有产生过多的物理读,那么这个设置就是比较合理的设置。
需要注意的是,不要设置得过大,因为我们需要给操作系统和其他程序预留内存,增加的负荷也会导致更多的内存使用,
我们还可能需要预留内存以用于文件缓存,比如InnoDB的日志文件、MySQL的二进制日志文件等。
要注意32位系统的内存限制,超过了内存限制,可能会导致实例崩溃,系统宕机。
一般计算MySQL需要多少内存比较难,也难以预测,比较可靠的方式是查看目前的生产环境的内存消耗。
MySQL所消耗的内存还和连接数有关,每个连接所消耗的内存总量将依赖于负荷,
如果查询很复杂,那么它会消耗更多的内存,如果只是简单的查询,平时基本上是sleep的状态,那么它实际占用的内存就很少,
所以,对于连接数多的业务,你应该实际观察下,操作系统下实际占用的内存和你设置的InnoDB缓冲池之间的区别,
以衡量是否要调整设置,设置一个更安全一点的参数值,以免连接数暴涨消耗了过多的内存。
图18-1描述了存储的访问层次,对于图18-1中所示的架构,上一层应尽可能缓存下一层的“热点”数据,也就是说,我们需要平衡好内存和磁盘的成本,尽量避免磁盘访问,
对于MySQL来说,内存的主要部分就是InnoDB缓冲池,优化好了InnoDB缓冲池的访问,就成功了一大半,
一般采取的策略是利用空间和时间的局部性,频繁地访问,应该尽可能去访问内存而不是磁盘,
由于数据的访问可能会很复杂,也许1%的缓存未命中(cache miss)需要几十乃至上百GB的内存来避免,而不仅仅是cache miss对应的数据大小,
所以我们还要充分理解数据库的物理设计和逻辑设计,设计出合理的方案,减少cache miss导致的物理读。
需要清楚的一个道理是,一般而言,数据库系统的专用存储系统比操作系统的存储系统高效得多。
InnoDB缓冲池就是如此,而MyISAM仍然是需要OS来缓存数据的,加速访问,所以往往表现得不那么好。

2.innodb_flush_method
这个选项只在Unix系统上有效。
如果这个选项被设置为fdatasync(默认值),那么InnoDB将使用fsync()来刷新数据和日志文件。
如果被设置为O_DSYNC,那么InnoDB将使用O_DSYNC来打开并刷新日志文件,但使用fsync()来刷新数据文件。
如果指定了O_DIRECT(在一些GNU/Linux版本上可用),那么InnoDB将使用O_DIRECT来打开数据文件,并使用fsync()来刷新数据和日志文件。
笔者的建议是设置innodb_flush_method=O_DIRECT,因为这样设置可以避免双重缓冲,让数据库跳过文件系统缓冲直接和设备进行交互,
需要留意的是,如果你的磁盘做了RAID,那么你必须使用带电池的RAID卡。

3.innodb_log_file_size
日志组里每个日志文件的大小。
早期的版本中,在32位的计算机上,日志文件的合并大小必须小于4GB。默认是5MB。
不太好去确定innodb_log_file_size这个参数的大小,早期MySQL版本的配置文件里建议的InnoDB缓冲大小的25%是没有什么道理的,
首先InnoDB缓冲不一定就设置对了,而且InnoDB事务日志一般和你的日志写入量、写入频率有关系,和你的缓冲池大小不存在必然的关系。
MySQL的灾难恢复分为redo和undo两个过程,redo即找到日志文件里记录的已经更改了但是并未写入数据文件的记录,然后应用这些日志。
undo即回滚那些没有提交的操作,undo的时候,数据库已经可以访问了,但是undo的那部分数据还不能更改。
MySQL在切换事务日志的时候,可能会进行一次“check point”的操作,将部分数据写入到磁盘,也就是说,要确保我们的缓存里比日志还旧的数据写入了磁盘。
其他时刻也可能发生“check point”。
如果数据库宕机,MySQL重新启动,那么,它会去事务日志里找到“check point”的标记信息。
在这个“check point”标记之前的数据, 我们可以认为都已经写入到了磁盘。
那么,我们就只需要执行这个“check point”之后的所有操作,应用这些日志到数据库即可。
事务日志不能过小,否则可能会导致性能问题。
事务日志是循环写的,先写第一个日志文件,再写第二个日志文件,然后又会去写第一个日志文件,而在覆盖旧的日志之前,需要确保我们的缓存里比日志还旧的数据已经写入磁盘。
如果事务日志过小,那么磁盘的I/O操作就可能会变得很频繁,因为MySQL必须写入一些脏数据到数据文件中。
一次性刷新大量数据,可能会导致性能下降。
事务日志也不能太大了,因为这个时刻,我们的“check point”会不怎么频繁,那么MySQL的灾难恢复可能需要更长的时间,因为它需要应用更多的日志。
生产环境的恢复速度将取决于应用日志的进度,一个1GB的事务日志,如果要全部应用,有可能需要应用半个小时以上来执行恢复。
我们可以配置事务日志可以写入半个小时到1个小时的日志。这样对于大部分应用已经足够了。
太小了,会频繁切换日志;太大了,可能会导致故障恢复的时间过长。我的经验值是256~512MB。
日志的写入量可以查看变量innodb_os_log_written。我们可以每隔一分钟查看一次,统计每分钟写入的日志量。
下面的命令将会每分钟检查一次日志的写入量。
mysqladmin extended -uroot -pxxxxxxxx -r -i 60 -c 3 |grep "innodb_os_log_written"
如果由上面的命令得知每分钟写入量为10MB,那么我们配置可以连续写45分钟的日志。
默认有2个日志,那么每个日志的大小=10*45/2=225,大约等于 256MB,那么我们可以配置innodb_log_file=256MB。
如果得出的结论是InnoDB日志需要几个GB那么大,那么很可能是不正常的,你要深究为什么会写入这么大的日志,为什么有这么多/大的变更,你可能需要在应用层就规避这种情况。

4.innodb_flush_log_at_trx_commit
当innodb_flush_log_at_trx_commit被设置为0时,日志缓冲将每秒一次被写到日志文件中,并且对日志文件进行磁盘操作的刷新,但是在事务提交时不进行任何操作。
当这个值为1(默认值)时,在每个事务进行提交时,日志缓冲将被写到日志文件,且把对日志文件的变更刷新到磁盘中。
当设置为2时,在每个事务进行提交时,日志缓冲将被写到文件,但不会对日志文件进行到磁盘操作的刷新,对日志文件的刷新每秒发生一次。
我们可以看到,设置为2比设置为0更安全。
我们的生产环境一般推荐设置为innodb_flush_log_at_trx_commit=2,因为它可以兼顾效率和一定的安全性,理想情况下,最多可能丢失1秒的事务。
如果设置为1, 则对于性能的影响会很大,因为每次提交事务,都会伴随着磁盘I/O的操作,需要把数据刷新到磁盘,I/O可能会成为瓶颈,
对于高安全性的数据,在能够满足I/O性能的前提下,可以考虑将其设置为1。

5.sync_binlog
这个参数是设置,每当写了sync_binlog次二进制日志后,把日志实际刷新到磁盘中,默认值是0,不与硬盘同步。
绝大部分公司的生产环境普遍使用的是auto commit模式(自动事务提交),每次写一个语句,就会写一次二进制日志,如果不是自动事务提交,那么每个事务将写入一次二进制日志。
如果设置为1,那么最多丢失1条记录(事务),这是最安全的选择。
生产环境的推荐设置是8~20,这样可以兼顾效率和安全,
如果设置为1,你可能会碰到I/O瓶颈,你需要选用更好的SSD设备,或者使用带电池的RAID卡来缓解I/O瓶颈,优化文件系统也是一个选项,ext4和xfs就比ext3的表现要好得多。
生产实践证明,sync_binlog会对事务吞吐率有比较大的影响。
事务日志、数据文件、二进制日志文件是需要同步的。
数据库可以看作一个巨大的同步机,各个组件之间存在复杂的通信和同步等待,如果sync_binlog操作较慢,那么可能对整个系统的吞吐率造成严重的影响。
有一个相关的参数innodb_support_xa我们需要了解。i
nnodb_support_xa设置为1时,这个变量允许InnoDB支持XA事务,即Distributed(XA)Transactions分布式事务,MySQL部分支持XA事务。
一般互联网公司的业务不需要分布式事务,而且应该尽量避免,那么,是不是要禁用innodb_support_xa呢?
并不是像一些人理解的那样,没有分布式事务,就不要这个特性,MySQL内部会使用XA来协调存储引擎和二进制日志,以确保灾难恢复功能工作正常,所以应该开启它。
MySQL存储引擎各自独立,互不知晓其他引擎的状态,因此,跨引擎的事务可以看作一个分布式的事务,且需要一个第三方来协调它,这个第三方就是MySQL Server。
我们可以把“二进制日志”看作一个“存储引擎”。MySQL Server需要协调二进制日志的写入和InnoDB事务的写入。
生产环境为了安全和复制,必须开启binlog和innodb_support_xa。
如果将sync_binlog参数设置为1,那么存储引擎和二进制日志需要完全同步。
如果二进制日志所在的磁盘存在性能问题,那么也会影响到我们的事务提交。
生产繁忙的系统,有时经常会看到许多commit慢查询,就是因为二进制日志的写入瓶颈导致了InnoDB事务的提交缓慢。

6.innodb_thread_concurrency
InnoDB试着在InnoDB内部保持操作系统线程的数量少于或等于这个参数给出的限制。
官方建议是将其设置为处理器数目加磁盘数之和,对于高并发事务,也许你应该把这个值设置得更大一些。
对于一些资源等待异常的情况,后来的事务会被已经在等待队列中的事务卡住,你可以通过临时增大这个值,让更多的事务并发执行。

7.innodb_max_dirty_pages_pct
这是一个范围从0到100的整数。默认是90。
InnoDB中的主线程试着从缓冲池写数据,使得脏页(没有被写的页面)的百分比不超过这个值。
可以运行如下命令进行修改:SET GLOBAL innodb_max_dirty_pages_pct = value;
生产环境建议将其设置为更小的值:50~75。

8.read_buffer_size
每个线程连续扫描时为扫描的每个表分配的缓冲区的大小(字节)。
如果进行多次连续扫描,可能还需要增加该值,默认值为131072。
只有当查询需要的时候,才分配read_buffer_size指定的全部内存。

9.read_rnd_buffer_size
排序后,按照排序后的顺序读取行时,则通过该缓冲区读取行,以避免搜索硬盘。
将该变量设置为较大的值可以改进ORDER BY的性能。
但是,这是为每个客户端分配的缓冲区,因此你不应该将全局变量设置为较大的值。
相反,只为需要运行大查询的客户端更改会话变量即可。

10.sort_buffer_size
每个排序线程分配的缓冲区的大小。增加该值可以加快ORDER BY或GROUP BY操作。
查询需要排序的时候(如filesort)才分配sort_buffer_size指定的内存,不要设置得过大,否则小的排序也需要大的内存。
在我们确定需要进行大的排序操作的时候,我们可以在会话级别定义大的排序sort_buffer_size。

11.myisam_sort_buffer_size
当运行REPAIR TABLE命令修复表、运行CREATE INDEX命令创建索引或运行ALTER TABLE命令修改表结构时,排序过程中需分配的缓冲区,可以在会话级别进行设置。
12.query_cache_size
为缓存查询结果分配的内存的数量,默认值是0,即禁用查询缓存。
请注意:即使将query_cache_type设置为0也将分配query_cache_size设置的内存。重新定义大小会清除原来缓存的结果集。
对于写操作很频繁的应用,可以禁用它,以消除失效Query Cache的开销,这样可能获得性能上的提升。禁用的办法是设置 query_cache_size=0。
建议生产环境中将其设置为64MB~256MB,不要太大,对于绝大部分业务,256MB就已经足够了。
如果要启用QueryCache,那么需要同时设置query_cache_type=1。

13.join_buffer_size
用于完全连接(当不使用索引的时候使用连接操作)的缓冲区的大小。给不能利用索引的连接使用的。
多表连接需要多个join buffer,所以一个查询可能要用到多个join_buffer_size。
14.max_connections 允许的并行客户端连接数目。
15.max_connect_errors
如果中断与主机的连接超过了该数目,则该主机会阻塞后面的连接。
你可以用FLUSH HOSTS语句解锁锁定的主机。默认值太小了,可以设置在5000以上。
16.skip-name-resolve
不要解析客户端连接的主机名,只使用IP。
如果你要使用该项,那么授权表中的所有Host列值必须为IP号或localhost。
生产环境中必须设置这个参数,否则反向解析缓慢时,会导致MySQL连接缓慢,出现严重的性能问题。

18.3 MySQL内存优化
18.3.1 如何避免使用swap
这里我们仅仅讨论Linux系统下的swap(交换),其他系统,如Solaris,会有一些区别。
简单地说,swap指的是将最近不常使用的内存移动到下一级存储里(硬盘),在需要的时候,再载入到主内存中。
swap空间一般是指我们磁盘上的预先配置的一个分区,也可以是文件,用于将内存中的数据交换到磁盘上。
物理内存和swap空间之和就是我们可用的虚拟内存的大小。
当我们的内存不够了或应用程序消耗了太多的内存,操作系统会把不需要立即使用的数据传输到磁盘,以释放内存空间,
如果以后需要了,再从磁盘上复制回内存,这样一个过程也称为交换(swap out/swap in)。
通过这样一个交换的动作,增加了实际可用的内存,可以提高系统的吞吐能力,但是数据的交换如果太频繁,就会大大增加磁盘的延时时间,可能会导致严重的性能问题。
一般来说,数据库负载,需要尽量避免使用到swap。
我们可以使用free、vmstat、sar等命令查看 swap使用的统计信息。
通过free命令,如果我们看到了一小部分swap空间被使用,那么这一般是正常的,不需要额外关注,我们需要关注的是是否有正在进行的swap in/swap out操作。
一些人建议将swap分区设置为物理内存的大小,对于Linux系统来说,这个建议有一定的意义,为了不浪费过多的硬盘空间,建议使用如下的策略。
如果MEM<2GB,那么SWAP=MEM×2,否则SWAP=MEM+2GB。
对于内存非常大的系统,如32GB、64GB,我们可以使用0.5×内存大小。
MySQL避免使用swap的一些方法如下。
(1)设置memlock
可在参数文件中设置memlock,将MySQL InnoDB buffer锁定到内存,永不使用swap,
但这是有风险的,如果内存不够大,MySQL会被操作系统的OOM机制杀掉。
如果因为物理内存故障导致内存总量变少,那么它可能还会导致系统无法顺利启动,因为MySQL会不断申请内存。
(2)使用大内存页
可以设置MySQL使用Linux系统的大内存页(操作系统和MySQL都需要设置),Linux系统的大内存页是不会被交换出去的。
(3)设置vm.swappiness
可以设置vm.swappiness=0,以减少使用swap的可能。
swappiness参数,它可以在运行时进行调优。
这个参数决定了,将应用程序移动到交换空间而不是移动到正在减少的高速缓存和缓冲区中的可能性,
降低 swappiness可以提高交互式应用程序的响应能力,但是会降低系统的总体吞吐量。
(4)禁用NUMA或调整NUMA
在生产环境中你可能会碰到在没有内存压力的情况下,也发生swap in/swap out的情况,导致不定时出现的性能问题。
尤其是在使用了大的buffer pool size的情况下,这一般是因为使用了NUMA技术,需要考虑禁用NUMA或更改程序分配内存的方式,
numactl命令可以实现这个目的,使用方式为:numactl --interleave all command,例如,/usr/bin/numactl --interleave=all mysqld,详情请参考18.3.2节NUMA。
注意:
不要去禁用swap,并不是所有内核在swap分区被禁用的情况下都能工作得很好,这可能会导致服务异常,
某个服务在禁用swap的时候能够工作得很好,并不代表所有程序都能很好地工作。
而且内存不够的概率更高了,当使用了过多的内存时,程序更容易被操作系统的OOM机制杀掉。
我们需要意识到,swap分区为我们处理问题留了一个缓冲,给我们争取到了处理问题的时间,
所以我们不要把swap分区设置得过小,相对于你所获得的收益,“浪费”一些磁盘空间是值得的。

18.3.2 NUMA
从系统架构来说,目前的主流企业服务器可以分为3类:SMP(Symmetric Multi Processing,对称多处理架构)、
NUMA(Non-Uniform Memory Access,非一致存储访问架构)和MPP(Massive Parallel Processing,海量并行处理架构)。
下面我们来看下SMP和NUMA架构。
1.SMP
如图18-2所示的是一个SMP系统。
在这样的系统中,所有的CPU共享全部资源,如总线、内存和I/O系统等,多CPU之间没有区别,均可平等地访问内存和外部资源。
因为CPU共享相同的物理内存,每个CPU访问内存中的任何地址所需要的时间也是相同的,
因此SMP也被称为一致存储器访问结构(Uniform Memory Access,UMA),尤其是在和NUMA架构对比的时候。
对于SMP服务器而言,每一个共享的环节都可能是瓶颈所在。
由于所有处理器都共享系统总线,所以当处理器的数目增多时,系统总线的竞争冲突也会加大,系统总线成为了性能瓶颈,
所以其扩展性有限,这种架构已经被逐步淘汰,但在CPU内部还有应用,单个CPU的所有核共享访问该CPU的本地内存。

2.NUMA
如图18-3所示的是NUMA系统。
在这种架构中,每颗CPU有自己独立的本地内存,CPU节点之间通过互联模块进行连接,访问本地内存的开销很小,延时比访问远端内存(系统内其他节点的内存)小得多。
这也是非一致存储访问NUMA的由来。
综上所述可以得知,NUMA对内存访问密集型的业务更有好处,NUMA系统提升了内存访问的局部性,从而提高了性能。
关于CPU信息,我们可以查看/proc/cpuinfo。
对于NUMA的访问统计,我们可以使用numastat命令进行检查,也可以查看/sys/devices/system/node/node*/numastat文件。
如图18-4所示,NUMA使用了default策略,这将导致内存分配的不均衡,numastat命令的输出如下。
各项输出的含义如下:
numa_hit:在此节点分配内存而且成功的次数。
numa_miss:由于内存不够,在此节点分配内存失败转而在其他节点分配内存的次数。
numa_foreign:预期在另一个节点分配内存,但最终在此节点分配的次数。
interleave_hit:交错分布策略分配内存成功的次数。
local_node:一个运行在某个节点的进程,在同一个节点分配内存的次数。
other_node:运行在其他节点的进程,在此节点分配内存的次数。
在Linux上NUMA API支持4种内存分配策略,具体如下:
缺省(default):总是在本地节点分配(分配在当前线程运行的节点上)。
绑定(bind):分配到指定节点上。
交织(interleave):在所有节点或指定的节点上交织分配。
优先(preferred):在指定节点上分配,失败后在其他节点上分配。
绑定和优先的区别是,在指定节点上分配失败时(如无足够内存),绑定策略会报告分配失败,而优先策略会尝试在其他节点上进行分配。
强制使用绑定有可能会导致前期的内存短缺,并引起大量换页。
我们可以检查程序具体的内存分配信息,假设pid是mysqld的进程ID,通过查看/proc/pid/numa_maps这个文件,我们可以看到所有mysqld所做的分配操作。
各字段的显示如下:
2aaaaad3e000 default anon=13240527 dirty=13223315
swapcache=3440324 active=13202235 N0=7865429 N1=5375098
各字段及其解析如下:
2aaaaad3e000:内存区域的虚拟地址。实际上可以把这个当作该片内存的唯一ID。
default:这块内存所用的NUMA策略。
anon=number:映射的匿名页面的数量。
dirty=number:由于被修改而被认为是脏页的数量。
swapcache=number:被交换出去,但是由于被交换出去,所以没有被修改的页面的数量。这些页面可以在需要的时候被释放,但是此刻它们仍然在内存中。
active=number:“激活列表”中的页面的数量。
N0=numberand N1=number:节点0和节点1上各自分配的页面的数量。
我们可以使用numactl命令显示可用的节点。
numactl --hardware
available: 2 nodes (0-1)
node 0 size: 64570 MB
node 0 free: 8556 MB
node 1 size: 64640 MB
node 1 free: 1982 MB
node distances:
node 0 1
0: 10 20
1: 20 10
如上命令告诉我们,系统有两个CPU节点:node0、node1。每个节点分配了64GB的内存。
distance衡量了访问内存的成本,系统认为访问本地节点内存的成本是10,访问远端内存的成本是20。
NUMA架构存在的一个问题是:
对于NUMA架构,Linux默认的内存分配方案是优先在请求线程当前所处的CPU的本地内存上尝试分配空间,一般是node0。
如果内存不够,系统就会把node0上已经分配的内存交换出去,以释放部分node0的内存,尽管node1上还有剩余的内存,但是系统不会选择向node1去申请内存。
显然,swap的成本远比访问远端内存的成本高,这将导致不定时地出现性能问题。
解决办法具体如下:
1)关闭NUMA。
如果是单机单实例,则建议关闭NUMA,关闭的方法有如下两种。
硬件层,在BIOS中设置关闭。
OS内核,启动时设置numa=off。
可用类似如下的方式进行修改。
[root@db1000 ~]# cat /proc/cmdline
ro root=LABEL=/ rhgb quiet
vi /etc/grub.conf
kernel /vmlinuz-2.6.18-164.el5 ro root=LABEL=/ rhgb quiet numa=off
确认NUMA是否关闭,检查numactl--show的输出信息。
[root@db1000 home]# /usr/bin/numactl --show
policy: default
preferred node: current
physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
cpubind: 0
nodebind: 0
membind: 0
关闭之前这个命令会显示多个节点的信息,输出结果如下所示。
policy: default
preferred node: current
physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
cpubind: 0 1
nodebind: 0 1
membind: 0 1
而关闭之后则只会显示一个节点的信息,nodebind项只有一个值0。我们也可以检查启动信息dmesg | grep-i numa。
2)使用numactl命令将内存分配策略修改为interleave(交叉)或绑定CPU
可通过修改单实例启动脚本mysql.server或多实例启动脚本mysqld_multi,例如,修改msyqld_multi脚本(MySQL 5.1)320行
将$com="$mysqld"更改为$com="/usr/bin/numactl--interleaveall$mysqld";
也可以修改启动脚本和参数,绑定MySQL的各个实例到固定的CPU节点,笔者更推荐使用这种方式。
下面的例子,在节点0的CPU上运行名为program的程序,并且只在节点0和1上分配内存。
numactl --cpubind=0 --membind=0,1 program
下面的例子,在节点1上运行$MYSQLD程序,只在节点内分配内存。
numactl --cpunodebind=1 --localalloc $MYSQLD
3)设置参数memlock。
MySQL进行初始化启动的时候,就已经预先把InnoDB缓冲池的内存锁住了,即设置参数memlock等于1,
设置这个参数,也有一定的风险,如果内存不够,可能会导致系统启动不正常,因为MySQL Server会不断申请内存。
4)使用大内存页。
还有一些其他的辅助手段。
配置vm.zone_reclaim_mode=0使得内存不足时倾向于向其他节点申请内存。
echo-15>/proc/<pid_of_mysqld>/oom_adj,将MySQL进程被OOM_killer强制kill的可能性调低。

18.4 MySQL CPU优化
系统的性能一般取决于系统所有组件中最弱的短板,CPU、内存、I/O、网络都可能会成为瓶颈所在。
现实中,一般是CPU瓶颈或I/O瓶颈,I/O瓶颈也可能是由于内存不够所导致的。
CPU的瓶颈一般是大量运算和内存读取所导致的,比如加密操作、索引范围查找、全表扫描等。
生产环境中出现CPU瓶颈往往是因为大量的索引范围查找或连接了太多表。
I/O瓶颈往往是因为内存已经不能保存住数据库的热数据,因此读写操作必须访问实际的物理磁盘,从而导致过多的物理读。
实际生产环境中,更多的会碰到I/O瓶颈,而不是CPU瓶颈,你可以使用top或mpstat判断数据库服务器是否存在CPU瓶颈。
由于MySQL在多CPU主机上的扩展性有限,不能充分利用多CPU的主机,所以生产中可能会在同一个主机上部署多个实例。
有时我们会绑定MySQL实例到某个CPU节点上。
如果想要优化性能,那么我们更倾向于选取速度更快的CPU,而不是增加CPU。
从理论上来说,如果操作比较集中于一些资源对象,瓶颈多是因为锁和队列等待,那么这个时候应该选取更强劲的CPU。
而如果操作分散于诸多不相干的资源上,那么并发程度可以更高,可以倾向于使用更多的CPU,但能否使用更多的CPU、 并发多线程执行操作,还要受制于存储引擎的设计。
就目前来说,InnoDB的扩展性还是不佳。
下面我们来看看CPU的高级特性。
PC Server上有一种节能模式,一般是处于关闭的状态,这种电源管理技术可以在负载低的时候,调低CPU的时钟速度,降低能耗,
但这种技术并不能和突发的负荷协作得很好,有时会来不及调整时钟以响应突然的高并发流量。
还有另外一种电源管理技术,它通过分析当前CPU的负载情况,智能地完全关闭一些用不上的核心,而把能源留给正在使用的核心,并使它们的运行频率更高,从而进一步提升性能。
相反,需要多个核心时,应动态开启相应的核心,智能调整频率。
这样,就可以在不影响CPU的TDP(热功耗设计)的情况下,把核心工作频率调得更高。
这种加速技术可能会破坏我们的性能规划,因为系统的行为并不是“线性”的了。

18.5 MySQL I/O优化
18.5.1 概述
我们的生产环境一般是OLTP应用,I/O瓶颈一般来自于随机读写,随机读的消除和写的缓解主要靠缓存,所以我们要确保MySQL的缓冲区能够缓存大部分的热点数据。
当然,也没有必要缓存所有的热点数据,可以接受一定的缓存未命中(cache miss)。
注意,传统的一个调优方法是基于命中率进行调优,更靠谱的方案是基于缓存未命中的情况进行调优,虽然有时命中率很高了,
但只要缓存未命中次数达到一定的频率,你就会碰到I/O瓶颈。
数据库引擎比操作系统或RAID更了解数据,能够更高效地访问数据,文件系统和RAID层面的预读要关掉,因为它们帮不上什么忙,应该交给数据库以更智能地判断数据的读取。
内存的随机读写速度比硬盘的随机读写速度快了几个数量级,所以如果有I/O的性能问题,那么添加内存会是最简便的方案。
数据库缓冲是调优的重点,我们需要确保数据库缓冲能够缓存大部分的热点数据,
理论上来说,如果数据库缓冲已经不够了,那么文件系统或RAID缓冲也没有什么用,因为它们要小得多,且不了解数据,
缓存应该考虑在更接近用户的地方进行优化,由于应用比数据库更了解数据,
所以对于高并发的业务,客户端/应用程序的本地内存或缓存服务(如 Memcached)会比MySQL更有效率,提供更好的扩展性。
顺序读写无论是在内存还是在磁盘中,都比随机读写更快。一般是不用考虑特殊的缓存策略。
对于机械硬盘,由于磁盘的工作原理,顺序读写的速度比随机读写速度快得多,我们需要着重优化随机读写,尽量减少随机读,以提高吞吐。
对于SSD,虽然顺序读写也很快,相对而言,随机读写并没有差太多,而且优化随机读写也不是那么迫切,
但是还是有必要优化大量随机读写的SQL,因为随着访问量的上升,贡献大量随机读写的SQL,将会很快导致整个系统出现瓶颈。
随机读写往往来自质量不高的SQL,这些SQL往往是因为索引策略不佳或表连接过多,从应用层优化或进行索引优化,会更有效果,也更具可行性。
文件碎片也可能会导致更多的随机I/O,尽管数据库是顺序访问数据的,但是I/O却不是顺序的,
MySQL自身并没有提供工具来检查数据文件是否碎片很多,我们也不建议频繁地进行表的重建和优化,
但是在进行了大批量数据操作之后,比如大量删除数据之后,在不影响服务的前提下,优化一下表(OPTIMIZE TABLE)还是可取的。
对于OLAP应用,I/O调优和OLTP有些相似,也是要先考虑应用调优和SQL调优,尽量减少I/O操作,如果必须要执行大量的I/O操作,那么应该尽量将其转换为顺序读写。

18.5.2 选择合适的I/O大小
一般来说,MySQL的块大小是操作系统块的整数块,你可以通过命令getconf PAGESIZE来检查操作系统的块大小,更大的I/O大小,意味着更大的吞吐,
尤其是对于传统的机械硬盘,一次更大的I/O,意味着不需要进行多次I/O,可以减少寻道的时间。
对于数据库,由于往往是一些随机记录的检索,因此并不需要一次性读取大量的记录,所以一次I/O不需要太大。
许多人把默认的数据库的块大小调整为8KB,以获得更高的性能。

18.5.3 日志缓冲如何刷新到磁盘
对于数据库的I/O性能调整,需要在性能和数据的安全性上求得平衡。
如果生产环境有严重的I/O性能问题,那么它往往是由程序的不良设计造成的。
一个应用级别的SQL调整,可能就能解决了问题。而从操作系统的I/O层面可能就会无解。
InnoDB使用了数据缓冲和事务日志,数据缓冲大小、日志大小、日志缓冲,InnoDB如何刷新数据和缓冲,都会对性能产生影响。
InnoDB的脏数据并不是马上写入数据缓冲(数据文件)的,而是会先写日志缓冲(日志文件),将脏数据暂时保留在数据缓冲区中,
这是一种常见的数据库持久化的技术,这些日志记录了数据变更,可以用来做故障恢复。
数据的读写一般是随机读写,而日志的写入,是顺序写入,日志写入的要效率高得多,通过延缓数据的持久化,可以将数据更高效率地写入到磁盘中。
InnoDB在缓冲区满的情况下会将日志缓冲区刷新到磁盘,一般不需要调整日志缓冲区的大小(innodb_log_buffer_size),除非有很多有BLOB字段的记 录,
innodb_log_buffer_size的大小默认是1MB,建议是1~8MB。
我们通过配置innodb_flush_log_at_trx来控制如何将日志缓冲刷新到磁盘,innodb_flush_log_at_trx的值可设置为0、1、2,默认为1。
设置为1的情况下,每个事务提交都要写入磁盘,这是最安全的做法,而设置为其他值时,可能会丢失事务。
一般机械磁盘受磁盘旋转和寻道的限制,最多只能达到几百次IO/每秒,所以这个设置会严重降低事务并发,
如果你数据库的安全性要求很高,那么设置innodb_flush_log_at_trx为1,这时你可能要把日志文件放在更好的磁盘设备上,如SSD设备或带电池的磁盘阵列上。
如果将innodb_flush_log_at_trx设置为2,那么每次事务提交时会将日志缓冲写到操作系统缓存中,但不实际刷新到磁盘中,每秒再刷新日志缓冲到磁盘中,
这样做可以减轻I/O负荷,如果不存在极端的情况,理论上宕机最多只会丢失最近1秒的事务。
如果innodb_flush_log_at_trx设置为0,那么每秒都会将日志缓冲写到日志文件中,且将日志文件刷新到磁盘,但在事务提交的时候并不会将日志缓冲写到日志文件中,
一般不建议将其设置为0,在设为0时,如果mysqld进程崩溃,那么停留在日志缓冲区的数据将被丢失,因此你会丢失事务。
当为2时,进程虽然会崩溃,但每次事务提交,都写入了日志,只是暂时没有被刷新到磁盘,所以不会丢失事务,
因为操作系统负责把这些数据写入文件,当然,如果宕机了,那么你的数据还是会被丢失的。
设置为1将会更安全,但每次事务提交时都会伴随磁盘I/O,受机械硬盘的寻道和旋转延迟限制,可能会成为系统瓶颈,在确认可以满足I/O性能的前提下,可将 其设置为1。
建议在生产环境中将innodb_flush_log_at_trx设置为2。

18.5.4 事务日志
如果日志文件里记录的相关数据并未写入数据文件,那么这个日志文件是不能被覆盖的。
日志文件如果过小,那么可能会过多地检查点操作,增加I/O操作,而如果日志文件过大,则会增加实例崩溃的恢复时间。
一般建议在生产系统中将其大小设置为256~512MB,以平衡恢复时间和性能。
你也可以定量分析实际需要的事务日志大小,
方法是衡量一段时间(如0.5~2个小时)内写入的日志记录(innodb_os_log_written)的大小,你所分配的多个日志的总计大小应能确保保留此段时间的日志。
事务日志一般没有必要和数据文件分离,除非你有许多(20+)盘。如果只有几个盘,却专门使用独立的盘来存放二进制日志、事务日志,则有些浪费。
在有足够多的盘的情况下,磁盘I/O分离才有意义,不然成本就会太高了,且无法充分利用有限的资源。
建议将日志文件和数据文件放在同一个盘/卷的另一个原因是日志文件和数据文件放在一起,可以做LVM快照。

18.5.5 二进制日志
如果分离二进制日志和数据文件,可能会带来一点性能上的提升,但分离的主要目的不是性能,而是为了日志的安全。
如果没有带电池的RAID卡,那么分离就是有必要的。
如果有带电池的RAID卡,那么一般情况下就没有必要进行分离,即使有许多顺序日志写入,RAID卡也可以合并这些操作,最终只会看到不多的一些顺序I/O。
如果将二进制日志存放在独立的盘上,那么即使我们的数据文件损坏了,我们也可以利用备份和日志做时间点恢复。

18.5.6 InnoDB如何打开和刷新数据、日志文件
InnoDB有几种方式和文件系统进行交互,默认是以fdatasync的方式读写文件的,生产环境中推荐设置为O_DIRECT。
以下将简单介绍这两种方式:
fdatasync:默认InnoDB使用fsync()刷新数据和日志。使用默认设置没有什么问题,但也许发挥不了你硬件的最高性能。
O_DIRECT:对于数据文件,MySQL Server也是调用fsync()刷新文件到磁盘的,但是不使用操作系统的缓存和预读机制,以避免双重缓冲,
如果你有带电池的RAID卡,则可以配合这个选项一起使用。
注意:RAID卡需要开启写缓存,默认策略是Write Back。

18.5.7 InnoDB共享表空间和独立表空间
InnoDB表空间不仅仅可以存储表和索引数据,还有UNDO(可以理解为数据前像)、insert buffer、double write buffer等其他内部数据结构。
目前有两种表空间的管理方式,共享表空间和独立表空间。
默认的是共享表空间的管理方式,InnoDB表空间的管理比较简单,并没有Oracle那样丰富的特性。
如果使用默认的共享表空间的话,数据和索引就是放在一起的,所有数据都存储在innodb_data_file_path参数设置的数据文件里。
我们可以通过innodb_data_file_path设置多个InnoDB数据文件,一般将最后一个文件设置为可自动扩展的,以减少数据文件的大小,你也可以将数据文件分离到不同的磁盘中。
由于数据文件不能收缩,所以使用共享表空间存在的一个严重的问题是空间的释放。
如果你增加了数据文件,那么你还需要重启数据库实例,这些都加大了管理开销。
当自动扩展的数据文件被填满之时,每次扩展默认为8MB,我们可以调整为更大的值,如32MB、64MB,这个选项可以在运行时作为全局系统变量而改变。
因为每次分配小空间,代价都会比较大,所以预分配一个较大的文件是有道理的。
另一种方式是独立表空间,我们需要将innodb_file_per_table设置为1。
这个选项可以将每个InnoDB表和它的索引存储在它自己的文件中,由于每个表都有自己的表空间,所以又称为独立表空间。
UNDO、各种数据字典等其他数据仍然存储在共享表空间内。
你可以通过操作系统命令比较直观地看到数据大小,也方便删除表释放空间,
所以许多有经验的DBA都设置MySQL实例为独立表空间, 从而可以更方便地释放空间和减少文件系统的I/O争用。
InnoDB也支持在裸设备上存储,通过这种方式,你也许可以得到少许的性能提升,但由于管理难度比较大,因此很少有人使用这种方式管理数据库文件。

18.5.8 UNDO暴涨的可能性
有时我们的共享表空间会暴涨,其实是由于UNDO空间发生了暴涨,UNDO空间暴涨的原因主要有如下两点。
存在长时间未提交的事务,因为未提交的事务需要使用发布查询时刻的UNDO的数据,所以共享表空间内的这部分UNDO数据不能被清除,将会积累得越来越多。
也许是负载太高,清理线程还来不及清除UNDO,这种情况下,性能将会急剧下降。
18.5.9 关于double write buffer
InnoDB可使用double write buffer来确保数据安全,以避免块损坏。
double write buffer是表空间的一个特殊的区域,可顺序写入。
当InnoDB从缓冲池刷新数据到磁盘时,它首先会写入double write buffer,然后写入实际的数据文件。
InnoDB检查每个页块的校验和,以判断是否坏块,
如果写入double write buffer的是坏块,那么显然还没有写入实际数据文件,那么就用实际数据文件的块来恢复double write buffer。
如果写入了double write buffer,但是数据文件写的是坏块,那么就用double write buffer的块来重写数据文件,这也是MySQL灾难恢复的一个基本步骤。
如果操作系统本身支持写入安全,不会导致坏块,那么我们可以禁用这个特性。

18.5.10 数据库文件分类
可以考虑把二进制日志文件、InnoDB数据文件的物理文件分布到不同的磁盘中,
这样做主要考虑的是把顺序I/O和随机I/O进行分离。
你也可以把顺序I/O放到机械硬盘上,把随机I/O放到SSD上,如果有带电池的RAID卡且开启了写缓存,那么顺序I/O的操作一般是很快的。
具体如何放置文件,还需要综合考虑性能、成本和维护性等多个因素。
笔者的做法是,如果没有性能问题,就把所有文件都放在一个盘上,这样维护起来将会更方便。
如下是按照顺序I/O和随机I/O对数据库文件做了下分类。
(1)随机I/O
表数据文件(*.ibd):启用了独立表空间(innodb_file_per_table=1)。
UNDO区域(ibdata):UNDO里存储了数据前像,MySQL为了满足MVCC,需要读取存储在UNDO里的前像数据,这将导致随机读,
如果你要运行一个需要很长时间的事务或一个时间很长的查询,那么可能会导致很多随机读,因为长事务或未提交的事务将有更多的可能性读取前像数据。
(2)顺序I/O
事务日志(ib_logfile*)。
二进制日志(binlog.xxxxxxx)。
double write buffer(ibdata)。
insert buffer(ibdata)。
慢查询日志、错误日志、通用日志等。

18.5.11 何时运行OPTIMIZE TABLE
有些人会建议定时运行一些OPTIMIZE TABLE之类的命令,以优化性能,这点与Oracle类似,也总会有些人建议你定时运行重建索引的操作。
一般来说,除非在进行了大量会影响数据分布的操作之后,比如删除了大量的数据、导入数据等,一般情况下是不需要重整表的。
定时地运行OPTIMIZE TABLE命令不现实,还可能会导致生产系统的不可用。
OPTIMIZED TABLE命令会优化InnoDB主键的物理组织,使之有序、紧凑,但是其他索引仍然会和以前一样未被优化。
哪一个索引对性能更重要呢?也许从来没有基于主键的查询条件。
其实,数据、索引的分布也是需要一个过程的,随着时间的演变,自然而然会达到一个平衡。
强制优化之后,过一段时间,它又会回到原来的不好不坏的状态。
所以MySQL 5.1的官方文档中才会建议:如果您已经删除了表的一大部分,
或者如果您已经对含有可变长度行的表(含有VARCHAR、BLOB或TEXT列的表)进行了很多更改,则应使用OPTIMIZE TABLE。

18.5.12 MySQL磁盘空间
磁盘空间如果出现瓶颈,往往是因为数据库规划失误,前期没有进行足够的调研,也有小部分原因是因为业务发展得太快了,数据呈现爆炸式增长。
大部分业务,一般预留1到2年的数据增长空间就已经足够了,如果你预计数据未来会有一个海量的规模,那么提前进行分库分表则是有必要考虑的。
你需要尽可能地了解占据数据库总体空间比重较大的一些数据,清楚哪些表是可以被清理或归档的,
许多情况下,我们并不需要这么多的数据,或者许多数据是不需要保留很久的,是完全可以清除的,
你越了解数据,就越能够和研发团队一起制定合理的数据保留策略。
在系统上线之前,你就需要制订好将数据进行批量清理和归档的方案,可以使用定期任务删除数据,你也可以利用分区表删除旧的历史数据。
当数据库实例的数据变得很大,单台机器已经很难保存所有数据的时候,你可以考虑将实例、数据库分离到其他的机器。
由于处理器和高速缓存存储器速度的提升超过磁盘存储设备速度的提升,许多业务将受磁盘空间所累。
一些业务拥有海量数据,但大部分都是冷数据,你又不能进行简单的归档处理,这个时候数据压缩就派上用场了。
目前的数据库主机,CPU资源往往过剩,数据压缩可以减少数据库的大小,减少I/O和提高吞吐量,而压缩仅仅只会消耗部分CPU成本。
MySQL 5.5开始提供了InnoDB表压缩的功能,在MySQL 5.6中InnoDB表压缩的功能得到了进一步的完善,真正可以用于生产环境了。
对于真正海量高并发的应用,内存为王,你应该在内存中尽可能地保证热点数据和索引,更多的索引和数据可以放在一个内存块中,
那么查询的响应也将更快,表是压缩的也意味着你需要更少的存储空间和更小、更少的I/O操作。
对于MySQL 5.5、5.6,你需要配置为独立的表空间才能使用表的压缩功能,对于MySQL 5.7,你也可以不使用独立表空间。
由于固态硬盘一般比传统机械硬盘要小,且成本更高,所以压缩对固态硬盘尤其有意义。
不同的内容压缩率将会不一样,如果你需要将表修改为压缩表,那么你需要在更改之前进行测试验证,以确认压缩率和转换表的时间,
一般来说,设置 KEY_BLOCK_SIZE为8KB可以适用于大部分情况,8KB意味着将每个页压缩为8KB,你也可以将标准的16KB页压缩为4KB或2KB,
但可能会导致过多的性能损耗而压缩率并不能得到提升。

小结:
本章讲述了调优将会涉及的MySQL参数及在使用MySQL的过程中,内存、CPU、I/O的优化。
笔者不推荐读者对生产环境的参数做大的调整,也不推荐使用各种不常用的手段去优化硬件资源的利用率,压榨硬件的性能。
保持一个维护性更好的数据库,使用通用的参数,可以让工作变得更简单些,笔者认为这才是更重要的。
但是,作为DBA,一定要熟悉各种调优的手段,因为你可能会碰到极端的场景。

免责声明:文章转载自《18-MySQL DBA笔记-MySQL Server调优》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇CF55D Beautiful numberswinform中如何在TextBox中只能输入数字(可以带小数点)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

全网数据库大全

参考资源 数据库种类发展史和大数据下的数据库 NoSQL百度百科 NewSQL百度百科 分布式NewSQL对比——heaventouch大佬的博客 Google Spanner论文翻译——林子雨 人道浮沉的博客 列式数据库与行式数据库 Oracle数据库、MySQL数据库、SQL Server数据库 Oracle、Mysql、SQL Server比较 Mo...

0115percona-toolkit安装教程

一、percona-toolkit简介percona-toolkit是一组高级命令行工具的集合,用来执行各种通过手工执行非常复杂和麻烦的mysql和系统任务,这些任务包括: 检查master和slave数据的一致性 有效地对记录进行归档 查找重复的索引 对服务器信息进行汇总 分析来自日志和tcpdump的查询 当系统出问题的时候收集重要的系统信息 pe...

MySQL之IDE工具介绍及数据备份

一、IDE工具介绍 生产环境还是推荐使用mysql命令行,但为了方便我们测试,可以使用IDE工具 下载链接:https://pan.baidu.com/s/1bpo5mqj 二、MySQL数据备份 #1. 物理备份: 直接复制数据库文件,适用于大型数据库环境( 如数据文件、控制文件、归档日志文件等)。但不能恢复到异构系统中如Windows。 #2. 逻辑...

ext3,ext4,xfs和btrfs文件系统性能对比

应为原文:http://www.ilsistemista.net/index.php/linux-a-unix/6-linux-filesystems-benchmarked-ext3-vs-ext4-vs-xfs-vs-btrfs.html?start=1 还有一篇相关介绍:http://www.phoronix.com/scan.php?page=ar...

MySQL锁定状态查看命令

1 show processlist; SHOW PROCESSLIST显示哪些线程正在运行。您也可以使用mysqladmin processlist语句得到此信息。如果您有SUPER权限,您可以看到所有线程。否则,您只能看到您自己的线程(也就是,与您正在使用的MySQL账户相关的线程)。如果有线程在update或者insert 某个表,此时进程的stat...

mysql 基本使用教程(源于网络)

3.1. 连接与断开服务器 3.2. 输入查询 3.3. 创建并使用数据库 3.3.1. 创建并选择数据库 3.3.2. 创建表 3.3.3. 将数据装入表中 3.3.4. 从表检索信息 3.4. 获得数据库和表的信息 3.5. 在批处理模式下使用mysql 3.6. 常用查询的例子 3.6.1. 列的最大值 3.6.2. 拥有某个列的最大值的行...