MySQL概述

MySQL数据库目前已被Oracle收购,并发展处多个版本。目前使用最广泛且免费的MySQL版本是MySQL Community(社区版),另外还有三个付费的MySQL版本MySQL Standard(MySQL标准版)、MySQL Enterprise(MySQL企业版)、MySQL Cluster(MySQL集群版),这三个版本是按照CPU内核进行费用计算,并且价格由低到高。最后Oracle还提供了两个微型的MySQL版本:MySQL Classic(经典版),这个版本的MySQL只提供了MyISAM存储引擎但是安装快速,占用空间较少;MySQL Embedded(嵌入式版本),这个版本的竞争软件是SQLite。虽然社区版本是免费的并且这个版本提供的功能也没有企业级版本丰富,同样的硬件条件下单节点性能也没有企业基本版优秀。但是我们可以借助社区版本自身提供的功能和一些第三方软件配合使用,搭建起相对廉价且性能不俗的MySQL数据库集群。

当今MySQL正在被广泛的使用,支撑着无数互联网应用的数据层,很多抱着拿来主义的人,往往用的不得要领,出现这样或那样的性能问题,这是因为忽视了MySQL在应用的过程中需要的持续调优过程,包括MySQL参数调优、架构部署调优、硬件调优、SQL调优等等。



数据库引擎的选择

MySQL数据库中最重要的一个概念就是数据库引擎,不同的数据库引擎的工作原理存在很大差异最终造成MySQL数据库服务的性能差异。例如如果数据库引擎需要支持事务,就必须满足事务的基本特性——AICD特性(AICD:原子性、隔离性、一致性和永久性),那么自然就需要一定处理机制来实现这些特性。这样做的现实效果就是导致写入同样数据量的情况下,支持事务的数据库引擎比不支持事务的数据库引擎耗费更多的时间。

这里列举MySQL数据库社区版中支持的一部分数据库引擎:

  • MEMORY:MEMORY存储引擎将表的数据完全存放在内存中。在MySQL数据库的历史版本中和该数据库引擎类似的其它引擎是HEAP,后者曾是MySQL数据库中访问速度最快的数据库引擎。但由于这两种数据库引擎完全工作在内存中,所以如果MySQL或者服务器重新启动,数据库引擎中保存的数据将会丢失。
  • BLACKHOLE:中文名“黑洞”,使用BLACKHOLE数据库引擎的数据表不存储任何数据,只根据数据库操作过程记录二进制日志。它的主要作用是作为MySQL主从复制的中继器,并且可以在其上面添加业务过滤机制。
  • MyISAM:MyISAM数据库引擎是MySQL数据库默认的数据库引擎。MyISAM使用一种表格锁定的机制,来优化多个并发的读写操作(实际上就是使用的一种避免数据脏读的机制)。但是这种机制对存储空间的使用有一定的浪费。MyISAM还有一些有用的扩展,例如用来修复数据库文件的MYISAMCHK工具和用来恢复浪费空间的MYISAMPACK工具。
  • InnoDB:InnoDB数据库引擎是在各种版本的MySQL数据库中使用最广泛的一种数据库引擎,本文后续的介绍中如果没有特别说明都默认是在说InnoDB数据库引擎。InnoDB数据库引擎使用日志机制提供事务的支持。


基本I/O性能

对于数据库的优化,需要从整个计算机体系的发展角度考虑。在今天CPU、GPU、内存频率、总线带宽都得到快速发展,使得计算能力得到质的提升,但是存储机制(除了机械硬盘、固态硬盘、磁盘阵列),没有更加革命性的变化。而传统的关系型数据库,在保证ACID的原则下,持久化是保证一致性的重要手段。因此,如何大幅度提升IO的性能,是数据库优化的重点之一。

要了解MySQL数据库中的性能问题,就首先要搞清楚在客户端向MySQL数据库提交一个事务操作时后者到底做了些什么事情,以及主要是怎么做的。本节所描述的工作过程主要围绕InnoDB数据库引擎进行:

mysql商业版收费标准 mysql标准版报价_数据库

上图中只画出了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所得到的数值了

mysql商业版收费标准 mysql标准版报价_数据库_02

这样做的目的是保证了后续同步日志数据的操作都是顺序写,而不是随机写,以提高IO的效率。当日志数据写到最后一个文件的末尾时,下一条日志数据又会重新从第一个日志文件的开始位置进行写入。



MySQL I/O 性能问题的产生

InnoDB Log Buffer内存空间中的四个标识指针是InnoDB数据库引擎日志处理部分最重要元素,它们分别是:Log sequence、Log flushed、Pages flushed、Last checkpoint,这四个标识涉及到InnoDB在崩溃重启时不同的数据恢复策略,以及I/O性能优化中的关键原理。

这四个标识,实际上是四个数值。它们共享一个数值池(名叫LSN,log sequence number,日志序列号,其总长度是64位无符号整数),代表当前InnoDB对事务操作的“检查点或持久点”,并且它们之间的检查点数值关系特征如下:

Log sequence 大于等于 Log flushed 大于等于 Pages flushed 大于等于 Last checkpoint

  1. 分配LSN。每当InnoDB接收到一个完整数据库insert/update请求事务后,就会创建一个新的LSN。新的LSN = 旧的LSN + 本次写入的日志大小。这条最新的日志将会使用Log sequence进行标记,并且如果出现接收到多个事务请求的情况下,InnoDB也会按照一个既定的顺序对这些日志进行排序,然后依次生成新的LSN。这一步骤是完全在内存中进行的,所以不存在I/O性能问题
  2. 处理事务中的操作,写入InnoDB File Log Group日志文件。InnoDB数据库引擎专门有一个InnoDB Buffer Pool内存空间用来进行数据更改或数据新增。其大小由innodb_buffer_pool_size参数控制,其数据来源于innoDB data file并且以Page的形式存在于InnoDB Buffer Pool中。当日志中有insert操作时则生成新的Page;当日志中有update操作时,InnoDB会检查该数据是否已经存在于Page Cache中,如果存在(命中)就直接更新这个Page Cache中的内容,如果不存在(未命中)就会继续从InnoDB data file中读取(支持预读机制)原始数据到InnoDB Buffer Pool中然后再更新。
    当InnoDB完成InnoDB Buffer Pool中的数据操作后,更改后数据所涉及到的Page将和此时存储在磁盘上的数据不一样,这样的Page称为脏页

    如何控制脏页是保持数据一致性的关键。InnoDB数据库引擎的做法是:首先向InnoDB File Log Group日志文件中写入这个事务的日志信息(写入策略由innodb_flush_log_at_trx_commit参数决定);然后InnoDB数据库引擎在这一步骤的最后一个动作是更改Log flushed标识指针值为当前最后完成刷新动作的事务日志LSN值。实际上执行完这个步骤,一个事务处理操作才算真正成功。
  3. 将脏页写入磁盘。上面两个步骤,涉及数据变动的脏页还没有更新到磁盘上,为什么事物的处理就可以算作成功了呢?这是因为即使这个时候数据库异常崩溃了,就凭存储在磁盘上的完整日志我们也可以重做数据。但是最好还是尽快同步脏页吧。在第三个步骤InnoDB数据库引擎将会把最近Log flush时所涉及到的脏页(最旧脏页)更新到磁盘上。当完成脏页向磁盘的同步操作后,InnoDB数据库引擎将会更新Pages flushed标识点的LSN值,表示这个LSN值所代表的事务(以及之前LSN的事务)都已经完成了内存和磁盘上的数据同步动作。

    当InnoDB数据库引擎进行脏页更新时,将会按照一定的周期策略批量提交脏页到Linux操作系统的cache memory区块中。不同版本InnoDB数据库引擎支持的pages flush策略是不一样的,但最基本的规则没有变化,就是周期性刷新。每一次批量提交的脏页数量由innodb_io_capacity_max参数决定。
    从Mysql version 5.6开始InnoDB数据库引擎向管理者提供了一个innodb_adaptive_flushing参数,InnoDB数据库引擎将检测脏页在InnoDB Buffer Pool中的比例,以及即时I/O状态等情况来决定pages flush的周期。如果脏页在InnoDB Buffer Pool中的比例达到了由innodb_max_dirty_pages_pct(默认为75)参数设置的百分比阀值,这时InnoDB数据库引擎将按照innodb_io_capacity_max(默认值2000)参数设置的数量将这写脏页一起同步到磁盘。
    当磁盘I/O性能不足,且innodb_io_capacity设置过大时,会导致产生较长的I/O队列造成I/O请求阻塞,一旦累积到innodb_max_dirty_pages_pct阀值,又会产生更长的I/O阻塞队列;反之则会造成物理服务器的I/O性能没有被去完全使用。所以innodb_io_capacity的设置非常重要,特别是当读者在硬件层采用SSD固态硬盘和高速磁盘阵列时
  4. Checkpoint是InnoDB数据库引擎中最后一个标识点。这个标识点代表着当数据库异常崩溃重启后,小于或者等于这个标识点LSN值的所有日志信息、数据信息都无需进行重做检查。而LSN值大于Checkpoint的所有事务都需要重做,只是重做策略将视LSN值所在标识区域的不同而不同:
  • 当代表事务的LSN数值在Log sequence——Log flushed范围时(不包括Log flushed),说明在数据库崩溃时内存中的事务并没有处理完,这部分事务操作将在恢复时被丢弃。
  • 当代表事务的LSN数值在Log flushed——Pages flushed范围内时(不包括Pages flushed),说明数据库崩溃时磁盘上已经拥有这些事务完整的日志记录。InnoDB数据库引擎将读取这些日志数据,并继续执行下去,直到代表这些事务的LSN值被标记为Checkpoint(或者小于Checkpoint标识的LSN值)。这里要注意,在数据库崩溃时处于这个范围内的某些事务可能已经完成了一部分的数据同步动作,但是肯定是不完整的。所以即使是这样的事务也要重新进行磁盘同步,才能保证数据的一致性。
  • 实际上在MySQL version 5.5的早期版本,InnoDB数据库引擎中只有三个标识:Log sequence、Log flushed和Checkpoint。也就是说当脏页成功同步到磁盘后,就会直接更新Checkpoint标识的LSN值。后续版本的MySQL数据库增加了Pages flushed标识点,这样做的目的是保证Checkpoint和Pages flush的更新可以拥有独立的周期,从而降低其带来的性能消耗。


Log flush和Pages flush

我们大致知道了在InnoDB数据库引擎中一个事务的处理过程中有两个步骤存在I/O操作:Log flush和Pages flush。

Log flush的过程是将完成的事务日志写入到日志文件中,由于InnoDB数据库引擎中日志文件的组织方式(建立在块存储方案上,对于大部分关系型数据库都适用。而不是建立在文件存储方案或者对象存储方案上),Log flush中对磁盘的操作是顺序写。并且技术团队还可以通过innodb_flush_log_at_trx_commit参数来调整InnoDB Log Buffer到InnoDB File Log Group的写入策略,这有助于进一步提高Log flush性能。

Pages flush的过程就没有那么幸运了,InnoDB数据库引擎不可能事先知道数据库会存放哪些数据,也不可能知道下次的update操作和select操作的目标数据存放在哪个区域。所以InnoDB数据库引擎针对Page的读取和更新都只能基于随机读写。那么Pages flush过程就需要在如何保持I/O性能这问题上想更多的解决办法。

  • 预读预写机制。例如在读取Page时,采用“预读”思路将目标Page所临近的Page一起读取出来;在写入Page时将目标Page所临近的Page一起写入(“临近写”)。“预读”策略可以通过innodb_read_ahead_threshold参数进行设置,并通过read thread完成,另外 innodb_flush_neighors参数可以控制是否开启“临近写”策略。总的来说“预读”/“临近写”在默认情况下都是开启的,但“预读”/“临近写”思路本身就需要一定的准确性,低命中率的“预读”反而会降低InnoDB的I/O性能。还有一种“随机预读”,它在MySQL version 5.6版本中默认就是关闭的,并且在随后的版本中将会慢慢废除,所以这里就不再介绍了。
  • 批量执行机制。例如将向磁盘提交Page的动作设计为周期性且批量进行,并且始终保持InnoDB Buffer Pool内存区域的脏页(Dirty Page)在一定的比例,这些策略主要由innodb_io_capacity、innodb_max_dirty_pages_pct、innodb_io_capacity_max等参数控制。
  • 提高内存命中率和利用率。例如通过调整Innodb_Buffer_Pool_size参数获得更大的InnoDB Buffer Pool内存区域,存储更多的Page。实际上Innodb Buffer Pool区域不仅包括我们已经介绍的Page Cache数据部分,还包括其它的数据区块。例如为了快速定位B+树索引的Hash Index结构。调整Innodb_Buffer_Pool_size参数将会使这些数据区域都享受到内存容量带来的优势——至少不会频繁地发生内容空间的强制清理。