你所不知道的MySQL数据库性能优化方案
MySQL数据库中最重要的一个概念就是数据库引擎,不同的数据库引擎的工作原理存在很大差异最终造成MySQL数据库服务的性能差异。
一、MySQL数据库引擎的选择
1.1 概述
MySQL数据库中最重要的一个概念就是数据库引擎,不同的数据库引擎的工作原理存在很大差异最终造成MySQL数据库服务的性能差异。
例如如果数据库引擎需要支持事务,就必须满足事务的基本特性——AICD特性(AICD:原子性、隔离性、一致性和永久性。属于基础知识所以不在这里赘述),那么自然就需要一定处理机制来实现这些特性。这样做的现实效果就是导致写入同样数据量的情况下,支持事务的数据库引擎比不支持事务的数据库引擎耗费更多的时间。
这里我们首先为读者列举MySQL数据库社区版中支持的数据库引擎(部分):
1.2 引擎
- MEMORY:
MEMORY存储引擎将表的数据完全存放在内存中。在MySQL数据库的历史版本中和该数据库引擎类似的其它引擎是HEAP,后者曾是MySQL数据库中访问速度最快的数据库引擎。
但由于这两种数据库引擎完全工作在内存中,所以如果MySQL或者服务器重新启动,数据库引擎中保存的数据将会丢失。
- BLACKHOLE:
中文名“黑洞”,使用BLACKHOLE数据库引擎的数据表不存储任何数据,只根据数据库操作过程记录二进制日志。它的主要作用是作为MySQL主从复制的中继器,并且可以在其上面添加业务过滤机制。
- MyISAM:
MyISAM数据库引擎是MySQL数据库默认的数据库引擎。MyISAM使用一种表格锁定的机制,来优化多个并发的读写操作(实际上就是使用的一种避免数据脏读的机制)。
但是这种机制对存储空间的使用有一定的浪费。MyISAM还有一些有用的扩展,例如用来修复数据库文件的MYISAMCHK工具和用来恢复浪费空间的MYISAMPACK工具。本文所介绍的MySQL数据库相关技术将不涉及到这种数据库引擎。
- InnoDB:
InnoDB数据库引擎是在各种版本的MySQL数据库中使用最广泛的一种数据库引擎,本文后续的介绍中如果没有特别说明都默认是在说InnoDB数据库引擎。InnoDB数据库引擎使用日志机制提供事务的支持
二、基本I/O性
2.1 概述
要了解MySQL数据库中的性能问题,就首先要搞清楚在客户端向MySQL数据库提交一个事务操作时后者到底做了些什么事情,以及主要是怎么做的。本节所描述的工作过程主要围绕InnoDB数据库引擎进行:
上图中笔者只画出了InnoDB数据库引擎在insert/update一个事务的过程中所涉及的重要工作区域,InnoDB的实际工作细节要比上图所示的步骤复杂得多。
上文已经说到InnoDB数据库引擎是一个支持事务的数据库引擎,那么如何解决异常崩溃情况下的数据一致性问题就是它的设计中最重要的任务之一。
InnoDB数据库引擎采用日志来解决这个问题,请注意这里说的InnoDB数据库引擎日志,并不是MySQL数据库全局的二进制日志。InnoDB数据库引擎日志还有另外一个名字:重做日志(redo log),这是因为这部分日志主要的作用就是在数据库异常崩溃并重启后进行InnoDB引擎中数据的恢复。
为了提高MySQL数据库的性能,InnoDB数据库引擎的数据操作过程基本上都在内存中完成,然后通过一定的策略(后文会详细介绍)将InnoDB Log Buffer内存区域中的日志数据同步到磁盘上的InnoDB File Log Group区域。
InnoDB File Log Group区域主要用于存储InnoDB数据库引擎的日志文件,它由多个大小相同的日志文件构成并且这些文件都采用顺序读写。innodb_log_file_size参数将决定每个文件的大小,而innodb_log_files_in_group参数将决定整个日志组中有多少个日志文件。
当MySQL数据库完成初始化过程后这些日志文件将会按照参数的设置值,在磁盘上预占一个连续的磁盘空间。这样做的现象就是虽然数据库中还没有任何数据,但是日志文件的总大小就已经是 innodb_log_file_size * innodb_log_files_in_group所得到的数值了:
InnoDB数据库引擎 日志文件示例
…
total 1.0G
-rw-rw---- 1 mysql mysql 500M May 4 06:09 ib_logfile0
-rw-rw---- 1 mysql mysql 500M May 4 06:09 ib_logfile1
…
这样做的目的是保证了后续同步日志数据的操作都是顺序写,而不是随机写。当日志数据写到最后一个文件的末尾时,下一条日志数据又会重新从第一个日志文件的开始位置进行写入。
2.2 I/O性能问题的产生
InnoDB Log Buffer内存空间中的四个标识指针是InnoDB数据库引擎日志处理部分最重要元素,它们分别是:Log sequence、Log flushed、Pages flushed和Last checkpoint,这四个标识涉及到InnoDB在崩溃重启时不同的数据恢复策略,以及I/O性能优化中的关键原理。
这四个标识实际上是四个数值它们共享一个数值池(名叫LSN,日志序列号,其总长度是64位无符号整数),代表当前InnoDB对事务操作的处理状态。并且它们数值有以下特点:
Log sequence >= Log flushed >= Pages flushed >= Last checkpoint
- I/O性能问题(1)
- I/O性能问题(2)
1 innodb_flush_log_at_trx_commit = 0时,InnoDB将按照1秒钟为单位向磁盘写入这个阶段所有已完成的事务日志信息。这里的写入成功并不是说写入到Linux操作系统的Page Cache中就算成功,而是需要等待操作系统真正写到了物理磁盘上的通知(具体请参见之前讲解文件系统的文章)。
这意味 着即使InnoDB Buffer Pool中的数据操作是成功的,但是一旦数据库系统异常崩溃,那么业务系统将会丢失前1秒内写入的数据:因为没有磁盘介质上的日志就无法在异常重启后恢复数据信息。
2 innodb_flush_log_at_trx_commit = 1时,InnoDB按照完成一个日志操作就向磁盘写入事务日志信息的方式来工作(执行一个事务就写入一个事务日志)。同样,这里的写入成功同样是要等待操作系统返回真正写入了物理磁盘的通知。
3 innodb_flush_log_at_trx_commit = 2时,InnoDB按照完成一个日志操作就向磁盘写入日志信息的方式来工作。但是,这种工作模式下InnoDB不会等待操作系统返回物理磁盘上写入成功的通知,就会继续工作。
实际上这个时候,数据一般还存在于Linux操作系统的cache memory区块中,所以这种模式下最好使用带有日志功能的文件系统,并且确认开启了文件系统的日志功能。
InnoDB数据库引擎在这一步骤的最后一个动作是更改Log flushed标识指针值为当前最后完成刷新动作的事务日志LSN值。实际上执行完这个步骤,一个事务处理操作才算真正成功。
- I/O性能问题(3)
- I/O性能问题(4)
1 当代表事务的LSN数值在Log sequence——Log flushed范围内时(不包括Log flushed),说明在数据库崩溃时内存中的事务并没有处理完,这部分事务操作将在恢复时被丢弃。
2 当代表事务的LSN数值在Log flushed——Pages flushed范围内时(不包括Pages flushed),说明数据库崩溃时磁盘上已经拥有这些事务完整的日志记录。
InnoDB数据库引擎将读取这些日志数据,并继续执行下去,直到代表这些事务的LSN值被标记为Checkpoint(或者小于Checkpoint标识的LSN值)。这里要注意,在数据库崩溃时处于这个范围内的某些事务可能已经完成了一部分的数据同步动作,但是肯定是不完整的。所以即使是这样的事务也要重新进行磁盘同步,才能保证数据的一致性。
3 实际上在MySQL version 5.5的早期版本,InnoDB数据库引擎中只有三个标识:Log sequence、Log flushed和Checkpoint。也就是说当脏页成功同步到磁盘后,就会直接更新Checkpoint标识的LSN值。
后续版本的MySQL数据库增加了Pages flushed标识点,这样做的目的是保证Checkpoint和Pages flush的更新可以拥有独立的周期,从而降低其带来的性能消耗
2.3 I/O 性能问题要点
- Log flush和Pages flush
1 例如在读取Page时,采用“预读”思路将目标Page所临近的Page一起读取出来;在写入Page时将目标Page所临近的Page一起写入(“临近写”)。“预读”策略可以通过innodb_read_ahead_threshold参数进行设置,并通过read thread完成,另外 innodb_flush_neighors参数可以控制是否开启“临近写”策略。
总的来说“预读”/“临近写”在默认情况下都是开启的,但“预读”/“临近写”思路本身就需要一定的准确性,低命中率的“预读”反而会降低InnoDB的I/O性能。
还有一种“随机预读”,它在MySQL version 5.6版本中默认就是关闭的,并且在随后的版本中将会慢慢废除,所以这里就不再介绍了。
2 例如将向磁盘提交Page的动作设计为周期性且批量进行,并且始终保持InnoDB Buffer Pool内存区域的脏页(Dirty Page)在一定的比例,这些策略主要由innodb_io_capacity、innodb_max_dirty_pages_pct、innodb_io_capacity_max等参数控制。
3 例如通过调整Innodb_Buffer_Pool_size参数获得更大的InnoDB Buffer Pool内存区域,存储更多的Page。实际上Innodb Buffer Pool区域不仅包括我们已经介绍的Page Cache数据部分,还包括其它的数据区块。
例如为了快速定位B+树索引的Hash Index结构。调整Innodb_Buffer_Pool_size参数将会使这些数据区域都享受到内存容量带来的优势——至少不会频繁地发生内容空间的强制清理。
- 基础硬件条件
2.4 突破解决I/O性能问题
2.4.1 概述
为了解决上一节中提到的I/O性能问题,本文这里基于之前介绍的块存储方案的知识,列出这个问题的几种解决方案。
除了根据I/O吞吐量要求对MySQL数据库特别是InnoDB引擎的配置参数进行更改以外,本文提到的硬件层解决方法所需要花费的资金和能够得到的I/O性能和扩展能力基本上成正比。
2.4.2 对MySQL中的I/O相关参数进行调整
上一节我们已经对InnoDB数据库引擎(以下简称InnoDB引擎)进行事务操作时的I/O过程进行了简单说明,主要介绍了Log flush和Pages flush两个过程。
如果我们需要挖掘正式生产环境上MySQL数据库服务的性能潜力,那么对MySQL数据库服务中的默认参数进行更改就是必须要做的事情。在进行配置修改之前我们先来看看如何查看当前MySQL数据库特别是InnoDB引擎的工作状态:
通过执行以下命令,我们可以查看当前InnoDB引擎的工作状态
show engine innodb status;
执行后可以得到类似如下的执行结果(已省去一部分与本文没有涉及到的知识点所相关的状态描述信息):
参数设置规则介绍:
- innodb_log_file_size
单个日志文件的大小不宜过小,例如设置为500MB。由于InnoBD引擎对日志文件采用顺序写的操作方式,所以不必担心日志文件的操作消耗比数据文件操作更多的性能。
- innodb_log_files_in_group
该参数控制了文件组中日志文件的总数。设置为2-5的范围都不会有太大影响。更重要的是读者应该清楚innodb_log_file_size * innodb_log_files_in_group就是InnoDB引擎在磁盘上可用日志空间的总大小
- innodb_log_buffer_size
这个参数决定了InnoDB引擎可使用的日志内存空间。只要没有类似插入blob类型数据的操作(也不建议有这样的操作),这个内存空间都不需要设置得太大。5MB-10MB是一个推荐的设置值,不过这个参数还是要和innodb_flush_log_at_trx_commit参数配合使用。
- innodb_flush_log_at_trx_commit
该参数可以说是InnoDB引擎日志操作策略部分最重要的设置参数之一。如果您将innodb_flush_log_at_trx_commit设置为0,代表着InnoDB引擎将会按照1秒钟的周期进行日志从内存到磁盘的同步。
这时innodb_log_buffer_size的值就不能过小,因为在一个同步周期内如果待刷新的日志超过了innodb_log_buffer_size设置的大小,InnoDB就会强制执行同步操作。
如果您的Linux操作系统使用的是带有日志功能的文件系统并且日志功能是开启的,那么还是建议将该参数设置为2。
- innodb_buffer_pool_size
这个参数调整分配给InnoDB引擎使用的可用数据内存区域的大小。实际上这个数据区域不止包括了本文中一直强调的Page Cache区域,它还有很多数据区域。
例如InnoDB中用来进行查询排序的Sort Buffer区域。建议的设置大小是MySQL数据库服务所在物理服务器上总内存的60%——80%(文件系统的Cache Memory/Buffer Memory等其它程序还要使用)。
8GB的物理服务器可设置6GB的InnoDB Buffer Pool可用内存区域。注意,当MySQL数据库启动时并不是立刻就会占据所有数据区域。
- innodb_buffer_pool_instances
本小节和本文中多处位置都提及到innodb_buffer_pool_size参数以及它的含义。这个参数值在生产环境下一般设置得都比较大(例如4GB、8GB、12GB、24GB等等)。
但是由于脏数据刷盘的周期性,在I/O性能强劲的物理机器上可能就会存在I/O间歇性低谷。为了将I/O操作一直保持在一定的工作效能上,也为了发挥CPU的计算性能,InnoDB引擎允许将innodb_buffer_pool划分为多个独立的运行实例,当InnoDB需要读取新的Page时,它们会按照一定的算法被分配到某个独立运行的buffer pool instance中。
这些buffer pool instance有各自独立的LRU算法队列、独立计算脏页比例,并且独立进行脏页刷新。innodb_buffer_pool_instances参数在具有较高I/O性能并且具有较大innodb_buffer_pool_size设定值的物理设备上能够对I/O性能产生非常明显的影响。
如果您采用的是固态磁盘或者磁盘阵列作为MySQL服务器的硬件层存储介质,那么建议1-2GB的innodb_buffer_pool就分配一个独立的运行实例(这样算下来12GB的buffer pool可以设置6-12个运行实例,注意进行生产环境下的实测调整哦-)。
但如果您只是使用的机械磁盘又或者innodb_buffer_pool_size的值并不大,那么将innodb_buffer_pool_instances参数设置为1就可以了。
- innodb_io_capacity
该参数控制着InnoDB Buffer Pool数据内存区域进行磁盘同步时每次可以同步的脏页数量。在磁盘I/O性能不足时,如果innodb_io_capacity参数值过大就会造成I/O阻塞,并且造成InnoDB引擎性能较大的降低。但如果您使用的是固态硬盘或者RAID磁盘阵列,就可以将innodb_io_capacity参数默认的200设置大一些,例如设置成500——800)。
- innodb_adaptive_flushing
该参数一定要打开,保证脏页的同步周期由InnoDB引擎根据实时I/O性能情况自行控制同步频率(实际上只有两种频率:1秒或者10秒)。
- innodb_max_dirty_pages_pct
该参数默认为75,一般情况下无需更改。另外innodb_io_capacity_max参数表示当脏页数量在InnoDB Buffer Pool内存中的比例超过了innodb_max_dirty_pages_pct参数设置的上限后,就按照innodb_io_capacity_max设置的脏页数量强制进行脏页的刷新(建议采用默认值即可)。
但是设想一下这个问题:什么情况下最可能使脏页在内存中的占比超过上限呢?当然是InnoDB引擎的事务不断快速执行,并且I/O性能又不足以快速完成同步。这时InnoDB引擎将停止事务的执行,并且进行强制刷新。所以,当问题真正发生时innodb_io_capacity_max参数设置得再大也不可能解决I/O拥堵的问题,反而可能使问题更严重。
- innodb_page_size(截止其他参数)
该参数决定了InnoDB引擎中每一页的大小。每一个page包含多条row数据,更大的page size意味着内存中存储的每页信息有更多的数据条数。由于文件系统和底层硬件设置的结构,所以该值都为4KB的整数倍(默认值为16KB,可选值为4KB、8KB、16KB)。
注意如果您需要更改这个参数值,那么就必须在MySQL数据库初始化启动时,就加入到my.cnf配置文件中。否则一旦创建了用户数据表,再对这个参数进行修改,MySQL数据库就会报错。
- innodb_read_io_threads
该参数设置InnoDB数据库中的负责从磁盘上读取数据的线程数量,另外这些线程还负责在预读选项开启时承担起预读的工作任务。innodb_read_io_threads的建议值为CPU的内核数量.
- innodb_write_io_threads
该参数设置InnoDB数据库中负责将脏页同步到磁盘上的线程数量。innodb_write_io_threads的建议值为CPU的内核数量。
- innodb_read_ahead_threshold
该参数表示InnoDB引擎中的顺序预读阀值。在buffer pool中的page也有一个组织结构:64个page组成一个extent结构。当InnoDB发现在一个extent结构中已经连续读取N个page,那么InnoDB会接着将另外64 - N个后续的page读入到buffer pool中。
顺序预读在“连续读”性能较高的硬件设备上,对性能的影响非常小。所以如果读者使用了I/O性能比较强劲的固态磁盘环境或者磁盘阵列环境,则建议直接关闭该功能(设置为0即可)。
- innodb_random_read_ahead
该参数表示是否开启随机预读,默认是关闭的。
- innodb_flush_neighbors
既然InnoDB引擎提供Page的预读功能,当然就提供预写功能。该参数表示当Buffer Pool中的脏页被同步到磁盘时,是否一起刷新和这个脏页临近的页信息。
该参数在I/O性能比较强劲的固态磁盘环境或者磁盘阵列环境下,对性能提升并不明显。所以建议在这样的情况下直接关闭这个功能(设置为0即可)。
- sort_buffer_size
后文介绍数据库查询优化时会讨论到这个参数。该参数对数据库引擎的查询性能,特别是有对结果进行排序要求的查询性能影响非常大。
- join_buffer_size
后文介绍数据库查询优化时会讨论到这个参数。该参数对数据库引擎的查询性能,特别是有各种join连接要求的查询性能影响非常大。
- binlog_cache_size
在MySQL数据库中处理InnoDB层存在“重做日志”以外,在数据库管理层还有一个独立工作的二进制日志模块。这个日志模块的工作方式和“重做日志”的工作方式相似,它们采用的办法都是在内存中进行日志数据变更,然后按照一定的策略周期性/直接同步到磁盘上,binlog_cache_size参数设置的就是可供二进制日志在内存中进行暂存的空间大小。
需要注意的是:binlog_cache_size和innodb_buffer_pool_size不同的是,前者的大小以MySQL数据库的客户端连接为单位。也就是说MySQL数据库会为两个独立的数据库客户端连接分别分配独立运行的binlog cache空间。
正式环境的数据库中为每一个数据库连接设置的binlog cache空间不需要太大,当然这还要考虑实际的客户端请求频度和数据类型,还要考虑下面将介绍的sync_binlog参数设定。该参数建议的几个设置值为:32768(32KB为默认值,没有特别的要求可以保留该设置)、65536(64KB)、131072(128KB)、262144(KB)、524288(512KB)、1048576(1MB)以内。
- sync_binlog
在MySQL数据库中除了InnoDB的“重做日志”需要同步以外,二进制日志也需要进行同步。这个参数是指MySQL数据库在内存中进行X次二进制日志操作后,就将内存中的二进制日志同步到磁盘中
2.4.3 调整后的参数情况
- 概述如下:
以下是一组可以在配置有固态硬盘和磁盘阵列的正式MySQL数据库环境下使用的配置项参考,主要是为读者总结InnoDB引擎中和I/O性能相关的重要参数(只和I/O性能有关,因为后续的文章中还会介绍其他参数)。
读者在进行参数配置是还是需要按照自己团队的生产环境情况,对配置项进行调整(这些参数信息都在MySQL数据库的my.cnf主配置文件中进行设置):
- 配置完成后可以通过以下命令查看当前MySQL数据库和InnoDB引擎中相关的配置参数(为节约篇幅,已省去一部分查询结果):
三、提供更优的硬件方案
3.1 使用磁盘阵列替代单块磁盘
这是最基本的硬件层改造方式,目前大多数厂商提供的PC Server基本上都集成了RAID控制器。所以这样做一般不需要额外增加购买硬件设备的费用。
在MySQL官网上并没有明确推荐使用哪一种磁盘阵列模式,但是从搭建磁盘阵列支持MySQL的实际引用情况来看,更多是使用RAID 10阵列模式(另外磁盘阵列的整体性能和阵列控制芯片有很大关系)。
RAID 10阵列模式可以在提升了整个系统I/O性能的基础上兼顾了存储的安全性。为了使用RAID 10磁盘阵列模式,读者至少需要为准备4块磁盘。其中2/4的磁盘容量用来存储数据冗余,另外2/4的磁盘容量用来分散存储数据。对于RAID 10磁盘阵列模式的详细工作方式介绍,读者可以参看另一篇文章(《架构设计:系统存储(2)——块存储方案(2)》)
3.2 使用固态硬盘进一步替代机械磁盘
以上解决方案中,每一个机械磁盘的I/O性能将会成为整个RAID 10磁盘阵列的性能瓶颈(不考虑阵列控制芯片的处理性能)。所以如果技术团队还有多余的资金支持那么下一步要做的就是将构成RAID 10磁盘阵列多个机械磁盘全部替换成固态磁盘。如下图所示:
3.3 进一步使用外置磁盘阵列柜 + 光纤结构
USB3.0接口的理论带宽只有600MB/S,而且PC Server内置的磁盘阵列控制器由于服务器内部空间的限制,也存在磁盘数量扩展困难的问题。如果读者确认生产环境的某个物理服务器将以I/O读写操作为主,且I/O性能将成为其上工作的应用软件的瓶颈。
那么这时最好的硬件方案就是直接采用外置企业级磁盘阵列柜 + 光纤接口的方式搭建硬件层支持。
目前主流的光纤线路带宽为16Gb/s,这远远高于USB3.0 6Gb/s的理论带宽、高于SAS 12Gb/s的理论带宽。另外单个企业级磁盘阵列柜可容纳的磁盘数量就已经很高了(例如IBM Storwize V5000 单柜提供24个磁盘位,单柜支持最大72TB存储容量),并且这些企业级盘柜一般支持扩展成多柜。
这两种特性有效解决了硬件层面磁盘I/O速度和容量的问题,但代价就是这些IT基础折本的价格一般比较昂贵,技术团队所在企业需要有比较宽裕的项目/产品建设预算。
更多推荐
所有评论(0)