高性能MySQL --- 读书笔记(2) - 2016/8/2

第1章  MySQL架构

  MySQL架构与其他数据库服务器大不相同,这使它能够适应广泛的应用。MySQL足够灵活,能适应高要求架构。例如Web应用,同时还适用于嵌入式应用、数据仓库、内容索引和分发软件、高可用的冗余系统、联机事务处理系统OLTP及很多其他应用类型。

  为了充分发挥MySQL的性能,顺畅地使用它,就必须理解它的设计。MySQL的灵活性体现在很多方面,它可以再众多硬件平台上良好的配置和运行,还支持多种数据类型。不过MySQL最重要、最不同寻常的特征是它的存储引擎框架,这种架构可以讲查询处理(Query Processing)和各类服务器任务(Server Tasks)与数据的存储(Storage)/提取(Retrieval)相分离。这种分离特性使用户可以基于每张表来选择存储引擎,以满足对数据存储、性能特征及其他特性的各种需求。

  本章描述了MySQL服务器架构的总体架构、各种存储引擎间的主要区别,以及这种区别的重要性,并通过简化细节和介绍示例来讨论MySQL的原理,这种讨论极其有裨益。

1.1 MySQL的逻辑架构

  在头脑中构建出一幅MySQL各种组件如何协同工作的图像,有助于深入理解MySQL服务器。如下图展示了MySQL架构的逻辑视图:

  高性能MySQL --- 读书笔记(2) - 2016/8/2

  最顶层的各种服务并非MySQL独有。它们是许多基于网络的客户端/服务器(Client/server)工具或服务器都需要的服务,比如连接处理、授权认证、安全等。

  第2层值得关注。它包括了MySQL的大多数核心内容,比如查询解析、分析、优化、缓存及所有内建函数的代码。各种存储引擎提供的功能也集中在这层,如存储过程、触发器、视图等。

  第3层包含了存储引擎,存储引擎负责存储和提取所有存放在MySQL中的数据。每个存储引擎都有自己的优势和劣势。服务器通过存储引擎API与引擎进行通信。该接口隐藏了存储引擎之间的区别,使它们在查询层上是透明的。API包含了几十个底层函数,用来执行相关操作,如:“开始一个事务”,“提取一行拥有某主键的数据”等等。存储引擎不会进行SQL解析,也不会互相通信,它们只是简单地相应服务器的请求。

1.1.1 连接管理与安全性

  每个客户连接在服务器进程中都拥有自己的线程。每个连接所属的查询都会在制定的某个单独线程中完成。这些线程轮流运行在某个CPU核心或CPU上。服务器负责缓存线程,因此不需要为每个新的连接重建或撤销线程。

  当客户端(应用)连接到MySQL服务器时,服务器要对其进行认证,认证方式基于用户名、原始主机信息和口令。对于安全套接字层(SSL)方式的连接,还是用了X.509证书。一旦某个客户端成功连接服务器,服务器就会验证该客户端是否有权限执行某个具体查询。

1.1.2 优化与执行

  MySQL会解析查询,并创建一个内部数据结构(解析树),然后对其进行各种优化。其中包括重写查询,决定查询的读表顺序,以及选择须使用的索引等。用户可以通过特殊的关键字给优化器optimizer传递各种提示hint,影响它的决策过程descision-making process。另外还可以请求服务器给出优化过程的各种说明,使用户可以知晓服务器是如何优化决策的,为用户提供一个参考基准,方便用户重写查询、架构和修改相关配置,便于应用尽可能高效地运行。

  优化器并不关心某个表使用哪种存储引擎,但存储引擎对服务器的查询优化过程有影响。优化器会请求存储引擎,为某种具体操作提供性能与开销方面的信息,以及表内数据的统计信息。例如,某些存储引擎可以支持对某类查询更有利的索引类型。

  在解析查询之前,服务器会“询问”查询缓存,它只能保存SELECT语句和相应的结果。如果能在缓存中找到将要执行的查询,服务器就不必重新解析、优化或重新执行查询,只须直接返回已有结果即可。

1.2 并发控制

  只要不止一个查询同时修改数据,都会产生并发控制问题。本章的目的是讨论MySQL在两个层面的并发精致:服务器层与存储引擎层。并发控制是一个内容庞大的主题,本章只给出一个概念,描述MySQL如何处理并发读取程序Concurrent Reader,以及并发写入程序Concurrent Writer。

  以Unix系统的email box应用为例,典型的mbox file的格式是非常简单的。在mbox邮箱中,所有邮件信息都是串行的,彼此首尾相连。这使得邮件系统可以很容易读取与分析邮件信息。投递邮件也很容易,只要在文件的末尾附加新的邮件内容即可。

  但是,加入两个进程在同一时间向同意邮箱投递邮件,那么会发生什么情况呢?显然,邮箱的数据会被破坏掉。两封邮件会被交叉地保存在邮箱文件的末尾。设计良好的邮箱投递系统可以使用锁定防止数据损坏。如果客户试图投递邮件,但邮箱是被锁住的,那么客户就必须等待,知道锁定解除才能投递自己的邮件。

  这种方案在实际应用环境中工作良好,但它不支持并发处理。因为在任意给定时间里,只有一个进程可以修改邮箱的数据。这种方法在高容量的邮箱系统中也是个问题。

1.2.1 读锁Read Lock/写锁Write Lock

  读取邮箱信息并不麻烦,即使多个用户并发读取同一邮箱,也不会有什么问题。因为操作不会造成任何修改,所以就不会出错。不过,假如一个程序正在读邮箱,另一个用户试图删除编号25的邮件,那将发生什么结果?结果可能是,某一正在读的用户报错退出,或者是他看到一幅与邮箱的实际状态不符的错误试图。所以为了安全起见,即使读邮箱也必须特别注意。

  可以把邮箱想象成数据库,把每封邮件想象成表中的行,就很容易发现,在这类场景里,问题都是类似的。如修改数据库表中的行,就十分类似于删除或修改邮件文件中的邮件信息。

  解决这类经典问题的方法时使用并发控制,并发控制的概念是很简单的。在处理并发读或者并发写时,系统会使用一套锁系统来解决问题,这种锁系统由两类锁组成,通常称之为共享锁shared lock和排他锁Exclusive lock,或者叫读锁Read Lock和写锁Write Lock。

  不用关系锁技术的具体实现方式,在这里描述相关锁概念如下:某一资源上的读锁是共享的,或者说是互不阻塞的。在同一时间,多个用户可以读取同一资源,而互不干扰。另一方面,写锁是排他的,也就是说,一个写锁会阻塞其他的读锁和写锁,这是出于安全策略的考虑,在给定时间里,只有一个用户能写入资源,以防止用户在写操作的同时其他用户读取同一资源。

  对数据库来说,随时随地都会发生锁定。当某一用户修改某一部分数据时,MySQL会禁止其他用户读取同一数据。大多时候,MySQL都是以透明的方式实现锁的内部管理。

1.2.2 锁粒度Lock Granularity

  一种提高共享资源并发性的方法就是让锁定对象更有选择性。要记住只锁定部分须修改的数据,而不是所有的资源。更理想的方式是,只对要修改的数据片精确枷锁。任何时间,在给定的资源上,被加锁的数据量越小,就可以允许更多的并发修改,只要相互之间互不冲突即可。

  这么做的问题是加锁也会消耗系统的资源。每一种锁操作,如获得锁、检查锁是否已解除,以及释放锁等,都会增加系统的开销。如果系统花费大量时间来管理锁,而不是读/写数据,那么系统整体性能可能会因此受到影响。

  所谓的锁策略,就是在锁开销和数据安全之间寻求一种平衡,这种平衡也能影响系统性能。大多数的商业数据库服务器没有提供更多的选择,通常都是在表上施加行级锁Row-Level Locking,并提供种种复杂得手段,在有锁的情况下改善系统的性能。  

  而另一方面,MySQL则提供了多种选择。每种MySQL存储引擎都可以实现独有的锁策略Locking Policy或锁粒度Lock Granularity。在存储引擎设计中,锁管理Lock Management是个非常重要的议题。将锁粒度调整到某一水平,也许就能为某种应用目的提供更加的性能。不过,这也可能使存储引擎又不适用于其他的用途了。由于MySQL可以提供多种存储引擎,所以它不需要一个通用解决方案。下面将介绍两种最重要的锁策略。

  表锁Table Locks

  MySQL支持大多数基本的的锁策略,其中开销最小的锁策略是表锁。它将整个表加锁。当一个用户对表进行写操作(如插入、删除、更新)时,用户可以获得一个写锁。写锁会禁止其他任何用户的读/写操作。另外,只有五人做写操作时,用户才能获得读锁,读锁之间是互不冲突的。

  在特定的环境中,表锁可能性能良好。例如,READ LOCAL表锁支持某种类型的并发写操作。另外,写锁比读锁有更高的优先级,即使有读操作用户已排在队列中,一个被申请的写锁仍可以排列在锁队列的前列(写锁会被安置在读锁之前,而读锁不能排在写锁之前)。

  虽然存储引擎管理自己的锁,MySQL本身也能使用各种有效的表锁,以用于各种目的。例如,MySQL服务器可以再语句中,如ALTER TABLE语句中,使用表锁,而不用考虑存储引擎。

  行级锁Row Locks

  行级锁可以支持最大的并发处理(同时也带来最大的锁开销)。总所周知,行级锁在InnoDB和Falcon存储引擎中已得以实现,在其他一些存储引擎也有实现。行级锁由存储引擎实现,而不是由MySQL服务器实现。服务器完全不了解存储引擎里的锁实现方式。在本章的后续内容及全书中,所有存储引擎都是以自有方式实现加锁机制的。

1.3 事务

  事务是一组原子性的SQL查询语句,也可以被看做一个工作单元。如果数据库引擎能够成功地对数据应用所有的查询语句,它就会执行所有的查询。但是,如果任何一条查询语句因为崩溃或其他原因而无法执行,那么所有的语句都不会执行。也就是说,事务内的语句要么全部执行,要么一句也不执行。

  事务的内容并非专属于MySQL,学习了解ACID事务的概念。

  银行应用是一个经典案例。可以解释事务应用的必要性。假设银行数据库有两张表,checking表和saving表。现在要从支票账户Checking Account转账200美元到他的存储账户Savings Account,那么需要至少完成3步操作:

  1、检查支票存款账户的余额是否大于200$;

  2、从支票存储账户余额中减去200$;

  3、在存款账户余额中增加200$。

  所有的操作被打包在一个事务里,如果某一步失败,就回滚所有已完成步骤。

  可以用START TRANSACTION语句开始一个事务,用COMMIT语句提交整个事务,永久地修改数据,或者用ROLLBACK语句回滚整个事务,取消已做的修改。事务SQL样本如下:

START TRANSACTION;
SELECT balance FROM checking WHERE customer_id=10233276;
UPDATE checking SET balance=balance-200.00 WHERE customer_id=10233276;
UPDATE savings SET balance=balance+200.00 WHERE customer_id=10233276;
COMMIT;

  单纯的事务概念不是全部的主题。试想,如果数据库服务器在执行第4条语句时突然奔溃,会发生什么事?没人知道,但用户会损失200美元。再假如,在执行第3和第4条语句之间时,另一个进行同时运行,它的目的是要先删除支票存款账户的全部余额,那么结果可能是,银行根本不知道有这个逻辑先发事件,白白给用户增加了200美元存款。

  除非系统通过ACID测试,否则空谈事务概念是不够的。ACID代表了原子性Atomicity、一致性Consistency、隔离性Isolation和持久性Durability。这个学概念与事务的处理标准密切关联,一个有效的事务处理系统必然满足相关标准。

原子性Atomicity

  一个事务必须被视为一个单独的内部“不可分”的工作单元,以确保整个事务要么完全执行,要么全部回滚。当一个事务具有原子性时,该事务绝对不会被部分执行,那么完全执行,要么根本不执行。

一致性Consistency

  数据库总是从一种一致性状态转换到另一种一致性状态。在上述例子中,一致性确保了,即使数据库系统在执行第3、4条语句时崩溃了,支票存款账户也不会损失200美元。因为最终事务根本没有被提交,任何事务处理过程中所有的数据改变,也不会影响到数据库的内容。

隔离性Isolation

  某个事务的结果只有在完成之后才对其他事务可见。在上述例子中,当数据库执行完第3条语句,还未执行第4条语句时,如果此时银行汇总程序也同时运行,它将仍视转账的200美元仍在支票存款账户内,当后文讨论隔离级别时,读者就会理解为什么我们所说的通常是“不可见”Invisible的。

持久性Durability

  一旦一个事务提交,事务所做的数据改变将是永久的。这意味数据改变已被记录,及时系统崩溃,数据也不会因此丢失。持久性是个有点模糊的概念,因为实际上持久性也会分很多级别。有些持久性策略提供一种强壮的安全保证,另一些则未必。另外,也没有什么东西时100%永远持久的。

  ACID事务确保了银行不会弄丢你的钱。而这种特性在应用逻辑设计中很难实现的,甚至不可能实现。一个ACID兼容的数据库服务器,要为事务处理做大量的复杂工作,确保ACID特性的实现,而这也许是用户未能觉察的。

  正像锁粒度的增加会导致锁开销的增长一样,这种事务处理中的额外安全措施,也导致数据库服务器要完成更多的额外工作。通常,一个支持ACID特性的数据库,相对于不支持这种特性的数据库,需要更强的CPU处理能力、更大的内存和很多的磁盘空间。正如本章不断重复的,这正是选用MySQL存储引擎架构的有利之处。用户可以根据应用是否需要事务处理,选择相应的存储引擎。如果对于某些类型的数据查询,用户不需要真正的事务处理,可以选择一个非事务处理型的存储引擎来实现查询,以获得更高的处理性能。用户也可以使用LOCK TABLES语句,为应用提供某种级别的数据保护,而这些选择完全由用户自主决定。

1.3.1 隔离级别

  隔离的问题比想象的要复杂。SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

  下面简单介绍四种隔离级:

READ UNCOMMITTED(读取未提交内容)

  在READ UNCOMMITTED隔离级,所有事务都可以“看到”未提交事务的执行结果。在这种级别上,啃会产生很多问题,除非用户真的知道自己在做什么,并有很好的理由选择这样做。本隔离级很少用于实际应用,因为它的性能也不必其他级别好多少,而别的级别还有其他更多的有点。读取未提交数据,也被称之为“脏读”Dirty Read。

READ COMMITTED(读取提交内容)

  大多数数据库系统的默认隔离级是READ COMMITTED(但这不是MySQL默认的!)。它满足了隔离的早先简单定义:一个事务在开始时,只能“看见”已经提交事务所做的改变,一个事务从开始到提交前,所做的任何数据改变都是不可见的,除非已经提交。这种隔离级别也支持所谓的“不可重复读”Nonrepeatable Read。这意味着用户运行同一语句两次,看到的结果是不同的。

REPEATABLE READ(可重读)

  REPEATABLE READ隔离级解决了READ UNCOMMITTED隔离级导致的问题。它确保同一事务的多个实例在并发读取数据时,会“看到同样的”数据行。不过理论上,这回导致另一个棘手的问题:幻读Phantom Read。简单来说,幻读指当用户读取某一范围的数据行时,另一个事务又再该范围内插入新行,当用户再读取该范围的数据行时,会发现有新的“幻影”行。InnoDB和Falcon存储引擎通过多版本并发控制机制解决了幻读问题。

  REPEATABLE READ是MySQL的默认事务隔离级别。InnoDB和Falcon存储引擎都遵循这种设置。

SERIALIZABLE(可串行化)

  SERIALIZABLE是*别的隔离级,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,SERIALIZABLE是在每个读的数据行上加锁。在这个级别,可能导致大量的超时Timeout现象和锁竞争现象。

  如下所示,总结了各种隔离级别及相关的缺点。

  高性能MySQL --- 读书笔记(2) - 2016/8/2

1.3.2 死锁

  死锁是指两个或多个事务在同一资源上互相占用,并请求加锁时,而导致的恶性循环现象。当多个事务以不同顺序视图加锁同一资源时,就会产生死锁。任何时间,多个事务同时加锁一个资源,一定产生死锁。例如,设想下列两个事务同时处理StockPrice表:

-- 事务1
START TRANSACTION;
UPDATE StockPrice SET close=45.50 WHERE stock_id=4 AND date='2002-05-01';
UPDATE StockPrice SET close=19.80 WHERE stock_id=3 AND date='2002-05-02';
COMMIT; -- 事务2
START TRANSACTION;
UPDATE StockPrice SET high=20.12 WHERE stock_id=3 AND date='2002-05-02';
UPDATE StockPrice SET high=47.20 WHERE stock_id=4 AND date='2002-05-01';
COMMIT

  如果很不幸凑巧,每个事务咋处理过程中,都执行了第一个查询,更新了数据行,也加锁了数据行。接着,每个事务都去视图更新第二个数据行,却发现改行已经被对方加锁。然后两个事务都开始互相等待对方完成,陷入无限等待中,除非有外部因素介入,才能解除死锁。

  为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。对于更复杂的系统,例如InnoDB存储引擎,可以预知循环相关性,并立刻返回错误。这种解决方式实际很有效,否则死锁将导致很慢的查询。其他的解决方式,是让查询达到一个等到超时时间,然后再放弃争用,但这种方法不够好。目前InnoDB处理死锁的方法是,回滚拥有最少排他行级锁的事务。

  锁现象和锁顺序是因存储引擎而异的,某些存储引擎可能会因为使用某种顺序的语句导致死锁,其他的却不会。死锁现象具有双重性;有些是因真实的数据冲突产生的,无法避免,有些则是因为存储引擎的工作方式导致的。

  如果不以部分或全部的方式回滚某个事务,死锁将无法解除。在事务性的系统中,这是个无法更改的事实。

1.3.3 事务日志

  事务日志可使事务处理过程更加高效。和每次数据以改变就更新磁盘中表数据的方式不同,存储引擎可以先更新数据在内存中的拷贝。这非常快,然后,存储引擎将数据改变记录写入事务日志,它位于磁盘上,因此具有持久性。这相对较快,因为追加日志事件导致的写操作,只涉及了磁盘很小区域上的顺序I/O,而替代了写磁盘中表所需要的大量随机I/O。最后,相关进程会在某个时间把表数据更新到磁盘上。因此,大多数存储引擎都选用了这种技术,也是通常所说的预写式日志Write-Ahead Logging,利用两次磁盘写入操作把数据改变写入磁盘。

  如果数据更新已写入事务日志,却还未写入磁盘的表中,而系统发生崩溃,存储引擎将会在重启后回复相关数据改变,具体的恢复方式因存储引擎而异。

1.3.4 MySQL中的事务

  MySQL AB提供3个事务型存储引擎:InnoDB、NDB Cluster和Falcon。目前最知名的第三方事务性引擎是solidDB和PBXT。

AUTOCOMMIT(自动提交)

  MySQL默认操作模式是AUTOCOMMIT模式。这意味着除非显式地开始一个事务,否则它将把每个查询视为一个单独事务自动执行。在当前连接中,可以通过变量设置,启用Enable和禁用Disable AUTOCOMMIT模式:

SHOW VARIABLES LIKE 'AUTOCOMMIT';

  Variable_name  Value

  autocommit    on

  mysql>SET AUTOCOMMIT=1;

  值1和on是等效的,同样,0和OFF也是等效的。如果设置AUTOCOMMIT=0,用户将一直处于某个事务中,直到用户执行一条COMMIT或ROLLBACK语句,之后MySQL将立即开始一个新事务。对于非事务型的表,如MyISAM表或内存表(Memory Table),改变AUTOCOMMIT值没有意义,这些表本质上一直操作在AUTOCOMMIT模式。

  某些命令,在一个活动事务Open Transaction中一旦执行,会在这些事务显式提交之前,直接出发MySQL立即提交当前事务。典型的例子,如数据定义语言DDL命令,可以导致有效的数据改变,如ALTER TABLE命令,另外LOCK TABLES和其他一些语句也会有这种效果。

  MySQL允许使用SET TRANSACTION ISOLATION LEVEL命令设置隔离级,新的隔离级将在下一个事务开始后生效。用户可以在配置文件中,为整个服务器设置隔离级;或者使用下列命令,只为当前会话设置隔离级。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

  MySQL可以识别 所有的4个ANSI标准隔离级,InnoDB引擎也支持所有的隔离级,

在事务中混合使用存储引擎

  MySQL没有在服务器层管理事务,而是由下一层的存储引擎实现事务的处理。这意味着,在单一事务中,混合使用不同存储引擎并不可靠。

  在一个事务中,如果混合使用事务性表和非事务性表(例如InnoDB表和MyISAM表),假如事务处理一切顺利,那么结果也会正常。但是,如果事务必须回滚,那么在非事务性表上做的修改将无法取消。这将导致数据库处于数据不一致的状态。在这种状态下,很难对数据进行恢复,并且事务会变得悬而未决。这正好说明了为每个表选择正常的存储引擎多么重要。

  如果在一个非事务性表上进行事务性操作,MySQL通常不会给出警告或报错信息。

隐式和显式锁定

  InnoDB使用二相锁定协议。一个事务在执行过程中的任何时候,都可以获得锁,但只有在执行COMMIT或ROLLBACK语句后,才可以释放这些锁。它会同时释放掉所有锁。前文描述的锁定机制都是隐式锁定,InnoDB会根据用户的隔离级别,自动处理锁定。

  不过,InnoDB也支持显式锁定,例如以下语句:

SELECT ...... LOCK IN SHARE MODE
SELECT ...... FOR UPDATE

  MySQL也支持LOCK TABLES和UPLOCK TABLES命令。这些命令由MySQL服务器实现,而不是由存储引擎。如果需要事务性处理,应该选择使用事务性引擎。常常可以看到,一个应用从MyISAM引擎转换到InnoDB引擎后,仍然在使用LOCK TABLES命令,而这实际是没有必要的。因为InnoDB已经可以实现行级加锁,况且这种命令还会引起服务器的性能问题。

1.4 多版本并发控制

  大多数MySQL的事务性存储引擎,不是简单地使用行加锁的机制,而是选用一种多版本并发控制(MVCC,multiversion concurrency Control)的技术,和行加锁机制关联使用,以应对更多的并发处理问题。MVCC不是MySQL独有的技术,Oracle、PostgreSQL及其他一些数据库系统也使用同样的技术。

  可以将MVCC设想成一种行级加锁的变形,它避免了很多情况下的加锁操作,大大降低了系统开销。依赖于具体技术实现,它可以在读取期间锁定需要的记录的同时,还允许非锁定读取。

  MVCC是通过及时保存在某些时刻的数据快照,而得以实现的。这意味着同一事务的多个实例,在同时运行时,无论每个实例运行多久,它们看到的数据视图都是一致的;而同一时间,对于同一张表,不同事务看到的数据却是不同的!

  每种存储引擎实现MVCC的方式是不同的,例如乐观并发控制、悲观并发控制。下面通过描述InnoDB简化版的行为方式,举例说明MVCC的工作原理。

  InnoDB通过未每个数据行增加两个银行值的方式来实现MVCC。这两个隐含值记录了行的创建时间,以及它的过期时间(或者叫删除时间)。每一行都存储了事件发生时的系统版本号,用来替代时间发生时的实际时间。每一次,开始一个新事务时,版本号都会自动递增。每个事务都会保存它在开始时的“当前系统版本”的记录,而每个查询都会根据事务的版本号,检查每行数据的版本号。

  下面看一下,当事务隔离级设置为REPEATABLE READ时,MVCC在实际操作中的应用方式:

SELECT

  InnoDB检查每行数据,确保他们符合两个标准:

  InnoDB只查找版本早于当前事务版本的数据行,这确保了当前事务读取的行都是在事务开始前已经存在的,或者是由当前事务创建或修改的行。

  数据行的删除版本必须是未定义的,或是大于事务版本的,这保证了事务读取的行,在事务开始是未被删除的。

  只有通过上述两项测试的数据行,才会被当做查询结果返回。

INSERT

  InnoDB为每个新增行记录当前系统版本号。

DELETE

  InnoDB为个删除行记录当前系统版本号,作为行删除标识。

UPDATE

  InnoDB为每个需要更新的行,建立一个新的行拷贝,并且为新的行拷贝,记录当前的系统版本号。同时,也为更新前的旧行,记录系统的版本号,作为旧行的删除版本标识。

  保存这些额外记录的好处,是使大多数读操作都不必申请加锁,这使操作变得尽可能的快,因为读操作只要选取符合标准的行数据即可。这种方式的缺点是,存储引擎必须为每行数据,存储更多的额外数据,做更多的行检查工作,以及处理一些额外的整理操作。

  MVCC只工作在REPEATABLE READ和READ COMMITTED两个隔离级。

  如下汇总了MySQL中的各种加锁模型和并发级别。

  高性能MySQL --- 读书笔记(2) - 2016/8/2

1.5 MySQL的存储引擎

  在文件系统中,MySQL会把每个数据库(也叫做架构)保存为数据目录下的一个子目录。当创建一个表时,MySQL会在和表名同名的、以.frm为后缀的文件中存储表的定义。例如,当创建一个名为MyTable的表时,MySQL将在MyTable.frm中存储相关表定义。因为MySQL使用文件系统来存储数据库名和表定义,大小写敏感性将依赖于具体的平台。在Windows平台,MySQL的实例名、表明、数据库名都是大小写不敏感的;在Unix类平台,则是大小写敏感的。每种存储引擎对表数据和索引的存储方式有所不同,但表定义是由MySQL服务器独立处理的。

  如须获知具体每张表使用何种存储引擎,可以使用SHOW TABLE STATUS命令。例如,如果要检查MySQL数据库中的User表,可以执行下列命令:

  mysql>SHOW TABLE STATUS LIKE 'user' \G

Name:user
Engine:MyISAM
Row_format:Dynamic
Rows:6
Avg_row_length:59
Data_length:356
Max_data_length4294967295
Index_length:2048
Data_free:0
Auto_increment:NULL
Create_time:2002-01-24 18:07:17
Update_time:2002-01-24 18:07:59
Check_time:NULL
Collation:utf8_bin
Checksum:NULL
Create_options:
Comment:User and global privileges

  命令输出显示这是一个MyISAM类型表,在输出中还有其他很多信息和统计信息,下面简述每行输出的意义。

  name:表名。

  engine:表的存储引擎。

  row_format:行格式。对于MyISAM表,可能是Dynamic、Fixed和Compressed。动态行的行长度可变,因为他们包含有可变长的字段。例如VARCHAR或BLOB类型字段。固定行,是指行长度相同,由不可变长的字符段组成。例如CHAR和INTEGR字段。压缩行只存在于压缩表中。

  rows:表中的行数,对于非事务性表,这个值是精确的。对于事务性表,这通常是个估算值。

  avg_row_length:平均每行包括的字节数。

  data_length:整个表的数据量(以字节计算)。

  max_data_length:表可以容纳的最大数据量。

  index_length:索引数据占用磁盘空间的大小。

  data_free:对于MyISAM表,表示已分配,但现在未被使用的空间。这部分空间包含了以前被删除的行,这些空间可以用于以后的INSERT语句。

  auto_increment:下一个AUTO_INCREMENT值。

  create_time:表最初创建时的时间。

  update_time:表数据最近被更新的时间。

  check_time:使用CHECK TABLE命令检查表时的最近检查时间。

  collation:指本表中的默认字符集和字符列排序规则。

  checksum:如果启用,则对整个表的内容计算实时的校验。

  create_options:指标创建时的其他所有选项。

  comment:本字段包含了其他额外信息,对于MyISAM表,还包含了注释,如果有注释,将是在表创建时设定的。如果表使用InnoDB存储引擎,将显示InnoDB表空间的剩余空间。如果表是个视图,注释里将包含“VIEW”的文本字样。

1.5.1 MyISAM引擎

  作为MySQL的默认存储引擎,在性能和可用特征之间,MyISAM提供一种良好的平衡,这些特征包括全文见搜(Full-Text Indexing)、压缩、空间函数(GIS)。MyISAM不支持事务和行级锁。

存储

  一般来说,MyISAM将每个表存储成两个文件:数据文件和索引文件。两个文件的扩展名分别为.MYD和.MYI。MyISAM的格式是平台通用的。这意味着用户可以在不同架构的服务器上毫无疑问地互相拷贝数据文件和索引文件,

  MyISAM表可以包含动态行和静态行。MySQL会根据表定义决定选用何种方式。MyISAM表的可容纳的行总数,一般只受限于数据库服务器的可用磁盘空间大小,以及操作系统允许创建的最大文件大小。

  在MySQL5.0中,默认配置的含有可变长行定义的MyISAM表可以支持256TB的数据处理,并使用6字节的指针记录数据。如果想改变MyISAM表上的指针大小,必须在表创建选项MAX_ROWS和AVG_ROW_LENGTH中,指定相关的值,这些选项代表了用户预计使用的表大小。

CREATE TABLE mytable (
a INTEGER NOT NULL PRIMARY KEY,
b CHAR(18) NOT NULL
) MAX_ROWS=1000000000 AVG_ROW_LENGTH=32;

  这个例子中,要求MySQL为表数据至少准备32G的数据存储容量。

MyISAM特性:

加锁与并发:

  MyISAM对整张表进行加锁,而不是行。读取程序Reader在需要读取数据时,在所有表上都可以获得共享锁(读锁),而写入程序Writer可以获得排他锁(写锁)。用户在运行select查询时,可以在同一张表内插入新航(也称之为并发插入)。这是一个非常重要和有用的特征。

自动修复:

  MySQL支持对MyISAM表的自动检查和自动修复。

手工修复:

  用户可以使用CHECK TABLE mytable和REPAIR TABLE mytable命令,检查表中的错误,并修复错误。当服务器离线时,也可以使用myisamchk命令行工具检查和修复表。

索引特性:

  在MyISAM表中,用户可以基于BLOB或TEXT类型列的前500个字符,创建相关索引。支持全文索引,它可以根据个别单词,为复杂的搜索选项创建相关索引。

延迟更新索引:

  使用表创建选项DELAY_KEY_WRITE创建的MyISAM表,在查询结束后,不会将索引的改变数据写入磁盘,而是在内存的缓冲区中缓存索引噶边数据。它只会在情理缓冲区,或者关闭表时,才将索引块转储到磁盘。对于数据经常改变,并且使用频繁的表,这种模式大大提高了表的处理性能。不过,如果服务器或系统崩溃,索引将肯定损坏,并需要修复。

压缩的MyISAM表:

  某些表,例如基于CD-ROM或DVD-ROM的应用,或者某些嵌入环境下的应用,一旦被创建和填写数据后,数据将永不改变。适合选用压缩的MyISAM表。压缩表占用的磁盘空间很小,使之可以提供更快的表处理性能。因为对于查找记录,压缩后的表将减少磁盘寻道时间。压缩表也可以拥有索引,但这些索引也是只读的。  

1.5.2 MyISAM Merge引擎

  Merge引擎是MyISAM的变种。合并表Merge Table是指将几个相同的MyISAM表合并为一个虚表Virtual Table。它对于MySQL记录日志或数据仓库应用特别有用。

1.5.3 InnoDB引擎

  InnoDB转为事务处理设计的一款存储引擎,特别是用于处理大量短期(short-lived)事务。短期事务是指一般能正常完成,不需要回滚的事务。InnoDB是一种最广泛应用的事务性存储引擎。它的性能水平和崩溃后自动恢复特性,也让它在非事务性存储应用中很流行。

  InnoDB将所有数据共同存储在一个或几个数据文件中,这种数据文件一般称之为表空间Tablespace。表空间本质上是一种“黑盒”(Black Box)。在“黑盒”内,InnoDB自我管理一切数据。InnoDB也支持将每个表和相关索引存储为单独的分离文件。

  InnoDB使用MVCC机制获取高并发性能,并且实现所有四个标准隔离级。它的默认隔离级为REPEATABLE READ,在这个隔离级上,使用了间隙所策略防止“幻读”问题产生:不仅对查询中读取的行进行加锁,而且还对索引结构中的间隙(Gaps)进行加锁,以防止幻影插入。

  InnoDB表是基于聚簇索引建立的。InnoDB的索引结构,非常不同于其他大多数MySQL的存储引擎,因此,它能提供一种非常快速的主键查找性能。不过,它的辅助索引(也就是非主键索引)也会包含主键列,所以,如果主键定义的比较大,其他索引也将很大。如果想在表上定义很多索引,则争取尽量把主键定义得小一些。InnoDB不会压缩索引。

  InnoDB还不能根据排序Sorting建立索引,而MyISAM支持。因此,当InnoDB加载数据和创建索引时,要比MyISAM慢很多。任何改变InnoDB表结构的操作,会导致整个表的重建,包括重建所有的索引。  

  除了高并发处理能力,InnoDB另一个特征是它的外键约束。InnoDB做了各种内部优化。例如可预测性的预读,支持在磁盘中事先提取数据,可使用的哈希索引,支持在内存中自动创建哈希索引,可以实现极快的数据查找。还提供了插入缓冲区,支持快速插入。

1.5.4 Memory引擎

  如果用户想获得快速数据访问性能,并且数据永不改变,或者系统重启不需要保留,那么内存表Memory tables(之前称之为Heap  tables,即‘堆表’)是很有用的。一般来说,内存表要比MyISAM快一个数量级。内存表的所有数据都保存在内存中,所以相关查询根本不需要为此等待磁盘I/O处理。一旦系统重启,内存表的表结构仍然会保留,但原有的相关数据将丢失。

  内存表有几个很好的用途:

  用户查找(lookup)或映射表;

  用户缓存周期性聚合数据的结果;

  用户在数据分析中产生的中间结果。

  内存表支持哈希索引,这使它在查找查询中非常快速。虽然内存表非常快速,但它还不能完全等同通常意义上的基于磁盘存储的表。内存表使用表级加锁,只支持较低的写并发。它们只支持固定大小行,比如讲VARCHAR类型实际存储为CHAR类型,而这可能浪费不少内存。

  MySQL在处理保存中间数据的临时表时,也会在内部使用Memory引擎,对于内存表而言,如果中间数据非常大,MySQL会将其转换成MyISAM表,存储在磁盘上。

  人们常把内存表与临时表(Temporary Tables)混淆,临时表是指使用CREATE TEMPRARY TABLE创建的临时性的表,临时表可以使用任何存储引擎;它们与Memory引擎中使用的表不是同一个事物。临时表只在单个连接中可见,并且当连接断开时,也将不复存在。

1.5.5 Archive引擎

  Archive引擎只支持INSERT和SELECT查询,而不支持索引。Archive引擎因为缓冲了数据写操作,并在插入时使用zlib算法对每行数据进行了压缩,因此相对于MyISAM,它的磁盘I/O消耗更少。基于此引擎的任何SELECT查询都会导致全表扫描Full Table Scan。因此,归档表Archive Tables适合于数据记录和数据采集的应用。另外也适合于复制服务器上的快速数据插入。相对而言,在复制可以从服务器上使用其他的存储引擎,存储同样的表,这就意味着从服务器上的表可以拥有索引,在数据分析中性能会更快。

  Archive引擎支持行级加锁和专用的缓冲区系统,因而实现高并发的数据插入。在开始一个查询,并读取若干表内数据行后,Archive引擎通过阻断SELECT操作,来保证读操作的一致性。

1.5.6 CSV引擎

  CSV引擎可以将“逗号分隔值文件”作为表进行处理,但不支持在这种文件上简历索引。在服务器运行中,这种引擎支持从数据库中拷入/拷出CSV文件。如果从电子表格软件输出一个CSV文件,将其存放在MySQL服务器的数据Data目录中,服务器就能马上读取相关的CSV文件。同样,如果写数据到一个CSV表,外部程序也可以立刻读取它。在实现某种类型的日志记录时,CSV表作为一种数据交换格式,特别有用。

1.5.7 Federated引擎

  Federated引擎不在本地存储任何数据,每个联合表都代表了一张远程MySQL服务器上的表,对于任何操作,它都是连接到远程服务器上完成的。它特别适合于基于主键的单行数据查找,以及针对远程服务器的INSERT查询。

1.5.8 Blackhole引擎

1.5.9 NDB Cluster引擎

  2003年,索爱公司为MySQL AB提供了NDB Cluster群集引擎。它最初是为满足一种高速性能需求而设计的,同时支持冗余和负载均衡特性。虽然它也在磁盘上记录相关日志,但它将所有数据保存在内存中,并且优化了主键查找功能。MySQL还为此增加了一些索引方式和许多优化方式。

  NDB的体系结构是独特的:比如,一个NDB群集完全不同于一个Oracle群集。NDB的架构是基于无共享概念的。在这种架构中,不需要存储区域网络SAN或大型中心式存储解决方案。一个NDB的数据库由数据节点Data Node、管理节点Management Node和SQL节点MySQL实例组成。每个数据节点都保存了群集数据中的一段Segment数据。这些片段Fragment都是复制而来的。因此在不同节点上,系统会拥有同一数据的多份拷贝。一个屋里服务器通常只专用于一个节点,借此提供冗余特性和高可用性。从这种意义上,NDB有点像服务器级的RAID。

  管理节点用于获取Retrieve集中式配置,以及监控、控制所有群集节点。所有数据节点间彼此通信,并且所有MySQL服务器连接所有的数据节点。对于NDB群集架构,要求有低网络等待时间,这点至关重要。

  NDB群集是中非常酷的技术,绝对值得为了满足自己的好奇心而进行探索。举一个例子,目前NDB执行连接操作是MySQL服务器层次,而不是在存储引擎层次,因为所有NDB的数据都要经过网络进行检索,复杂得连接Join操作将会因此变得非常慢。另一方面说,单标上的查找可以非常快,因为多个数据节点都可以提供一部分数据查找内容。

1.5.10 Falcon引擎

  Falcon引擎是专为最新硬件所涉及的,它适合于拥有64位多处理器和大量内存的服务器,也适用于更先进的硬件环境。Falcon引擎使用MVCC技术,并试图在内存中完成全部事务处理操作,着使它在回滚操作和恢复操作方面速度非常快。

1.5.11 silidDB引擎

1.5.12 PBXT引擎

1.5.13 Maria存储引擎

1.5.14 其他存储引擎

1.5.15 选择合适的引擎

  在设计基于MySQL的应用时,要决定选择何种存储引擎来存储数据。如果不在设计阶段就考虑这个问题,在后续工作种可能就会面临复杂得问题。用户可能会发现默认配置的引擎并不能提供满足自己的某种需要。比如事务。或者,应用产生的混合读写查询需要更细粒度的加锁机制,而不是MyISAM提供的表锁。

  引擎的选择可以基于每一张表。因此,须要清醒考虑如何使用每张表,以及如何存储数据,这种考虑也有助于用户从整体上正确理解应用,并且预测应用的增长需求。理解和掌握相关信息,可以使用用户在选择引擎时,有一个良好的判断。

1.5.16 考虑的因素

  虽然有许多因素会影响存储引擎的选择,但通常可以归结为几个主要考虑因素。

事务:

  如果应用需要事务处理操作,InnoDb仍是最稳定的、良好集成的、已被证明的一种恰当选择。如果不需要事务处理,并且主要操作是处理一些SELECT或INSERT查询,那么MyISAM是一个很好的选择。另外,有时应用的某个专用部分(例如日志记录)也适合选用MyISAM引擎。

并发:

  如何更好地满足并发需求,要依据应用的工作负载而定。如果只是需要并发的插入操作和读操作,那么不论读者信与不信,MyISAM都是正确的选择。如果需要混合型的并发操作,并且操作之间要互不干扰,那么支持行级锁的引擎是更好的选择。

备份:

  常规备份需求也会影响表的选择。如果服务器可以周期性地停机,来进行备份,那么各种引擎都能很容易地实现备份功能。但如果需要这样或者那样的某种联机备份,那么选择可能就不简单了。另外,如果同时使用多种引擎,会使备份和服务器性能调整变得更加复杂。 

崩溃后恢复:

  如果有大量的数据,应该深中华考虑,系统崩溃后需要花多长时间才能恢复。比如通常来说,MyISAM表比InnoDB表更容易出错,恢复所需的时间也更长。事实上,很多人并不需要事务处理,也选择了InnoDB引擎,就是基于这个重要的原因。

特有特性:

  应用程序依赖于只有某些存储引擎才能提供的特殊特性或优化手段。例如,很多应用都依赖于聚集索引优化。在这种情况下,只有InnoDB和solidDB可以选择。另一方面,在MySQL中也只有MyISAM支持全文检索。

  对于存储引擎的选择,不必马上决定。在其他部分,还有大量内容介绍各种引擎的优劣之处,另外还有许多有关架构于设计方面的技巧提示。总的来说,还有许多选项需要进一步认识考虑,等待有更多的了解后,再回头审视这个问题,可能更有裨益。

1.5.17 实例

  通过了解一些普通数据库应用的特点,看看各种表的特性,以及如何为这些表选择最适合的引擎。

数据记录

  假设,来自于中心电话交换机的所有电话呼叫,都要使用MySQL进行实时数据记录Logging。或者Apache中已经安装了mod_log_Sql模块,可以讲web站点的所有访问信息直接记录到表中。那么在这类应用中,速度可能是最重要的设计指标,没人希望数据库因此陷入瓶颈状态。MyISAM引起和Archive引擎非常适合这类应用,因为它们消耗的系统的开销很小,并且支持每秒钟高达数千记录的插入。

  事情看起来可能很简单,不过,如果还要运行报告程序,对记录的数据进行汇总,那么基于这种查询,报告程序可能要提取大量的数据,而这将严重减缓数据记录的插入处理。如果出现这种情况,那该怎么办?

  一种解决方案是使用MySQL内置的复制特性,将相关数据克隆岛第二胎服务器(从服务器)上,再从服务器上运行那么对时间或CPU要求苛刻的程序。这可以将主服务器腾出手来,继续完成数据插入。同时,从服务器上运行任何查询,都不必担心影响实时的数据记录。

  当然,也可以选择在系统低负载时段,运行相关查询。不过随着应用规模的不断增长,这种策略是不可靠的,无法保证应用稳定运行。

  另一个选择是使用合并表Merge Table。调整应用,不是讲所有记录在同一张表里,而是将数据记录在不同表中。表名可以个根据年份、原表名和月份来区分,然后定义一个合并表,用于相关查询,合并表中包含了须汇总的所有数据。如果要按照日或按照周进行汇总,策略也是一样的,只须创建含有特定名称的表即可。在这种设计里,汇总程序可以再那些不再需要进行写操作的表上,运行相关汇总查询;而对于当前表中,可以继续不中断地实时记录数据。

只读表Read-Only Table或主读表Read-mostly Table

  有些表的数据用于编制目录catelog或分列清单。在这些表上的读操作远多于写操作。对于这类表,加入不介意MyISAM的崩溃问题,那么选择MyISAM是很合适的。不过不要低估崩溃问题的重要性。

  不要轻易相信MyISAM比InnoDB快之类的坊间说法,这往往并不绝对正确。后续章节可以了解更多影响存储引擎性能的各类因素(如数据大小、所需I/O操作的频率、主键和辅助索引等),以及这些因素对应用的影响。

订单处理:

  如果涉及任何订单处理,事务操作一般是必不可少的。否则,未完成订单是无法让客户满意的,另一个需要关注的问题时,引擎是否需要支持外键约束。InnoDB可能是订单处理应用的最佳选择。

股票报价:

  如果要收集股票报价信息用于分析,那么MyISAM是非常适用的。当然,选择这种yinqi8ng的注意事项也是一样的。不过,如果要支持一个高流量的web服务,提供实时的报价,并服务数千在线客户,那么查询过程是绝对不能容忍等待的。大量的客户可能需要同时读/写表,通常的解决方法时使用行级锁,或者在方案设计中减少更新操作的处理。

电子公告板和主题讨论论坛

1.5.18 存储引擎总结

 高性能MySQL --- 读书笔记(2) - 2016/8/2

1.5.19 表转换

  可以把表从一种引擎转移到另外一种引擎上,每种方法各有优缺点。下面介绍3中最常用的方法。

ALTER TABLE  

  把表从一种引擎转移到到另外一种引擎最简单的办法是使用ALTER TABLE语句。下列命令将表mytable转换成Falcon引擎的表。

  mysql>ALTER TABLE mytable ENGINE=Falcon;

  这种语法适合所有的存储引擎,不过这里有一个陷阱:这种转换过程会消耗大量时间。MySQL要为此执行一个旧表到新表的逐行复制Row-By-Row Copy。在这期间,转换操作可能会占用服务器的所有I/O处理能力,并且在转换时,原表需要被读加锁。因此,在一个繁忙的表上做此操作,要加以注意。作为替代手段,可以使用下一节介绍的方法,它会先做个表拷贝。

  如果从一种引擎到另一种引擎做表转换,所有属于原始引擎的专用特性都会丢失。

转储Dump和导入Import

  如果想对表转换的过程做更多控制,可以选择使用mysqldump工具。将表先转储(Dump)成一个文本文件,然后再编辑转储文件(Dump file),修改其中的CREATE TABLE语句。一定要注意修改表名和引擎类型,因为即便引擎类型有所不同,同一数据库中也不允许存在同一表名的两张表。另外,mysqldump在CREATE TABLE语句之前,会默认地加上DROP TABLE命令。如果不注意,可能因此丢失原有数据库。

CREATE和SELECT

  第3中转换技术,是在第1中方法的速度和第2中方法的安全性之间做了一个平衡。它不是转储整张表,或者一次性转换所有的数据,而是创建一个新表,使用MySQL的INSERT...SELECT语法来转移数据。

CREATE TABLE innodb_table like myisam_table;
ALTER TABLE innodb_table ENGINE=InnoDB;
INSERT INTO innodb_table SELECT * FROM myisam_table;

  如果数据量不大,这种办法效果不错。但是更高效的办法是增量地填充表,在填充每个增量数据块的时候都提交事务,这样就不会导致撤销日志(Undo Log)变得过于庞大。假定ID是主键,可以重复运行下列查询(每次逐次增大x和y的值),直至所有的数据都复制到新表。

START TRANSACTION;
INSERT INTO innodb_table SELECT * FROM myisam_table
WHERE id BETWEEN x AND y;
COMMIT;

  转移操作完成后,源表仍会保留,可以再操作完成后删除它,而此时,新表已经被填充完毕。注意,如有必要,请在转换时加锁源表,防止转移复制时数据不一致的问题。

  

上一篇:[iOS基础控件 - 6.12.4] NavigationController vs TabBarController


下一篇:MVC, MVP, MVVM比较以及区别