首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

扩展MySQL

Scaling MySQL

我们早已讨论过MySQL中的瓶颈和性能方面的问题,但还没看过它所提供的各种存储引擎,以及如何利用这些引擎的不同特性。严格来讲,更换MySQL后端更大程度上是一个性能问题,而不是扩展的问题,但一旦碰到MyISAM的表级加锁的情况,不先解决这个

性能问题,扩展就无从谈起。

在谈论过各种不同存储后台和相应能力后,会看一些扩展MySQL安装的办法。对于有限非线性读取能力的扩展,可以使用MySQL的复制系统。对于体系结构的线性扩展,可以考虑纵向分区,将表空间划归不同的簇,或者横向分区,将数据划分到不同的区段。

在深入谈论之前,还要当心数据库扩展的方法和术语。和很多技术概念一样,有多种含混不清的方式来描述数据库扩展方法。纵向分区有时被称为聚簇(clustering),有时被称为分聚(segregation)。这样产生的结果区段有各种不同称呼,比如簇(clusters)、分区(partitions)和池(pools)。横向联合有时被称为聚簇(clustering)或者数据分区(data partitioning)。产生的不同区段往往被称为shards,但也可能叫做单元(cells)、簇(clusters)或者分区(partitions)。我们会一直使用分区/分区和联合/区段的说法,但在和其他人讨论这些主题的时候注意不要混淆,要不很容易发生误解。

存储后端

Storage Backends

在MySQL数据库的核心部分中,有一部分叫存储引擎,往往用后端或者表类型来指代它们。存储后端位于解析器和优化器之下,提供了存储和索引设施。MySQL带有多种内建的存储引擎,并且允许用户添加自建的存储引擎。每个存储引擎实现了简单的打开、关闭、读取、插入、更新和删除语义,基于这些,MySQL能够构建起自身的环境。不同的表类型提供了不同的特性和优点,但它们全部都位于查询解析器层次之下,如图9-5所示。

要查看MySQL安装中有哪些可用的存储引擎,可以直接询问MySQL服务器:

mysql> SHOW STORAGE ENGINES;

+------------+---------+------------------------------------------------------------+

| Engine    | Support                                                      | Comment |

+------------+---------+------------------------------------------------------------+

| MyISAM    | DEFAULT                                                      | Default engine as of MySQL 3.23 with great performance     |

| MEMORY    | YES                                                          | Hash based, stored in memory, useful for temporary tables         |

| MERGE     | YES    | Collection of identical MyISAM tables                         |

| InnoDB    | YES    | Supports transactions, row-level locking, and foreign keys    |

| BDB       | YES    | Supports transactions and page-level locking                  |

| NDBCLUSTER         | NO                                                  | Clustered, fault-tolerant, memory-based tables                   |

+------------+---------+------------------------------------------------------------+

6 rows in set (0.00 sec)

图9-5:“存储引擎和客户端”

这条命令给我们显示了所有可能的MySQL存储引擎,如本安装中可用的存储引擎,以及哪一个是默认用于新表的。通过显示数据库的表状态,可以知道每张数据库表具体使用了哪个引擎。这里为清晰起见,大多数列都被截断了——Engine列(MySQL 4.2.1以前用Type)给出表格所使用的引擎:

mysql> SHOW TABLE STATUS;

+--------------+--------+---------+------------+------+----------------+------------+

| Name       | Engine         | Version   | Row_format          | Rows      | Avg_row_length  | Data_length|

+--------------+--------+---------+------------+------+----------------+------------+

| columns_priv       | MyISAM |       7    | Fixed               |    0       |              0    |          0   |

| db          | MyISAM         |       7    | Fixed               |    2       |            153    |        306   |

| func        | MyISAM         |       7    | Fixed               |    0       |              0    |          0   |

| host               | MyISAM |       7    | Fixed               |    0       |              0    |          0   |

| tables_priv        | MyISAM |       7    | Fixed               |    0       |              0    |          0   |

| user        | MyISAM         |       7    | Dynamic             |   10       |             66    |        660   |

+--------------+--------+---------+------------+------+----------------+------------+

6 rows in set (0.00 sec)

我们可以使用ALTER TABLE命令更改表格所用的引擎。执行这个操作时要小心,因为如果表中有数据,那么这个操作可能需要很长时间。同时也要注意,转换含有数据的表到一些引擎时可能导致数据丢失,比如说Heap或者MySQL 5的Blackhole引擎。我们也可以在表格创建语法中设定表格的引擎类型:

mysql> CREATE TABLE my_table ( ... ) ENGINE=MyISAM;

mysql> ALTER TABLE my_table ENGINE=Heap;

MySQL 5支持10种不同的可用的存储引擎,可以完成各种挺有趣的事。但很可惜,在MySQL 5能够用于生产环境之前,还得继续使用MySQL 4所提供的有限的存储引擎。我们会讨论MySQL 4中的最重要的4种可用的引擎,并对它们提供的不同特性进行比较。

MyISAM

MyISAM是原来的默认存储引擎,是对IBM开发的ISAM(索引顺序存取方法)模式的扩展。MyISAM表格由磁盘上3个不同的文件组成。其中.frm文件存放表定义,.MYD文件存放行数据,.MYI文件存放索引。

MyISAM引擎能非常快速地进行读写操作,但却无法同时快速地进行读和写操作。MyISAM使用表级锁定,具体有3种不同的锁定类型。大多数读操作会使用READ LOCAL锁,它允许对同一张表的其他读操作,但禁止更新操作,除非更新操作是同步插入(数据插入到表的尾部)。READ锁主要由MySQL工具套件使用,和READ LOCAL类似,但连同步插入操作也被禁止。最后一种锁类型是WRITE,在insert、update或delete时都需要使用这种锁,它会阻塞其他所有对这张表的读和写操作。由于每张表都有一个单独的锁,任何的写操作都会阻塞读,反之亦然(大多数情况下)。写操作同时也阻塞其他写操作(除了同步插入之外),所以写操作同步性所能达到的最大程度相当低。MyISAM不支持事务。

MyISAM非常有用的一个特性是FULLTEXT索引类型。FULLTEXT索引能很方便地搜索文本列的内容,把标记化和匹配的问题交由MySQL去操心。FULLTEXT索引也支持布尔查询,让你能够搜索判断一个检索词是否出现,还可以搜索检索词的组合。

mysql> SELECT * FROM articles WHERE MATCH (title,body)

    -> AGAINST ('+foo -"bar baz"' IN BOOLEAN MODE);

FULLTEXT索仅仅适用于MyISAM表。如果想知道关于FULLTEXT索引的更多信息,可以到http://www.mysql.com/查询MySQL的在线手册。

MyISAM是使用R-Tree索引来支持GIS数据(地理的和空间的)的唯一的表类型。这让你能够输入空间数据(比如经度和纬度记录),还可以快速有效地使用边界框(bounding boxes)和其他空间工具进行搜索。

InnoDB

InnoDB存储引擎提供了大量MyISAM引擎中没有的特性。InnoDB是完全ACID兼容的

(原子性、一致性、隔离性和持久性),并且支持事务。InnoDB记录事务日志,能把日志回放和回滚未提交的事务从数据库崩溃中恢复。这避免了像MyISAM那样的情况,随着数据集增大,恢复时间也越来越长。InnoDB在数据库崩溃后的恢复时间并不固定,但视检查点的频繁程度不同,有一个上限,而不是取决于数据集的大小。

InnoDB 通过MVCC(多版本同步控制)来加锁,它提供了快速的行级锁定,而且不阻塞读操作。简单地说,MVCC 工作时会保留正被事务更改的行的一个复本。事务在写这些行的新的复本的时候,由所保留的复本来应对这些行的读请求。事务提交后,新的行就取代旧的复本,作为新的读取源而存在。

MVCC为InnoDB表提供了大量同步性。在数据库表被写入时,可以从表中读取数据,并且可以对表格的不同部分同时执行写操作,而不会相互阻塞。InnoDB 不好的方面相当少——表格会使用大约3倍于MyISAM的空间(在表格很大时这是个问题),而且无法使用FULLTEXT索引。

不管怎么说,我们能够使用外键和级联删除(ON DELETE CASCADE)这样的特性来管理参照完整性。InnoDB 通过聚簇主键,用B树来存储索引。和MyISAM有所不同,InnoDB的行数据按主键在磁盘上排序。这样能比在按主键顺序读取数据时提供快得多的速度,因为磁头不需要花很多时间来定位。

BDB

MySQL 能使用 Berkeley DB系统存放表数据。BDB 数据库由键值对的列表组成,为按键访问提供非常快的速度。MySQL 基于BDB模拟全表的存储,但表格必须有一个主索引,否则,MySQL 会为你创建一个隐藏的5字节的自增主键。BDB 引擎将每行作为一个键值对存储,以主索引为键。索引进一步被当作更多的键值对存放,以主索引为值。

BDB 支持事务,通过记录事务日志和周期性设置检查点,所以表崩溃后的恢复时间和InnoDB类似。由于它存储数据的方式,线性的表扫描相当慢。

BDB内部为写操作使用页面级别的加锁。页面大小由诸多因素决定,包括所用的MySQL的版本和数据的大小,但总归是由一定数量的行组成。这为读操作提供了很高的同步性,就像InnoDB一样(但还没那么快,因为有些读操作还是得等待获取锁),而且不用在更新大量的行时建立很多的锁,降低了由此带来的负载。

Heap

Heap表类把所有表数据保持在内存中,并且不会将它们持久化到磁盘上。重启MySQL服务器之后,Heap表就被清空了。但它们会保留模式定义,因为这一部分内容是存放在磁盘上的,要删除Heap表,得像其他表类型那样DROP它。

Heap表使用表级加锁,但由于写操作是如此之快,所以不管怎么说,它的速度总是很快,同步性也很好。除B树之外,Heap表还支持哈希索引,对不变的检索词能非常快速地查找定位。当然,它的缺点就是数据集必须小到足以放在内存中。一个被内核交换到磁盘上的Heap表会比其他表类型慢很多。Heap表非常适用于时间敏感性数据,比如实时事件队列,前提是你不介意在重启的时候丢失数据。Heap表还能让你把MySQL当成一个通用的内存中的缓存来使用。使用MySQL而不是memcached会给你更差的性能,也只能允许更小的数据集(因为MySQL自己会使用大量内存),但能够避免总体上的复杂性(假定你早就在程序中其他什么地方使用MySQL),并且不用付出什么额外的努力就能够添加SQL语义。

MySQL复制

MySQL Replication

我们知道,单独一台MySQL服务器也只能做到目前这样了,它无法应付更多的容量和性能要求。和写数据的能力比起来,Web应用程序往往需要更多的数据读取能力,基本上相对每个写操作,要执行10到100个读操作。为了帮助扩展数据读取性能,MySQL提供了复制支持。

复制所做的事情正如其名——在多台机器之间复制数据,提供多个可以进行数据读取的点。待会要讨论的各种不同的复制模式,能够基于多台机器扩展读取性能,因为它们在这些机器上保持表数据的多个副本。

在MySQL中,复制发生在一台主服务器和一台从属服务器之间。每个MySQL实例都可以是一台且只能是一台主服务器的从属服务器(要不就不是从属服务器),也可以是零个或多个从属服务器的主服务器。下面我们依次看看主要的配置种类和它们各自的优点。

主从复制

Master-Slave Replication

要从单一服务器迁移,最简单的是迁移到一个单一的主/从配置,如图9-6所示。

原来的单台机器成为新的主服务器。所有的写操作——插入、更新、删除和管理命令,比如创建和修改——都在主服务器上操作。当主服务器完成操作(或者事务,如果它是InnoDB),

图9-6:正在服务中的主机和从机

它就将这些写入到日志文件,所谓的binary log(或者binlog)中。当从属服务器起动后,它连接到主服务器,并且保持连接打开。因为事件都写入到了主服务器的binlog中,它们传递到从属服务器,从属服务器将它们写入relay log。这个连接,在从属端被称作复制I/O线程,在主机端被称作从属线程。从属服务器总有第二个线程处于活跃状态,称作从属SQL线程。这个线程从relay log中读取事件(由I/O线程写入),并且按顺序执行它们。

通过复制主服务器上的事件,从属服务器上的数据集合以完全相同的方式被修改。由于从属服务器的内容和主服务器一致,所以它可以被用来进行数据读取。从属服务器无法被写入,并且应该只能使用只读帐户连接。任何对从属服务器所做的改动,都不会复制回主服务器,因此,一旦你对从属服务器执行写操作(这有时是有用的,待会可以看到),两边的数据集合就会变得不一致。

事务是没有当成事务来复制的,如果一个事务被回滚了,没有内容会被写入binlog。如果一个事务提交了,事务中的每条语句都将被依次写入到binlog。这意味着,在执行组成一个事务的多条语句的短时间内,从属服务器是没有事务完整性的。

我们得到的是更多的读能力。现在应该能够执行双倍的读操作了——一般在主服务器上,或在从属服务器上。两台机器都还是得执行所有的写操作(尽管主服务器还得对失败的事务执行回滚),因此在相同的配置下,从属服务器无法提供比主服务器更多的写能力。这是经常有些人忽略的但非常重要的一点——因为从属服务器需要执行所有主服务器所执行的写操作,从属服务器如果做不到比主服务器强劲,那至少也得达到相同的程度。

当需要更多额外的读能力时,可以添加更多的从属服务器,如图9-7所示。

图9-7:添加额外的从属服务器

每台从属服务器都有各自的I/O和SQL线程,同步转播主服务器的binlog。从属服务器相互之间不能确保同步,因为速度快的机器(或服务的读请求数目少的)能比其他的机器更快地执行写请求。主服务器为每个连接中的从属服务器保持一个线程开启,在事件发生时,将它们传播给各个从属服务器。

随着读能力需求的增加,可以不断增加从属服务器。有些大型的Web应用程序其每台主服务器有数百台从属服务器,只需少许努力,就能提供让人惊讶的读性能。采用这种简单的技术,可以在很长一段时间内不断扩展读性能。唯一的问题在于,每台机器都不得不执行每个写操作,因此我们无法扩展写性能。复制不能在提高写性能方面给予帮助,这很遗憾,但在本章后面的内容中,我们将看到有其他的技术能够给我们帮助。

树状复制

Tree Replication

除了简单的主服务器加从属服务器的配置,还可以创建复制树,将一些从属服务器转变成为其他更多机器的主机,如图 9-8所示。

如果有数百台的从属服务器,主服务器将内容复制到所有从属服务器所需要的带宽将是很可观的,因为每台从属服务器对每个事件都需要一个独立发出的通知,所有这些通知都来自于主服务器的单个网络适配器(我们可以绑定多个网络适配器在一起,或者使用子网,但问题不在于此)。每台从属服务器在主服务器处都要求有一个线程,到一定程度,线程的管理和上下文切换将成为显著的负担。通过将一部分从属服务器变成更进一步的从属服务器的主服务器,可以限制和每台主服务器通话的从属服务器数目。如果限定每台主服务器有100个从属服务器,在三层树中,就可以容纳1 + 100 +(100×100)台机器,也就是 10101 台机器。只需再加一层,就可以有1010101台机器了。在进一步添加层次之前,我们很可能就已经拥有足够多的机器了。

图9-8:将一台从属服务器转变成主从服务器

但这种方式也有缺点。如果某个中间层次的从属服务器当机了,所有其下的从属服务器都将停止复制,并且内容将不再有效。很快我们会讨论到复制中的冗余问题,但这种情况显然不是所期望的。

每个写事件也需要花费更多的时间来到达底层从属服务器。写操作首先再顶层主服务器中执行,然后传播到第二层,并在队列中等待执行。在第二层中执行后,它被传播到第三层,并在队列中等待执行。第三层的从属服务器不仅仅要等待它自己的事件队列,还需要等待它的主服务器的事件队列。

在另一方面,使用多层复制,能做一些相当酷的事。根据复制流中的名称,MySQL允许在复制过程中包含或者排除数据库和表,这让我们可以从主服务器中只复制一部分数据到从属服务器,如图9-9所示。

如果对从属服务器直接执行一个写操作,这个操作不会被复制到它的主服务器中——我们早就说过,应该使用只读的帐户连接到从属服务器,以避免数据不一致。如果对中间层次的从属服务器执行写操作,这个操作在本地副本中得以执行,并且将被复制到下层的从属数据库中。综合这些概念,可以创建特殊的从属服务器来完成一些特定的任务。

例如,假定由于需要读写操作之间良好的同步,为50张表使用了InnoDB。有一张表包含了用户帐户,包括她自己输入的个人描述。我们希望对Users表执行一个全文搜索,FULLTEXT索引类型最适合不过了。但是,它只能用于MyISAM表格,而我们需要使用InnoDB——如果切换Users表成MyISAM,就无法跟上插入和更新所需要的速度,因为机器忙于其他

图9-9:复制部分数据以减少数据流量

表格,而Users表有很多其他索引,使得它的更新速度较慢(如果需要锁表来执行更新,速度缓慢是很糟糕的)。

我们可以创建一个从属服务器分支,只复制Users表,通过这些在特定的从属服务器上增加性能。然后从这些从属服务器上除去其他索引,这些从属服务器的Users表上就不再包含其他任何索引了。但主服务器和其他的从属服务器则不作改动。因为这些特定的从属服务器只包含一张表,而且由于没有索引能够更快执行插入操作,所以可以使用MyISAM。在这些特定的从属服务器上,使用alter来改变它们的存储引擎来使用MyISAM,并且只有这些从属服务器才进行更改——主服务器和其他从属服务器继续使用InnoDB,保持所有原有的索引。接着,就可以添加FULLTEXT索引到这些特定的从属服务器中,用于搜索用户描述。因为复制将更新操作以SQL语句的方式发出,我们可以从InnoDB表复制更改到MyISAM表,而不会产生任何问题。两者之间的数据仍然是同步的,但是索引、模式和引擎类型等却有所不同。

主服务器-主服务器复制

Master-Master Replication

采用另一种不同的方式,我们可以创建一对服务器,互为从属。这被称为主服务器-主服务器(master-master)复制,如图9-10所示。

每台服务器都从另一台读取binlog,但复制系统提供特性,以免服务器重新执行自己生成的事件。如果一个写操作发生在服务器A。它被复制到服务器B,并且得以执行,但接着不会复制回服务器A。在复制配置中,每台服务器都有一个独一无二的服务器ID,复制日志中的每个事件都有一个相关的服务器ID。当一台服务器遇到含有自己的服务器ID的复制事件时,它只需直接将其忽略。

图9-10:master-master复制的配置

master-master配置的缺点在于,如果使用了自增的主ID,那么就不能在各个服务器之中随意执行写操作了。如果想两台主服务器同时写入了两条不同的记录,它们可能会被赋予相同的自增ID。在事件被复制时,两者都会失败,因为它们不满足主键约束。这显然是一种不好的情况,而且MySQL在这时会中止复制,并且将错误写入MySQL错误日志,这样做并不能解决问题——如果在主服务器上执行的事件未能再从属服务器上成功执行,那么两者之间必然有不一致的数据。

有不少方法可以绕过这个缺陷。可以考虑调整程序代码,只对A中的一些表执行写操作,而对B中的其他表执行写操作,这意味着我们还能继续使用自增ID,而且不会出现冲突。也可以避免使用自增ID做主键,由于表中其他字段含有唯一值,所以这很简单,但对于一些重要的对象表(比如用户账号),往往没有合适的候选(电子邮件地址和用户名称不适合作为数字索引,它们占用很多空间,而且其他表不得不用它们作为外键)。使用其他某种服务来生成唯一ID,也可以解决这个问题,但就不得不放弃生成顺序的ID了。想要按插入顺序来排序记录,就要借助于每条记录的时间戳了,因为实际上没有真正的插入“顺序”。每台服务器都有自己认为正确的顺序。

把一对主服务器看成是含有两台机器的环,可以将它们进一步扩展。我们可以创建含有任意数目服务器的环,只要每台服务器都是其后服务器的主服务器,而且又是前面一台服务器的从属服务器。如图9-11所示。

多台主服务器方式的一个不足之处在于,任何时候都不存在一份“真实的”数据副本,所有机器上的数据都不相同(假定相互之间有流量),一部分记录只存在于一部分服务器之上,直到它们被完全复制。这给读操作的一致性带来一些麻烦。在任何时候,需要知道一行记录的真实的值,都不得不停止所有服务器上的写操作,等复制都完成后再执行这个读操作,这在生产环境中是不切实际的。

图9-11:建立主服务器环

复制失败

Replication Failure

master-master配置有什么好处呢?它能提供冗余性——因为每台机器都是其他机器的镜像,并且环环相扣地执行写操作,在一台服务器出现故障时,可以将故障转移到其他任意一台服务器上。假定没有使用自增ID,我们可以在任意时间向任意一台机器写入任何一条记录。这样,就能像均衡读操作一样来均衡写操作,从列表中第一台机器开始尝试写,直到找到一台活跃的服务器。这里的诀窍在于,在单个会话中,使单个用户固定使用一台机器(除非这台机器坏了),这样任何的读操作,相对于这个用户执行的写操作而言,都能得到处于一致状态的数据。

在master环配置中,冗余并不是那么简单的。虽然可以向任意一台机器写数据,但一台机器上的失败,就会导致环中位于它之后的所有机器(除了正好处于它前面的机器)的内容无效,如图9-12所示。

图9-12:一台机器上的故障,导致其他机器内容失效

写操作仍然能够在A、B、C上执行,但写到B上的内容不会复制到A,而写到C上的内容根本得不到复制。只有C机器能够获得所有的写操作,因此它将是唯一一台数据处于正确状态的机器,直到出现故障的那台服务器恢复。

master-master配置是唯一给出清晰的故障转移语义的配置,但它不能超出两台机器。我们可以扩展这个配置,为每台主服务器添加一个或者更多从属服务器,从而提供更多的读能力和写冗余,如图9-13所示。

图9-13:添加从属服务器,以提高读能力和冗余能力

但是,一旦一台主服务器出现故障,所有其下的从属服务器都会失效。要处理这种情况,可以在程序中只连接一台主服务器的从属服务器,而这台主服务器是在这个会话中已经连接上的。遗憾的是,这意味着我们需要用两倍的从属服务器来处理相同程度的负载,从而在一台主服务器以及它的从属服务器故障时,我们还能够继续使用剩下的主服务器和从属服务器来服务所有的流量。

任何其他的模式都只能提供更多读冗余,而不能为写提供任何冗余——如果一台主服务器出现故障,我们就不能再向那个集群写入数据了。想要能提供进一步的冗余能力的配置,需要跳出复制系统,借助于其他办法。

复制延迟

Replication Lag

我们已经接触到了复制延迟的问题,但到目前为止,还没有处理过这个问题。复制延迟是在主服务器上执行的一个事件得以在一台从属服务器上执行所需的时间。对于处于空闲状态的系统,这通常只需要毫秒级的时间,但随着网络流量增加和从属服务器读负载的加重,事件可能需要花费一定时间才能抵达从属服务器。

复制处于延迟状态时,很容易造成读取内容的失效,如图9-14所示。

用户提交了应用程序中某处的一张表格,并且我们对主服务器执行了一个写操作。接着这个用户被重定向,以查看他提交的结果,并且这个结果是从从属服务器读取的。如果这个读操作和写操作之间间隔过小,这个写操作有可能还没有被复制到从属服务器,这样就读取了过时的数据。

对于用户而言,这看起来就像他们所做的更改消失了。最有可能的用户行为就是重新执行

图9-14:缓慢的复制导致的失效数据

刚才的操作导致另一次写和读操作。这次读可能会读取失效数据,也可能正好赶上了上一次写操作的结果。这个用户行为会让问题进一步恶化,因为它增加了读和写的负载——在负载加重时导致恶性循环。

有不少办法可以处理这个问题:我们可以使用同步复制(这意味着在进一步工作之前,必须对所有的服务器执行写操作),或者降低负载以保持复制的速度。前一种方式需要特殊的数据库技术(比如MySQL5的NDB,很快就会讨论到这个问题),或者需要自己来实现一种技术(在本章快要结束之时,我们会讨论这个问题)。

和查询速度以及通常的机器统计数据一样,复制延迟也是需要监视的一个重要统计数据,在第10章中,会探讨各种对它进行跟踪的方法。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论