MySQL
2024-08-01 18:38:57 5 举报
AI智能生成
登录查看完整内容
MySQL是一个开源的关系型数据库管理系统,它使用SQL(结构化查询语言)来管理数据。MySQL支持大量的数据库类型,包括事务安全的InnoDB、用于高可用性和灾难恢复的Galera Cluster,以及用于高速缓存和查询的Memcached。它具有高性能、高可靠性和易于使用的特点,被广泛应用于各种规模的企业和项目中。MySQL可以通过命令行界面或图形用户界面进行操作,支持多种操作系统,包括Linux、Windows和macOS。
作者其他创作
大纲/内容
举个例子。上图是本人的MySQL的版本号
SELECT COUNT(*) FROM test_null;# 认为每个null都是不同的 4SELECT COUNT(`field`) FROM test_null; # 认为null是无效的 2SELECT * FROM test_null WHERE `field` IS NULL;# 认为每个null都是不同的 2
如何看待NULL
一棵B+树可以存放多少条数据
https://juejin.cn/post/6863770705897455623
什么是松散索引扫描
COUNT(*)、COUNT(1)有什么区别
倒排索引词条A:文档1,文档2词条B:文档1,文档3词条C:文档1,文档3词条D:文档2词条E:文档3倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。
正排索引文档1:词条A,词条B,词条C文档2:词条A,词条D文档3:词条B,词条C,词条E正排表是以文档的ID为关键字,表中记录文档中的每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。正排表的结构如图所示,这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于维护;因为索引是基于文档建立的,若是有新的文档加入,直接为文档建立一个新的索引块,挂接在原来的索引文件的后面,如果是有文档删除,则直接找到该文档号对应的索引信息,将其直接删除。尽管正排表的工作原理非常的简单,但是由于其检索效率太低,除非在特定情况下,否则实用性价值不大。
倒排索引和全文索引有什么区别?倒排索引和全文索引之间存在一些关键的区别,尽管它们在某些方面可能有重叠,以下是它们的主要区别:1.定义和目的1.1倒排索引定义:倒排索引是一种索引数据结构,用于存储文档中的词条及其在文档中的位置。它的核心是一个映射,将词条映射到包含这些词条的文档ID列表目的:主要用于快速检索包含特定词条的文档,非常适合搜索引擎中的关键词查询1.2 全文索引定义:全文索引是一种用于加速对文本文档内容进行搜索的索引结构。它通常包括倒排索引,但可能还包含其他结构和优化技术,如位置索引、词频等目的:提供对文本文档的全文搜索能力,支持复杂查询,如布尔搜索、短语搜索、相似度搜索等2.索引结构倒排索引:包含一个或多个词条,每个词条关联一个文档ID列表。文档ID列表可能还包含位置信息(即词条在文档中的具体位置)。例如```词条 \"apple\" -> [文档1,文档2,文档5]词条\"banana\" -> [文档2,文档3]```全文索引:除了倒排索引外,全文索引可能还包含其他数据结构和信息,哟关于优化查询性能和支持复杂查询。可能包含c词条的词频信息、词条的位置索引、同义词处理、词干处理等。例如:```词条\"apple\
倒排索引和正排索引的区别
概述。InnoDB是MySQL中一种常用的事务性存储引擎,它具有很多优秀的特性。其中,Doublewrite Buffer(双写缓冲区)是InnoDB的一个重要特性之一
Doublewrite Buffer相关参数1.innodb_doublewrite:Doublewrite Buffer是否启用开关,默认是开启状态,InnoDB将所有数据存储两次,首先到双写缓冲区,然后到实际数据文件2.innodb_dblwr_pages_written:记录写到DWB中的页数量3.innodb_dblwr_writes:记录DWB写操作的次数
总结.InnoDB Doublewrite Buffer是InnoDB的一个重要特性,用于保证MySQL数据的可靠性和一致性。它的实现原理是通过将要写入磁盘的数据先写入到DoublewriteBuffer中的内存缓存区域,然后再写入到磁盘的两个不同位置,来避免由于磁盘损坏等因素导致数据丢失或不一致的问题。DoublewriteBuffer对于保证MySQL数据的安全性和一致性具有重要意义
DoubleWriteBuffer的作用
https://github.com/Snailclimb/JavaGuide/issues/1763不可重复读:读的结果的行数不变或减少,结果的内容发生变化幻读:读的结果的行数变多了
不可重复读和幻读的区别
https://blog.csdn.net/u010648555/article/details/124011964
MySQL的默认排序规则
MySQL查看数据库各个表的占用空间大小
https://blog.csdn.net/ZK_J1994/article/details/72676862
扇区数和一簇
MySQL中,事务的操作日志,也就是对数据库所做的更改,通常是在事务提交时被记录到二进制日志(Binary Log)中的。二进制日志是一个重要的功能,它记录了所有更改数据的操作语句,不包括查询语句。这些日志对于数据库的备份、恢复、以及主从复制(Master-Slave Replication)等操作至关重要。在默认的`STATEMENT`格式下,MySQL服务器会在事务提交时将事务中的SQL语句记录到二进制日志中。如果使用的是`ROW`格式,那么改变的每一行数据都会被记录。还有一种格式是`MIXED`,这种格式是前两种格式的混合,MySQL会根据执行的SQL语句来决定使用哪种格式进行记录。二进制日志的写入时机可以通过配置项`sync_binlog`进行控制。如果`sync_binlog`设置为1,则每次事务提交时,MySQL都会将二进制日志同步到磁盘上,确保日志的持久性。如果设置为0,则依赖于操作系统的刷新机制,这可能会带来数据丢失的风险,因为如果在日志同步到磁盘之前发生系统故障,那么未同步的日志将会丢失。综上所述,事务的操作日志默认情况下会在事务提交时被记录到二进制日志中,具体时机还受到二进制日志格式和`sync_binlog`参数的影响。
MySQL如果要执行一个事务,这个事务的操作日志什么时候会被记录到二进制日志中?
innodb_flush_log_at_timeout
innodb_flush_log_at_trx_commit
在MySQL内部,在事务提交时利用两阶段提交(内部XA的两阶段提交)很好地解决了上面提到的binlog和redo log的一致性:1.第一阶段:InnoDB Prepare阶段。此时SQL已经成功执行,并生成事务ID(xid)信息及redo和undo的内存日志。此阶段InnoDB会写事务的redo log,但要注意的时,此时redo log只是记录了事务的所有操作日志,并没有记录提交(commit)日志,因此事务此时的状态为Prepare。此阶段对binlog不会有任何操作2.第二阶段:commit节点,这个阶段又分成两个步骤。第一步写binlog(先调用write()将binlog日志数据写入文件系统缓存,再调用fsync()将binlog文件系统缓存日志数据永久写入磁盘);第二步完成事务的提交(commit),此时在redo log中记录此事务的提交日志(增加commit标签)。还要注意的是,在这个过程中是以第二阶段中binlog的写入与否作为事务是否成功提交的标识,第一步写binlog完成,第二步redo log的commit未提交未完成,也算完成。可以看出,此过程中是先写redolog再写binlog的。但需要注意的是,在第一阶段并没有记录完整的redolog(不包含事务的commit标签),而是在第二阶段记录完binlog后再写入redolog的commit标签。当innodb的redolog记录,但是状态为Prepare时,尚未commit时,用户是查询不到该数据的
MySQL中六种日志文件,分别是重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log)、中继日志(relay log).其中重做日志和回滚日志与事务操作息息相关,二进制日志与事务操作有一定的关系,这三种日志,对理解MySQL的事务操作有着重要的意义。首先我们知道在MySQL中,二进制日志(binlog)是server层的,主要用来做主从复制和即时点恢复时使用的。而事务日志(redolog)是InnoDB存储引擎层的,用来保证事务安全的。你可能会有这样的疑问。1.为什么MySQL有binlog,还有redo log?2.事务是如何提交的?事务提交先写binlog还是redo log? 如何保证这两部分的日志做到顺序一致性?3.为了保障主从复制安全,故障恢复是如何做的?4.为什么需要保证二进制日志的写入顺序和InnoDB层事务提交顺序一致性呢?简单总结e二者关系:1.redolog用来恢复当前及其上的存储引擎中的数据,例如innodb2.binlog用来做复制同步的,让别的机器上的实例进行数据库操作
RedoLog日志和Binlog日志哪个先写的问题
为什么MySQL不能用binlog来进行崩溃恢复?
在MySQL中一条SQL语句的执行流程
InnoDB和MyISAM的区别
概述。和其他数据库系统相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥好的作用,但同时也会带来一点选择上的困难。MySQL并不完美,却足够灵活,能够适应高要求的环境,例如Web类应用。同时,MySQL既可以嵌入到应用程序中,也可以支持数据仓库、内容索引和部署软件、高可用的冗余系统、在线事务处理系统(OLTP)等各种应用类型。为了充分发徽MySQL的性能并顺利地使用,就必须理解其设计。MySQL的灵活性体现在很多方面。例如,你可以通过配置使它在不同的硬件上都运行得很好,也可以支持多种不同得数据类型。但是,MySQL最重要、最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理(Query Processing)及其他系统任务(Server Task)和数据的存储/提取相分离。这种处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求啦i选择数据存储的方式。
连接管理与安全性。每个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行,该线程只能轮流在某个CPU核心或者CPU中运行,服务器会负责缓存线程,因此不需要为每一个新键的连接创建或者销毁线程(MySQL5.5版本后提供了一个API,支持线程池(Thread-Pooling)插件,可以使用池中少量的线程来服务大量的连接)。当客户端(应用)连接到MySQL服务器时,服务器需要对其进行认证。认证基于用户名、原始主机信息和密码。如果使用了安全套接字(SSL)的方式连接,还可以使用X.509证书认证。一旦客户端连接成功,服务器会继续验证该客户端是否具有执行某个特定查询的权限(例如,是否允许客户端对world数据库的Country表执行SELECT语句)
优化与执行。MySQL会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。用户可以通过特殊的关键字提示(hint)优化器,影响它的决策过程。也可以请求优化器解释(explain)优化过程的各个因素,使用户可以知道服务器是如何进行优化决策的,并提供一个参考基准,便于用户重构查询和schema、修改相关配置,使应用尽可能高效运行。优化器并不关心表使用的是什么存储引擎,但存储引擎对于优化查询是有影响的。优化器会请求存储引擎提供容量或某个具体操作的开销信息,以及表数据的统计信息等。例如,某些存储引擎的某种索引,可能对一些特定的查询有优化。对于SELECT语句,在解析查询之前,服务器会先检查查询缓存(Query Cache),如果能够在其中找到对应的查询,服务器就不必再执行查询解析、优化和执行的整个过程,而是直接返回查询缓存中的结果集
MySQL逻辑架构。如果能在头脑中构建出一幅MySQL各组件之间如何协同工作的架构图,就会有助于深入理解MySQL服务器,如图所示,展示了MySQL逻辑架构图。最上层的服务并不是MySQL所独有,大多数基于网络的客户端/服务器的工具或者服务器都有类似的架构。比如连接处理、授权认证、安全等等。第二层架构是MySQL比较有意思的部分.大多数MySQL的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及所有的内置函数(例如,日期、时间、数学和加密函数),所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。和GNU/Linux下的各种文件系统一样,每个存储引擎都有它的优势和劣势。服务器通过API与存储引擎进行通信。这些接口屏蔽了不同存储引擎之间的差异,使得这些差异对上层的查询过程透明。存储引擎API包含几十个底层函数,用于执行诸如\"开始一个事务\"或者\"根据主键提取一行记录\
读写锁。从邮箱中读取数据没有这样的麻烦,即使同一个hi上课多个用户并发读取也不会有什么问题,因为读取不会修改数据,所以不会出错。但如果某个客户正在读取邮箱,同时另外一个用户视图删除编号为25的邮件,会产生什么结果?结论是不确定,读的客户可能会报错退出,也可能读取到不一致的邮箱数据。所以,为安全起见,即使是读取邮箱也需要特别注意。如果把上述的邮箱当称数据库的一张表,把邮件当成表中的一行记录,就很容易看出,同样的问题依然存在。从很多方面来说,邮箱就是一张简单的数据库表。修改数据库中的记录,和删除或者修改邮箱中的邮件信息,十分类似。解决这类经典问题的方法就是并发控制,其实非常简单。在处理并发读或者写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock),这里先不讨论锁的具体实现,描述一下锁的概念如下:读锁是共享的,或者说是相互不阻塞的。多个客户端在同一时刻可以同时读取同一个资源,而互不干扰。写锁则是排他的,也就是说一个写锁会阻塞其他的写锁和读锁,这是出于安全策略的考虑,只有这样,才能确保在给定的时间里,只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源。在实际的数据库系统中,每时每刻都在发生锁定,当某个用户在修改某一部分数据时,MySQL会通过锁定防止其他用户读取同一数据。大多数时候,MySQL锁的内部管理是透明的
行级锁(row lock)。行级锁可以最大程度地支持并发处理(同时也带来了最大的锁开销)。众所周知,在InnoDB和XtraDB,以及其他一些存储引擎种实现了行级锁。行级锁只在存储引擎层实现,而MySQL服务器层没有实现。服务器层完全不了解存储引擎中的锁实现
锁粒度。一种提高共享资源并发性的方式就是让锁定对象更有选择性。尽量只锁定需要修改的部分数据,而不是所有的资源。更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源山,锁定的数据量越少,则系统的并发程度越高,只要相互间不发生冲突即可。问题是加锁也需要消耗资源,锁的各种操作,包括获得锁、检查锁是否已经解除、释放锁等,都会增加系统的开销。如果系统花费大量的时间来管理锁,而不是存取数据,那么系统的性能可能会因此受到影响。所谓的锁策略,就是在锁的开销和数据的安全性之间寻求平衡,这种平衡当然也会影响到性能。大多数商业数据库系统没有提供更多的选择,一般是在表上施加行级锁(row-level lock),并以各种复杂的方式来实现,以便在锁比较多的秦广下尽可能地提供更好的性能。而MySQL则提供了多种选择。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。在存储引擎的设计中,锁管理是个非常重要的决定。将锁粒度固定在某个级别,可以为某些特定的应用场景提供更好的性能,但同时却会失去对另外一些应用场景的良好支持。好在MySQL支持多个存储引擎的架构,所以不需要单一的通用解决方案
并发控制。无论何时,只要有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。这里我们讨论的只是MySQL的两个层面的并发控制:服务器层与存储引擎层。并发控制是一个内容庞大的话题,我们只是简要地分析MySQL如何控制并发读写。以Unix系统的email box为例,典型的mbox文件格式是非常简单的。一个mbox邮箱中的所有邮件都串行在一起,彼此首尾相连。这种格式对于读取和分析邮件信息非常友好,同时投递邮件也很容易,只要在文件末尾附加新的邮件内容即可。但如果两个进程在同一时刻对同一个邮箱投递邮件,会发生什么情况?显然,邮箱的数据会被破坏,两封邮件的内容会交叉地附加在邮箱文件的末尾。设计良好的邮箱邮递系统会通过锁(lock)来防止数据损坏。如果客户视图投递邮件,而邮箱已经被其他客户端锁住,那就必须等待,知道锁释放才能进行投递。这种锁的方案在实际应用环境中虽然工作良好,但并不支持并发处理。因为在任意一个时刻,只有一个进程可以修改邮箱的数据,这在大容量的邮箱系统中是个问题
隔离级别。隔离性其实比想象的要复杂。在SQL标准中定义了四种隔离几倍,每一种隔离级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。每种存储引擎实现的隔离级别不尽相同。下面简单地介绍一下四种隔离级别。1.READ UNCOMMITED(未提交读)在READ UNCOMMITTED级别,事务中地修改,即使没有提交,对其他事务也都是可见地。事务可以读取未提交的数据,这也被称为脏读(Dirty Read).这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的非常有必要,在实际应用中一般很少使用2.READ COMMITTED(提交读)大多数数据库系统的默认隔离级别都是READ COMMITTED(但MySQL 不是)。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务开始时,只能\"看见\"已经提交的事务所做的修改。换句话说,一个事务从开始知道提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。3.REPETABLE READ(可重复读)REPETABLE READ解决了脏读的问题,该级别保证了同一个事务中多次读取同样的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。可重复读是MySQL的默认事务隔离级别4.SERIALIZABLE(可串行化)SERIALIZABLE是最高的隔离几倍。她通过强制事务串行执行,避免了前面说的幻读问题。简单来说,SERIALIZABLE会在读取的每一行数据上都加上所,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别
死锁。死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定一个资源时,也会产生死锁。例如,设想下面两个事务同时处理StockPrice表:事务1:```sqlSTART 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:```sqlSTART 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;```如果凑巧,两个事务都执行了第一条UPDATE语句,更新了一行数据,同时也锁定了该行数据,接着每个事务都尝试去执行第二条UPDATE语句,却发现该行已经被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,则现如死循环。除非有外部因素介入才可能解除死锁。为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如InnoDB存储引擎,越能检测到死锁的循环一来,并立即返回一个错误。这种解决方式很有效,否则死锁会导致出现非常慢的查询。还有一种解决方式,就是当查询的时间达到锁等待超时的设定后放弃锁请求,这种方式通常来说不太好。InnoDB目前处理死锁的方法时,将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。锁的行为和顺序是和存储引擎相关的。以同样的顺序执行语句,有些存储引擎会产生死锁,有些则不会。死锁的产生有双重原因:有些是因为真正的数据冲突,这种情况通常很难避免,但有些完全是由于存储引擎的实现方式导致的。死锁放生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即可。
MySQL中的事务。MySQL提供了两种事务型的存储引擎:InnoDB和NDB Cluster。另外还有一些第三方存储引擎也支持事务,比较知名的包括XtraDB和PBXT.1.自动提交(AUTOCOMMIT):MySQL默认采用自动提交(AUTOCOMMIT)模式。也就是说,如果不是显式地开始一个事务,则每个查询都被当作一个事务执行提交操作。在当前连接中,可以通过设置AUTOCOMMIT变量来启用或者禁用自动提交模式:```cmysql > SHOW VARIABLES LIKE 'AUTOCOMMIT';Variable_name:autocommitValue: ONmysql> SET AUTOCOMMIT = 1;```1或者ON表示启用,0或者OFF表示禁用。当AUTOCOMMIT=0时,所有地查询都是在一个事务中,直到显式地执行COMMIT提交或者ROLLBACK回滚,该事务结束,同时又开始了另一个新事务。修改AUTOCOMMIT对非事务型的表,比如MyISAM或者内存表,不会又任何影响。对这类表来说,没有COMMIT或者ROLLBACK的概念,也可以说是相当于一直处于AUTOCOMMIT启用的模式。另外还有一些命令,在执行之前回强制执行COMMIT提交当前的活动事务。典型的例子,在数据定义语言(DDL)中,如果是回导致大量数据改变的操作,比如ALTER TABLE,就是如此。另外还有LOCK TABLES等其他语句也会导致同样的结果。如果有需要,请检查对应版本的官方文档来确认所有可能导致自动提交的语句列表。MySQL可以通过执行SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别。新的隔离级别会在下一次事务开始的时候生效。可以在配置文件中设置整个数据库的隔离界别,也可以只改变当前会话的隔离级别:```sqlmysql>SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;```MySQL能够识别所有的4个ANSI隔离级别,InnoDB尹千影也支持所有的隔离界别。2.在事务中混合使用存储引擎:MySQL服务器层不管理事务,事务是由下层的存储引擎实现的。所以在同一个事务中,使用多种存储引擎是不可靠的。如果在事务中混合使用了事务型和非事务型的表(例如InnoDB和MyISAM表),在正常提交的情况下不会有什么问题,但如果该事务需要回滚,非事务型表上的变更就无法撤销,这会导致数据库处于不一致的状态,这种情况很难修复,事务的最终结果将无法确定。所以为每张表选择合适的存储引擎非常重要。在非事务型的表上执行事务相关操作的时候,MySQL通常不会发出提醒,也不会报错。有时候只有回滚的时候才会发出一个警告:\"某些非事务型的表上的变更不能被回滚\"。但大多数情况下,对非事务型表的操作都不会有提示。3.隐式和显式锁定:InnoDB采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有执行COMMIT或者ROLLBACK的时候才会释放,并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB会根据隔离级别在需要的时候自动加锁。另外,InnoDB也支持通过特定的语句进行显式锁定,这些语句不属于SQL规范(这些锁定提示经常被滥用,实际上应当尽量避免使用)3.1 SELECT ... LOCK IN SHARE MODE3.2 SELECT ... FOR UPDATEMySQL也支持LOCK TABLES和UNLOCK TABLES语句,这是在服务器层实现的,和存储引擎无关。它们有自己的用途,但并不能替代事务处理。如果应用需要用到事务,还是应该选择事务型存储引擎。经常可以发现,应用已经将表从MyISAM转换到InnoDB,但还是显式地使用LOCK TABLES语句。这不但没有必要,还会严重影响性能,实际上InnoDB的行级锁工作得更好。LOCK TABLES和事务之间相互影响得化,情况会变得非常复杂,在某些MySQL版本中甚至会产生无法预料的结果。因此,建议除了事务中禁用AUTOCOMMIT,可以使用LOCK TABLES之外,其他任何时候都不要显式地执行LOCK TABLES,不管使用的是什么存储引擎。
InnoDB三大特性:1.AHI 自适应哈希2.双写机制3.Buffer Pool
字段含义:Name:表名Engine:表的存储引擎类型。在旧版本中,该列的名字叫Type,而不是Engine.Row_format:行的格式。可选的值为Dynamic、Fixed或者Compressed.Dynamic的行长度是可变,一般包含可变长度的字段,如VARCHAR或BLOB.Fixed的行长度则是固定的,只包含固定长度的列,如CHAR和INTERGER.Compressed的行则只在压缩表中存在。Rows:表中的行数。对于MyISAM和其他一些存储引擎,该值是精确的,但对于InnoDB该值是估计值。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命令或者myisamchk工具最后一次检查表的时间Collation:表的默认字符集和字符列排序规则Checksum:如果启用,保存的是整个表 的实时校验和Create_options:创建表时指定的其他选项Comment:该列包含了一些其他的额外信息。对于MyISAM表,保存的时表在创建时带的注释。对于InnoDB表,则保存的时InnoDB表空间的剩余空间信息。如果是一个视图,则该列包含\"VIEW\"的文本字样
InnoDB存储引擎.InnoDB是MySQL的默认事务型引擎,也是最重要、使用最广泛的存储引擎。它被设计用来处理大量的短期(short-lived)事务,短期事务大部分情况是正常提交的,很少会被回滚。InnoDB的性能和自动崩溃回复特性,使得它在非事务型存储的需求中也很流行。除非有特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎,如果要学习存储引擎,InnoDB也是一个非常好的值得花最多的时间去深入学习的对象,收益肯定比将时间平均花在每个存储引擎的学习上要高得多。InnoDB概览:InnoDB的数据存储在表空间(tablespace)中,表空间是InnoDB管理的一个黑盒子,由一系列的数据文件组成。在MySQL4.1以后的版本中,InnoDB可以将每个表的数据和索引存放在单独的文件中。InnoDB也可以使用裸设备作为表空间的存储节值,但现代的文件系统使得裸设备不再是必要的选择。InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。其默认级别是REPEATABLE READ(可重复读),并且通过间隙锁(next-key- locking)策略防止出现幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。InnoDB表是基于聚簇索引建立的,InnoDB的索引结构和MySQL的其他存储引擎有很大的不同,聚簇索引对主键查询有很高的性能。不过它的二级索引(secondary index,非逐渐索引)中必须包含主键列,所以如果主键列很大的话,其他的索引都会很大。因此,若表上的索引较多的话,主键应当尽可能的小。InnoDB的存储格式是平台独立的,也就是说可以将数据和索引文件从Intel平台复制到PowerPC或者Sun SPARC平台。font color=\"#a23735\
存储。MyISAM会将表存储在两个文件中:数据文件和索引文件,分别以.MYD和MYI为扩展名.MyISAM表可以包含动态或者静态(长度固定)行。MySQL会根据表的定义来决定采用何种行格式。MyISAM表可以存储的行记录数,一般受限于可用的磁盘空间,或者操作系统中单个文件的最大尺寸。在MySQL5.0中,MyISAM表如果是变长行,则默认配置只能处理256TB的数据,因为指向数据记录的指针长度是6个字节。而在更早的版本中,指针长度默认是4字节,所以只能处理4GB的数据,而所有的MySQL版本都支持8字节的指针。要改变MyISAM表指针的长度(调高或者调低),可以通过修改表的MAX_ROWS和AVG_ROW_LENGTH选项的值来实现,两者相乘就是表可能达到的最大大小。修改这两个参数会导致重建整个表和表的所有所i你,这可能需要很长的时间才能完成。
MyISAM压缩表。如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表或许适合采用MyISAM压缩表。可以使用myisampack对MyISAM表进行压缩(也叫打包pack).压缩表是不能进行修改的(除非先将表解除压缩,修改数据,然后再次压缩)。压缩表可以极大地减少磁盘空间占用,因此也可以减少磁盘I/O,从而提升查询性能。压缩表也支持索引,但索引也是只读的。以现在的硬件能力,对大多数应用场景,读取压缩表数据时的解压带来的开销影响并不大,而减少I/O带来的好处则要大得多。压缩时表中的记录时独立压缩的,所以读取单行的时候不需要去解压整个表(甚至也不解压行所在的整个页面)
MyISAM性能。MyISAM引擎设计简单,数据以紧密格式存储,所以在某些场景下的性能很好。MyISAM有一些服务器级别的性能扩展限制,比如对索引键缓冲区(key cache)的Mutex锁,MariaDB基于段(segment)的索引键缓冲区机制来避免该问题。但MySIAM最典型的性能问题还是表锁的问题,如果你发现所有的查询都长期处于\"Locked\"状态,那么毫无疑问表锁就是罪魁祸首
为什么MyISAM中的崩溃不能安全恢复?回答:MyISAM是MySQL数据库管理系统种的一个存储引擎,他曾是最常会用的默认存储引擎之一,早期版本中。MyISAM存储引擎的特点是速度快,对读取操作进行了优化,但在某些方面不如其他存储引擎(如InnoDB)健壮。MyISAM的一个已知问题是在崩溃恢复方面的脆弱性。MyISAM存储引擎的崩溃安全恢复问题主要源于一下几个方面:1.没有事务支持:MyISAM存储引擎不支持事务,这意味着如果在数据写入过程中发生系统崩溃或断电, 那么未完成的数据操作将会丢失,从而导致数据不一致2.表级锁定:MyISAM使用表级锁定,而不是行级锁定。在多用户环境中,这可能导致在一个长时间运行的写入操作过程中,如果发生崩溃,那么整个表可能会处于不一致状态3.更新操作:MyISAM在执行更新操作时,通常会直接在原数据文件上进行修改。如果更新操作未完成而系统崩溃,那么可能会留下部分更新过的数据,这会导致数据完整性问题4.缺乏自动崩溃恢复机制:与InnoDB等存储引擎不同,MyISAM没有自动的崩溃恢复机制。InnoDB使用事务日志(redo log)来保证即使在崩溃恢复后也能恢复到一致状态。MyISAM没有这样的机制,因此在崩溃后需要依赖MySQL的修复工具(如myisamchk)来恢复数据一致性,这个过程可能是手动执行的,并且有一定的风险5.数据文件和索引文件分离:MyISAM将数据文件和索引文件分开存储,这可能会导致在特定情况下,比如突然的电源故障,数据文件和索引文件之间的同步出现问题由于这些原因,MyISAM存储引擎子在需要高可靠性和自动崩溃恢复的场景中通常不是最佳选择。对于这些场景,InnoDB存储引擎提供了更好的支持,因为它支持事务、行级锁定、并发控制和自动崩溃恢复。随着MySQL的发展,MyISAM逐渐被InnoDB取代,成为新的默认存储引擎
为什么MyISAM的读取速度快呢?MyISAM的读取速度快主要是由于它的设计理念和存储特性,这些特性使得MyISAM在读取操作上非常高效,以下是一些关键因素:1.存储结构:MyISAM将数据存储在两个文件中,一个用于数据,另一个用户索引,这种分离使得MyISAM在读取数据时可以并行访问索引和数据文件,从而提高读取速度2.表级锁定:MyISAM使用表级锁定,这意味着在读取操作时,整个表会被锁定,从而避免了复杂的行级锁定带来的开销。在大量读取操作的场景中,表级锁定可以减少锁的开销,提高读取效率3.缓存机制:MyISAM有自己的缓存机制,可以缓存数据和索引。这意味着频繁访问的数据和索引可以保持在内存中,从而减少磁盘IO操作,提高读取速度4.优化读取操作:MyISAM存储引擎对读取操作进行了优化,例如,它支持全表扫描和索引扫描,这些操作在MyISAM中非常高效5.无事务支持:由于MyISAM不支持事务,它不需要维护事务日志,这减少了写入操作的开销,从而在读取操作上可以更加专注于性能优化6.简单性:MyISAM的设计相对简单,没有InnoDB那样的复杂机制(如MVCC多版本并发控制),因为MyISAM不都不支持行级锁,也不支持事务,也就没有所谓的事务版本号之说,这使得MyISAM在执行读取操作时可以更加直接和快速尽管MyISAM在读取操作上表现出色,但由于不支持事务、行级锁定和崩溃恢复的局限性,它在需要高并发、数据完整性和可靠性的应用场景中并不是最佳选择。对于这些场景,InnoDB通常是一个更好的选择,因为它提供了事务支持、行级锁定、并发控制和自动崩溃恢复等功能
MyISAM存储引擎。在MySQL5.1及之前的版本,MyISAM是默认的存储引擎。MyISAM是默认的存储引擎。MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务和行级锁,而且有一个毫无疑问的缺陷就是崩溃后无法安全回复。正是由于MyISAM引擎的缘故,即使MySQL支持事务已经很长时间了,在很多人的概念中MySQL还是非事务型的数据库。尽管MyISAM引擎不支持事务、不支持崩溃后的安全回复,但它绝不是一无是处的。对于只读的数据,或者表比较小、可以忍受修复(repair)操作,则依然可以继续使用MySQL(但请不要默认使用MyISAM,而是应当默认使用InnoDB)
MySQL内键的其他存储引擎。MySQL还有一些有特殊用途的存储引擎。在新版本中,有些可能因为一些原因已经不再支持;另外还有些会继续支持,但是需要明确地启用后才能使用。font color=\"#a23735\
面向列的存储引擎。MySQL默认是面向行的,每一行的数据是一起存储的,服务器的查询也是以行为单位处理的。而在大数据量处理时,面向列的方式可能效率更高。如果不需要整行的数据,面向列的方式可以传输更少的数据。如果每一列都单独存储,那么压缩的效率也会更高。Infobright是最有名的面向列的存储引擎,在非常大的数据量(数十TB)时,该引擎工作良好。Infobright是为数据分析和数据仓库应用设计的。数据高度压缩,按照块进行排序,每个块都对应一组元数据。在处理查询时,访问元数据可决定跳过该块,甚至可能只需要元数据即可满足查询的需求。但该引擎不支持索引,不过在这么大的数据量级,即使有索引也很难发挥作用,而且块结构也是一种准索引(quasi-index).Infobright需要对MySQL服务器做定制,因为一些地方需要修改以适应面向列存储的需要。如果查询无法在存储层使用面向列的模式执行,则需要在服务器层转换成按行处理,这个过程会很慢,Infobright有社区版和商业版两个版本。另外一个面向列的存储引擎时Calpont公司的InfiniDB,也有社区版和商业版。InfiniDB可以在一组机器集群间作分布式查询。
常见的引擎应用场景。1.日志型应用假设你需要实时地记录一台中心电话交换机的每一通电话的日志到MySQL中,或者通过Apache的mod_log_sql模块将网站的所有访问信息直接记录到表中。这一类应用的插入速度有很高的要求,数据库不能称为瓶颈.MyISAM或者Archive存储引擎对这类应用比较合适,因为它们开销低,而且插入速度非常快。如果需要对记录的日志做分析报表,那么事情就会变得有趣了。生成报表的SQL很有可能会导致插入效率明显降低,这时候该怎么办呢?一种解决办法,是利用MySQL内置的复制方案将数据复制一份到悲苦,然后在备库上执行比较消耗时间和CPU的查询。这样主库只用于高效的插入工作。而备库上执行的查询也无须担心影响到日志的插入性能。当然也可以在系统负载较低的时候执行报表查询操作,但应用在不断变化,如果依赖这个策略可能以后会导致问题。另外一种办法,在日志记录表的名字中包含年和月的信息,比如web_logs_2012_01或者web_logs_2012_jan.这样可以在已经没有插入操作的历史表上做频繁的查询操作,而不会干扰到最新的当前表上的插入操作2.只读或者大部分情况下只读的表。有些表的数据用于编制类目或者分列清单(如工作岗位、竞拍、不动产等),折中应用场景是典型的读多写少的业务。如果不介意MyISAM的崩溃恢复问题,选用MyISAM引擎是合适的。不过不要低估崩溃恢复问题的重要性,有些存储引擎不会保证将数据安全地写入到磁盘中,而许多用户实际上并不清楚这样有多大的风险(MyISAM只将数据写到内存中,然后等待操作系统定期地将数据刷出到磁盘上)一个值得推荐的方式,是在性能测试环境模拟真实的环境,芸一行应用,然后拔下电源模拟崩溃测试。对崩溃恢复的第一手测试经验是无价之宝,可以避免真的碰到崩溃时手足无措不要轻信\"MyISAM比InnoDB快\"之类的经验之谈,这个结论往往不是绝对的。在很多我们已知的场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是使用到聚簇索引,或者需要访问到数据都可以放入内存的应用。当设计上述类型的应用时,建议采用InnoDB。MyISAM引擎在一开始可能没有任何问题,但随者应用压力的上升,则可能迅速恶化。各种锁争用、崩溃后的数据丢失等问题都会随之而来。3.订单处理如果涉及订单处理,那么支持事务就是必要选项。半完成的订单是无法用来吸引用户的。另外一个重要的考虑点是存储引擎对外键的支持情况,InnoDB是订单处理类应用的最佳选择4.电子公告牌和主题讨论论坛。对于MySQL用户,主题讨论是个很有意思的话题。当前有成百上千的基于PHP或者Perl的免费系统可以支持主题讨论。其中大部分的数据库操作效率都不高,因为它们大多倾向于在一次请求中执行尽可能多的查询语句。另外还有部分系统设计为不采用数据库,当然也就无法利用到数据库提供的一些方便的特性。主题讨论区一般都有更新计数器,并且会为各个主题计算访问统计信息。多数应用只设计了几张表来保存所有的数据,所以核心表的读写压力可能非常大。为保证这些核心表的数据一致性,锁称为资源争用的主要因素。尽管有这些设计缺陷,但大多数应用在中低负载时可以工作得很好。如果Web站点得规模迅速扩展,流量随之猛增,则数据库访问可能变得非常慢。此时一个典型得解决方案是更改为支持更高读写得存储引擎,但有时用户会发现这么做反而导致系统变得更慢了。用户可能没有意识到这是由于某些特殊查询得缘故,典型的如:```sqlmysql> SELECT COUNT(*) FROM table;```问题就在于,不是所有的存储引擎运行上述查询都非常快:对于MyISAM确实会很快,但其他的可能都不行。每种存储引擎都能找出类似的对自己有利的例子5.CD-ROM应用如果要发布一个基于CD-ROM或者DVD-ROM并且使用MySQL数据文件的应用,可以考虑使用MyISAM表或者MyISAM压缩表,这样表之间可以隔离并且可以在不同介质上相互拷贝。MyISAM压缩表比未压缩的表要节约很多空间,但压缩表是只读的。在某些应用中这可能是个大问题。但如果数据放到只读介质的场景下,压缩表的只读特性就不是问题,就没有理由不采用压缩表了。6.大数据量什么样的数据量算大?我们创建或者管理的很多InnoDB数据库的数据量在3~5TB之间,或者更大,这是单台机器上的两,不是一个分片(shard)的量。这些系统运行得还不错,要做到这一点需要合理地选择硬件,做好物理设计,并未服务器地IO瓶颈做好规划。在这样的数据量下,如果采用MyISAM,崩溃后的恢复就是一个噩梦。如果数据量继续增长到10TB以上的级别,可能就需要建立数据仓库。Infobright是MySQL数据仓库最成功的解决方案,也有一些大数据库不适合Infobright,却可能适合TokuDB
选择合适的引擎。这么多存储引擎,我们怎么选择?大部分情况下,InnoDB都是正确的选择,所以Oracle在MySQL5.5版本时终于将InnoDB作为默认的存储引擎了。对于如何选择存储引擎,可以简单概括为一句话\"除非需要用到某些InnoDB不具备的特性,并且没有其他办法可以替代,否则都应该优先选择InnoDB引擎\"。例如,如果要用到全文索引,建议优先考虑InnoDB加上Sphinx的组合,而不是使用支持全文索引的MyISAM。当然如果不需要用到InnoDB的特性,同时其他引擎的特性能够更好地满足需求,也可以考虑一下其他存储引擎。举个例子,如果不在乎可扩展能力和并发能力,也不在乎崩溃后的数据丢失问题,却对InnoDB的空间占用过多比较敏感,这种场合下选择MyISAM就比较合适。除非万不得已,否则建议不要混合使用多种存储引擎,否则可能带来的一系列复杂的问题已经一些潜在的buf和边界问题。存储引擎层和服务器层的交互已经比较复杂,更不用说混合多个存储引擎了。至少,混合存储对一致性备份和服务器参数配置都带来了一些困难。如果应用需要不同的存储疫情,请先考虑一下几个因素。1.事务如果应用需要事务支持,那么InnoDB(或者XtraDB)是目前最稳定并且经过验证的选择。如果不需要事务,并且主要是SELECT和INSERT操作,那么MyISAM是不同的选择。一般日志行的应用比较符合这一特性。2.备份备份的需求也会影响存储引擎的选择。如果可以定期地关闭服务器来执行备份,那么备份的因素可以忽略。反之,如果需要在线热备份,那么选择InnoDB就是基本的要求3.崩溃恢复。数据量比较大的时候,系统崩溃后如何快速地恢复是一个需要考虑地问题。相对而言,MyISAM崩溃后发生损坏地概率比InnoDB要高很多,而且恢复速度也要慢。因此,即使不需要事务支持,很多人也选择InnoDB引擎,这是一个非常重要的因素。4.特有的特性.最后,有些应用可能一来一些存储引擎所独有的特性或者优化,比如很多应用一来聚簇索引的优化。另外,MySQL中也只有MyISAM支持地理空间搜索。如果一个存储引擎拥有一些关键的特性,同时却又缺乏一些必要的特性,那么有时候不得不做折中的考虑,或者在架构设计上做一些取舍。某些存储引擎无法直接支持的特性,有时候通过变通也可以满足需求。如果不了解具体的应用,上面提到的这些概念都是比较抽象的。所以接下来会讨论一些常见的应用场景,在这些场景中会涉及很多的表,以及这些表如何选用合适的存储引擎。
MySQL的存储引擎。这里只是概要地描述MySQL的存储引擎,而不会设计太多细节。在文件系统中,MySQL将每个数据库(也可以称之为schema)保存为数据目录下的一个子目录。创建表时,MySQL会在数据库子目录下创建一个和表同名的.frm文件保存的定义。例如创建一个名为MyTable的表,MySQL会在MyTable.frm文件中保存该表的定义。因为MySQL使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体的平台密切相关。在Windows中,大小写是不敏感的;而在类Unix中则是敏感的。不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在MySQL服务层统一处理。可以使用SHOW TABLE STATUS命令(在MySQL 5.0以后的版本中,也可以查询INFORMATION_SCHEMA中对应的表)显式表的相关信息。例如,对于mysql数据库中的user表:```sqlmysql> SHOW TABLE STATUS LIKE 'user' \\G*************************** 1. row *************************** Name: user Engine: InnoDB Version: 10 Row_format: Dynamic Rows: 6Avg_row_length: 2730 Data_length: 16384Max_data_length: 0 Index_length: 0 Data_free: 0 Auto_increment: NULL Create_time: 2023-08-15 23:04:22 Update_time: NULL Check_time: NULL Collation: utf8_bin Checksum: NULL Create_options: stats_persistent=0 Comment: Users and global privileges1 row in set (0.13 sec)```输出的结果表明,这是一个InnoDB表。输出中还有其他信息以及统计信息。
MySQL架构与历史
概述。最长碰到的三个性能相关的服务请求是:如何确认服务器是否达到了性能最佳的状态、找出某条语句为什么执行不够快、诊断被人们描述成\"停顿\"、\"堆积\"或者\"卡死\
理解性能剖析。MySQL的性能剖析(profile)将最重要的任务展示在前面,但有时候没显式出来的信息也很重要。不幸的是,尽管性能剖析输出了排名、总计和平均值,但还是有很多需要的信息是却是的,如下所示:1.值得优化的查询(worthwhile query)性能剖析不会自当给出那些查询值得花时间去优化。这把我们带回到优化的不呢一,如果你读过Cary Millsap的书,对此就会有很多的理解。这里我们要再次强调两点:第一,一些只占总响应时间比重很小的查询是不值得优化的。根据阿姆达尔定律(Amdahl's Law),对一个占总响应时间不超过5%的查询进行优化,无论如何努力,收益也不会超过5%.第二,如果花费了1000美元去优化一个任务,但业务的收入没有任何增加,那么可以说反而导致业务被逆优化了1000美元。如果优化的成本大于收益,就应当停止优化2.异常情况某些任务即使没有出现在性能剖析输出的前面也需要优化。比如某些任务执行次数很少,但每次执行都非常慢,严重影响用户体验。因为其执行频率低,所以总的响应时间占比并不突出。3.未知的未知一款好的性能剖析工具会显式可能的\"丢失的时间\"。丢失的时间指的是任务的总时间和实际测量到的时间之间的差。例如,如果处理器的CPU时间是10秒,二剖析到的任务总时间是9.7秒,那么就有300毫秒的丢失时间。这可能是有些任务没有测量到,也可能是由于测量的误差和精度问题的缘故。如果工具发现了这类问题,则要引起重视,因为有可能错过了某些重要的事情。即使性能剖析没有发现丢失时间,也需要注意考虑这类问题存在的可能性,这样才不会错过重要的信息。4.被掩藏的细节性能剖析无法显式所有响应时间的分布。只相信平均值是非常危险的,它会隐藏很多信息,而且无法表达全部情况。Peter经常举例说医院所有病人的平均提问没有任何价值。假如在前面的性能剖析的例子中,如果有两次查询的响应时间是1秒,而另外12771次查询的响应时间是几十微妙,结果会怎样?只从平均值里是无法发现两次1秒的查询的。要做出最好的决策,需要为性能剖析里输出的这一样中包含的12773次查询提供更多的信息,尤其是更多响应时间的信息,比如直方图、百分比、标准差、偏差指数。
通过性能剖析进行优化。一旦掌握并时间面向响应时间的优化方法,就会发现需要不断地对系统进行性能剖析(profiling).性能剖析是测量和分析时间花费在哪里的主要方法。性能剖析一般有两个步骤:测量任务所花费的时间;然后对结果进行统计和排序,将重要的任务排到前面。性能剖析工具的工作方式基本相同。在任务开始时启动计时器,在任务结束时停止计时器,然后用结束时间减去启动时间得到响应时间。也有些工具会记录任务的夫人吴。这些结果数据可以用来绘制调用关系图,但对于哦我们的目标来说更重要的时,可以将相似的任务分组并进行汇总。对相似的任务分组并进行汇总可以帮助对那些分到一组的任务做更复杂的统计分析,但至少需要知道每一组有多少任务,并计算出总的响应时间。通过性能剖析报告(profile report)可以获得需要的结果。性能剖析报告会列出所有的任务列表。每行记录一个任务,包括任务名、任务的执行时间、任务的消耗时间、任务的平均执行时间,以及该任务执行时间占全部时间的百分比。性能剖析报告会按照任务的消耗时间进行降序排序。我们将实际地讨论两种类型的性能剖析:基于执行时间的分析和基于等待的分析。基于执行时间的分析研究的是什么任务的执行时间最长,而基于等待的分析则是判断任务在什么地方被阻塞的时间最长。如果任务执行时间长是因为消耗了太多的资源且大部分时间花费在执行上,等待的时间不多,这种情况基于等待的分析作用就不大。反之亦然,如果任务一直在等待,没有消耗什么资源,去分析执行时间就不会有什么结果。如果不能确认问题是出在执行还是等待上,那么两种方式都需要试试。事实上,当基于执行时间的分析发现一个任务需要花费太多时间的时候,应该深入去分析一下,可能会发现某些\"执行时间\"实际上是在等待。例如,SELECT查询花费了大量时间,如果深入研究,则可能会发现时间都花费在等待IO上。在对系统进行性能剖析前,必须先要能够进行而苍凉,这需要系统测量化的支持。可测量的系统一般会有多个测量点可以捕获并手机数据,但实际系统很少可以做到可测量化。大部分系统都没有多少可测量点,即使有也只提供一些活动的技术,而没有活动花费的时间统计。```c# Profile# Rank Query ID Response time Calls R/Call V/M Ite# ==== ============================= ============== ===== ====== ===== ===# 1 0xFFFCA4D67EA0A788813031B8... 328.2315 90.4% 30520 0.0108 0.01 COMMIT# 2 0xB2249CB854EE3C2AD30AD7E3... 8.0186 2.2% 1208 0.0066 0.01 UPDATE sbtest?# 3 0xE81D0B3DB4FB31BC558CAEF5... 6.6346 1.8% 1639 0.0040 0.01 SELECT sbtest?# 4 0xDDBF88031795EC65EAB8A8A8... 5.5093 1.5% 756 0.0073 0.02 DELETE sbtest?# MISC 0xMISC 14.6011 4.0% 3334 0.0044 0.0 <13 ITEMS>```
性能剖析本身会导致服务器变慢吗?说\"是的\",是因为性能剖析确实会导致应用慢一点;说\"不是\",是因为性能剖析可以帮助应用运行得更快。性能剖析和定期检测都会带来额外开销。问题在于这部分得开销有多少,并且由此获得得收益是否能够抵消这些开销。大多数设计和构建过高性能应用程序得人相信,应该尽可能地测量一切可以测量地地方,并且接受这些测量带来的额外开销,这些开销应该被当称应用程序的一部分。Oracle的性能优化大师Tom Kyte曾被问到Oracle中的测量点的开销,他的回答是,测量点至少为性能优化贡献了10%。对此我们深表赞同,而且大多数应用并不需要要每天都运行详细的性能测量,所以实际贡献甚至要超过10%。即使不同这个观点,为应用构建一些可以永久使用的轻量级的性能剖析也是有意义的。如果系统没有每天变化的性能统计,则碰到无法提前预知的性能瓶颈就是一件头痛的事情,发现问题的时候,如果有历史数据,则这些历史数据价值是无限的。而且性能数据还可以帮助规划好硬件采购、资源分配、以及预测周期性的性能尖峰。那么何谓\"轻量级\
对应用程序进行性能剖析。对任何需要消耗时间的任务都可以做性能pixie,当然也包括应用程序。实际上,剖析应用程序一般比剖析数据库服务器容易,而且回报更多。虽然前面都是针对MySQL服务器的剖析,但对系统进行性能剖析还是建议自上而下地进行,这样可以追踪自用户发起到服务器响应的整个流程。虽然性能问题大多数情况下都和数据库有关,但应用导致的性能问题也不少。性能瓶颈可能有很多影响因素:1.外部资源,比如调用了外部的Web服务或者搜索引擎2.应用需要处理大量的数据,比如分析一个超大的XML文件3.在循环中执行昂贵的操作,比如滥用正则表达式4.使用了低效的算法,比如使用暴力搜索算法(naive search algorithm)来查找列表中的项幸运的是,确定MySQL的问题没有这么复杂,只需要一款应用程序的剖析工具即可(作为回报,一旦拥有这样的工具,就可以从一开始就写出高效的代码)。建议在所有的新项目中都考虑包含性能剖析得代码。往已有的项目中假如性能剖析代码也许很困难,新项目就简单一些。
捕获MySQL的查询到日志文件中。在MySQL中,慢查询最初只是捕获比较慢的查询,而性能剖析却需要针对所有的查询。而且在MySQL5.0及之前的版本中,慢查询日志的响应时间的单位是秒,粒度太粗了。幸运的是,这些限制都已经成为历史了。在MySQL5.1及更新的版本中,慢日志的功能已经被加强,可以通过设置long_query_time为0来捕获所有的查询,而且查询的响应时间单位已经可以做到微秒级。如果使用的是Percona Server,那么5.0版本就具备了这些特性,而且Percona Server提供了对日志内容和查询捕获的更多控制能力。在MySQL的当前版本中,慢查询日志是开销最低,精度最高的测量查询时间的工具。如果还在担心开启慢查询日志会带来额外的I/O开销,那大可放心。已经在IO密集型场景做过基准测试,慢查询日志带来的开销可以忽略不计(实际上在CPU密集型场景的影响还稍微大一些)。更需要担心的是日志可能消耗大量的磁盘空间。如果长期开启慢查询日志,注意要部署日志轮转(log rotation)工具。或者不要长期启用慢查询日志,只在需要手机负载样本的期间开启即可。MySQL还有另外一种查询日志,被称之为\"通用日志\",但很少用于分析和剖析服务器性能。通用日志在查询请求服务器时进行记录,所以不包含响应时间和执行计划等重要信息。MySQL5.1之后支持将日志记录到数据库的表中,但多数情况下这样做没什么必要。这不但对性能有较大影响,而且MySQL5.1在将慢查询记录到文件中时已经支持微秒级别的信息,然而将慢查询记录到表中会导致时间粒度退化为只能到秒级。而秒级别的慢查询日志没有太大的意义。Percona Server得慢查询日志比MySQL官方版本记录了更多细节且有价值得信息,如查询执行计划、锁、IO活动等。这些特性都是随着处理各种不同得优化场景得需求而慢慢加进来得。另外在可管理性上也进行了增强。比如全局修改针对每个连接的long_query_time的阈值,这样当应用使用连接池或者持久连接的时候,可以不用重置会话级别的变量而启动或者停止连接的查询日志。总的来说,慢查询日志是一种轻量而且功能全面的性能剖析工具,是优化服务器查询的利器。有时因为某些原因如权限不足等,无法在服务器上记录查询。这样的限制也常常碰到,所以官方开发了两种替代的技术,都集成到了Percona Toolkit中的pt-query-digest中。1.第一种是通过--processlist选项不断查看SHOW FULL PROCESSLIST的输出,记录查询第一次出现的时间和消失的时间。某些情况下这样的精度也足够发现问题,但却无法捕获所有的查询。一些执行较快的查询可能在两次执行的间隙就执行完成了,从而无法捕获到。2.第二种技术是通过抓取TCP网络包,然后根据MySQL的客户端/服务端通信协议进行解析。可以先通过tcpdump将网络包数据保存到磁盘,然后使用pt-query-digest的--type=tcpdump选项来解决并分析查询.此方法的精度比较高,并且可以捕获所有查询。还可以解析更高级的协议特性,比如可以解析二进制协议,从而创建并执行服务器预解析的语句(prepared statement)及压缩协议。另外还有一种方法,就是通过MySQL Proxy代理曾的脚本来记录所有查询,但在实践中很少这样做
剖析服务器负载。服务器端的剖析很有价值,因为在服务器端可以有效地审计效率低下的查询。定位和优化\"坏\"查询能够显著地提升应用的性能,也能解决某些特定的难题。还可以降低服务器的整体压力,这样所有的查询都将因为减少了对共享资源的争用而收益(\"间接的好处\")。降低服务器的负载也可以推迟或者避免升级更昂贵硬件的需求,还可以发现和定位糟糕的用户体验,比如某些极端情况.MySQL的每一个新版本中都增加了更多的可测量点。如果当前的趋势可靠的话,那么在性能方面比较重要的测量需求很快能够在全球范围内得到支持。但如果只是需要剖析并找出代价高的查询,就不需要如此复杂。有一个工具很早之前就能帮到我们了,这就是慢查询日志。
举个例子。```sqlmysql> SELECT * FROM chat_room ORDER BY id DESC LIMIT 10;10 rows in set (0.18 sec)mysql> SHOW PROFILES;+----------+------------+-------------------------------------------------------+| Query_ID | Duration | Query |+----------+------------+-------------------------------------------------------+| 1 | 0.00397750 | SHOW VARIABLES LIKE 'profiling' || 2 | 0.00010750 | SELECT * FROM chat ORDER BY id DESC LIMIT 100 || 3 | 0.00010100 | use chat || 4 | 0.00020625 | SELECT * FROM chat ORDER BY id DESC LIMIT 100 || 5 | 0.15971150 | SELECT * FROM chat_message ORDER BY id DESC LIMIT 100 || 6 | 0.00036200 | SELECT * FROM chat_message ORDER BY id DESC LIMIT 10 || 7 | 0.00037850 | SELECT * FROM chat_room ORDER BY id DESC LIMIT 10 |+----------+------------+-------------------------------------------------------+7 rows in set (0.17 sec)```该查询返回了10行记录,花费了0.18秒。接下来是SHOW PROFILES。首先可以看到的是以很高的精度显示了查询的响应时间,这很好。MySQL客户端显示的时间只有两位小数,对于一些执行得很快的查询这样的精度是不够的。接下来继续看接下来的输出:```sqlmysql> SHOW PROFILE FOR QUERY 7;+----------------------+----------+| Status | Duration |+----------------------+----------+| starting | 0.000077 || checking permissions | 0.000007 || Opening tables | 0.000071 || init | 0.000016 || System lock | 0.000006 || optimizing | 0.000003 || statistics | 0.000008 || preparing | 0.000009 || Sorting result | 0.000003 || executing | 0.000002 || Sending data | 0.000123 || end | 0.000004 || query end | 0.000005 || closing tables | 0.000005 || freeing items | 0.000024 || cleaning up | 0.000016 |+----------------------+----------+16 rows in set (0.17 sec)```剖析报告给出了查询执行的每个步骤及其花费的时间,看结果很难快速地确定哪个步骤花费地时间最多。因为输出是按照执行顺序排序,而不是按花费的时间排序的——而实际上我们更关心的是花费了多少多少时间,这样才能知道哪些开销比较打。但不幸的是无法通过诸如ORDER BY 之类的命令重新排序。假如不适用SHOW PROFILE命令而是这届查询INFORMATION_SHCEMA中对应的表,则可以按照需要格式化输出如下方所示
使用SHOW PROFILE。SHOW PROFILE命令是在MySQL 5.1以后的版本中引入的,来源于开源社区中的Jeremy Cole的贡献。这是唯一一个在GA版本中包含的真正的查询剖析工具。默认是禁用地,还可以通过服务器变量在会话(连接)级别动态地修改。```sqlmysql> SELECT VERSION();+------------+| VERSION() |+------------+| 5.7.42-log |+------------+1 row in set (0.11 sec)mysql> SHOW VARIABLES LIKE 'profiling';+---------------+-------+| Variable_name | Value |+---------------+-------+| profiling | OFF |+---------------+-------+1 row in set (0.07 sec)mysql> SHOW PROFILE -> ;Empty set```然后再服务器上执行的所有语句,都会测量其耗费的时间和其他一些查询执行状态变更相关的数据。这个功能有一定的作用,而且最初的设计功能更强大,但未来版本中可能会被Performance Schema所取代,尽管如此,这个工具最有用的作用还是在语句执行期间剖析服务器的具体工作。当一条查询提交给服务器时,此工具会记录剖析信息到一张临时表,并且给查询赋予一个从1开始的整数标识符。
使用慢查询Percona Server对慢查询日志做了哪些改进?比如\"使用SHOW PROFILE\"执行相同查询后可以抓取到的结果```c# Time:110905 17:03:18# User@Host:root[root] @localhost[127.0.01]# Thread_id:7 Schema:saklia Last_errono:0 Killed:0# Query_time:0.166872 Lock_time:0.000552 Rows_sent:997 Rows_examined:24861 Rows_affected:0 Rows_read:997# Bytes_sent:216528 Tmp_tables:3 Tmp_disk_tables:2 Tmp_table_sizes:11627188# InnoDB_trx_id:191E# QC_Hit:NO Full_scan:Yes Full_jooin:No Tmp_table:Yes Tmp_table_on_disk:Yes# Filesort:yes Filesort_on_disk:No Merge_passes:0# InnoDB_IO_r_ops:0 InnoDB_IO_r_bytes:0 InnoDB_IO_r_wait:0.000000# InnoDB_rec_lock_wait:0.000000 InnoDB_queue_wait:0.000000# InnoDB_pages_distinct:20# PROFILE_VALUES ...Copying to tmp table:0.090623...SET timestamp=1315256598SELECT * FROM sakila.nice_but_slower_film_list;```从这里可以看到查询确实以供创建了三个临时表,其中两个是磁盘临时表。而SHOW PROFILE看起来则隐藏了信息(可能由于服务i去执行查询的方式有不一样地方造成的)。最后对该查询执行SHOW PROFILE的数据也会写入到日志中,所以在Percona Server中甚至可以记录SHOW PROFILE的细节信息。另外可以看到,慢查询日志中详细记录的条目包含了SHOW PROFILE和SHOW STATUS所有的输出,并且还有更多的信息。所以通过pt-query-digest发现\"坏\
剖析单条查询。在定位到需要优化的单条查询后,可以针对查询\"钻取\
剖析MySQL查询。对查询进行性能剖析有两种方式,每种方式都有各自的问题,可以剖析整个数据库服务器,这样可以分析出哪些查询时主要的压力来源.定位到具体需要优化的查询后,也可以钻取下去对这些查询进行单独的剖析,分析哪些子任务是响应时间的主要消耗者。
其他剖析工具。1.使用USER_STATISTICS表。Percona Server和MariaDB都引入了一些额外的对象级别使用统计的INFORMATION_SCHEMA表,这些最初是由Google开发的。这些表对于查询服务器各部分的实际使用情况非常有帮助。在一个大型企业中,DBA负责管理数据库,但其对开发缺少话语权,那么通过这些表就可以对数据库活动那个进行测量和审计,并且强制执行使用策略。对于像共享主机环境这样的多租户环境也同样有用。另外,在查找性能问题时,这些表也可以帮助找出数据库中什么地方花费了最多的时间,或者什么表或索引表使用得最皮肤那你,抑或最不频繁,下面就是这些表:```sqlmysql> SHOW TABLES FROM information_schema LIKE '%_STATISTICS';+---------------------------------------------+| Tables_in_information_schema (%_STATISTICS) |+---------------------------------------------+| TABLE_STATISTICS || INDEX_STATISTICS || IO_STATISTICS || PERF_STATISTICS |+---------------------------------------------+4 rows in set (0.15 sec)```有几个要点要说明一下:1.可以查找使用得最多或者使用得最少的表和索引,通过读取次数或者更新次数,或者两者一起排序2.可以查找出从未使用的索引,可以考虑删除之3.可以看看复制用户的CONNECTED_TIME和BUSY_TIME,以确认复制是否会很难跟上主库的进度2.使用stracestrace工具可以调查系统调用的情况,有好几种可以使用的方法,其中一种是计算系统调用的时间并打印出来:```cstrace -cfp $(pidof mysqld)% time seconds usecs/call calls errors syscall------ ----------- ----------- --------- --------- ---------------- 100.00 0.012014 6 202 12 read 00.00 0.000000 0 2 0 write 00.00 0.000000 0 4 0 ioctl 00.00 0.000000 0 1 0 mmap------ ----------- ----------- --------- --------- ----------------100.00 0.012014 209 12 total```read 系统调用被调用了 202 次,总共耗时大约 0.012 秒,每次调用平均用时 6 微秒,并且有 12 次错误。write 系统调用被调用了 2 次,没有错误发生。ioctl 系统调用被调用了 4 次,没有错误发生。mmap 系统调用被调用了 1 次,没有错误发生。这种用法和oprofile有点像。但是oprofile还可以剖析程序的内部符号,而不仅仅是系统调用。另外,strace拦截系统调用使用的是不同oprofile的技术,这会有一些不可预期性,开销也更大些。strace度量时使用的实际时间,而oprofile使用的是花费CPU周期。举个例子,当IO等待出现问题的时候,strace能将它们希纳是出来,因为它诸如read或者pread64这样的系统调用开始计时,直到调用结束。但opfile不会这样,因为IO系统调用并不会真正地消耗CPU周期,而只是等待IO完成而已。strace对像mysqld这样有大量线程的场景会产生一些副作用。当strace附加上去后,mysqld的运行会变得很慢,因此不适合在产品环境中使用。但在某些场景下还是相当有用的,Percona Toolkit中有一个叫作pt-ioprofile的工具就是使用strace来生成IO活动的剖析报告的。
总结。1.定义性能最有效的方法是响应时间2.如果无法测量就无法有效地优化,所以性能优化工作需要基于高质量、全方位及完整的响应时间测量3.测量的最佳开始点是应用程序,而不是数据库。即使问题出在底层的数据库,借助良好的测量也可以很容易地发现问题4.大多数系统无法完整地测量,测量有时候也会有错误的结果。但也可以想办法绕过一些限制,并得到好的结果(但是要能意识到所使用的方法的缺陷和不确定性在哪里)5.完整的测量会产生大量需要分析的数据,所以需要用到剖析器。这是最佳的工具,可以帮助将重要的问题冒泡到前面,这样就可以决定从哪里开始分析会比较好6.剖析报告是一种汇总信息,掩盖和丢弃了太多细节。而且它不会告诉你缺少了什么,所以完全依赖剖析报告也是不明智的7.有两种消耗时间的操作:工作或者等待。大多数剖析器只能测量因为工作而消耗的时间,所以等待分析有时候是很有用的补充,尤其是当CPU利用率很低但工作却一直无法完成的时候8.优化和提升是两回事。当继续提升的成本超过收益的时候,应当停止优化9.注意你的直觉,但应该只根据直觉来指导解决问题的思路,而不是用于确定系统的问题。决策应当尽量基于数据而不是感觉。总的来说,解决性能问题的方法首先是要澄清问题,然后选择合适的技术来解答这些问题。如果你想尝试提升服务器的总体性能,那么一个比较好的七点是将所有查询记录到日志中,然后利用pt-query-digest工具生成系统级别的剖析报告。如果是要追查某些性能低下的查询,记录和剖析得方法也会有帮助。可以把精力放在寻找哪些消耗时间最多的、导致了糟糕的用户体验的,或者那些高度变化的,抑或有奇怪的响应时间直方图的查询。当找到了这些\"坏\"查询时,要钻取pt-query-digest报告中包含的该查询的详细信息,或者使用SHOW PROFILE及其他诸如EXPLAIN这样的工具。如果找不到这些查询性能低下的原因,那么也可能时遇到了服务器级别的性能问题。这是,可以较高精度测量和回直服务器状态计数器的细节信息。如果通过这样的分析重现了问题,则应该通过同样的数据制定一个可靠的触发条件,来收集更多的诊断数据。多花费一点时间来确定可靠的触发条件,尽量避免漏检或者误报。如果已经可以捕获故障活动期间的数据,但还是无法找到其根本原因,则要么尝试捕获更多的数据,要么尝试寻求帮助。我们无法完整地测量工作系统,但说到底它们都是某种状态机,所以只要足够细心,逻辑清晰并且坚持下去,通常来说都能得到想要的结果。要注意的时不要把原因和结果搞混了,而且在确认问题之前也不要随便针对系统做变动。理论上纯粹的自顶向下的方法分析和详尽的测量只是理想的情况,而我们常常需要处理的是真实系统。真实系统是复杂且无法充分测量的,所以我们只能根据情况尽力而为。使用诸如pt-query-digest和MySQL企业监控器的查询分析其这样的工具并不完美,通常都不会给出问题根源的直接证据。但真的掌握了以后,已经足以完成大部分的优化诊断工作了。
服务器性能剖析
概述。良好的逻辑设计和物理设计是高性能的基石,应该根据系统将要执行的查询语句来设计schema,这往往需要权衡各种因素。例如,反范式的设计可以加快某些类型的查询,但同时可能使另一些类型的查询变慢。比如添加计数器和汇总表是一种很好的优化查询的方式,但这些表的维护成本可能会很高。MySQL独有的特性和实现细节对性能的影响也很大。
实数类型。实数是带有小数部分的数字。然而,它们不只是为了存储小数部分;也可以使用DECIMAL存储比BIGINT还大的整数。MySQL既支持精确类型,也支持不精确类型。FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算。如果需要直到浮点运算时怎么计算的,则需要研究所使用的平台的浮点数的具体实现。DECIMAL类型用于存储精确的小数。在MySQL 5.0和更高版本,DECIMAL类型支持精确计算。MySQL4.1及更早版本则使用浮点运算来实现DECIMAL的计算,这样做会因为精度损失导致一些奇怪的结果。在这些版本的MySQL中,DECIMAL只是一个\"存储类型\
数据如何存储取决于存储疫情,并非所有的存储引擎都会按照相同的方式处理定长和变长的字符串。Memory引擎只支持定长的行,即使有变长字段也会根据最大长度分配最大空间。不过,填充和截取空格的行为在不同存储引擎都是一样的,因为这是在MySQL服务器层进行处理的。与CHAR和VARCHAR类似的类型还有BINARY和VARBINARY,它们存储的是二进制字符串。二进制字符串跟常规字符串非常相似,但是二进制字符串存储的是字节码而不是字符。填充也不一样:MySQL填充BINARY采用的\\0(零字节)而不是空格,在检索时也不会去掉填充值。当需要存储二进制数据,并且希望MySQL使用字节码而不是字符进行比较时,这些类型是非常有用的。二进制比较的有时并不仅仅体现在大小写敏感上。MySQL比较BINARY字符串时,每次按一个字节,并且根据该字节的数值进行比较。因此,二进制比较字符比较简单得多,所以也就更快
慷慨是不明智的。使用VARCHAR(5)和VARCHAR(200)存储'hello'得空间开销是一样得。那么使用更短的列有什么优势吗?事实证明有很大的优势。更长的列会消耗更多的内存,因为MySQL通常会分配固定大小的内存块来保存内部值。尤其是使用内存临时表进行排序或操作时会特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。所以最好的策略是只分配真正需要的空间
字符串类型。MySQL支持多种字符串类型,每种类型还有很多变种。这些数据类型在4.1和5.0版本发生了很大的变化,使得情况更加复杂。从MySQL4.1开始,每个字符串列可以定义自己的字符集和排序规则,或者说校对规则(collation).这些东西会很大程度上影响性能。1.VARCHAR和CHAR类型VARCHAR和CHAR时两种最主要的字符串类型。不幸的时,很静精确地解释这些值是怎么存储在磁盘和内存中地,因为这跟存储引擎地具体实现有关。下面的描述假设使用的存储引擎是InnoDB和/或者MyISAM。先看看VARCHAR和CHAR值通常在磁盘上怎么存储。请注意,存储引擎存储CHAR或者VARCHAR值得方式在内存中和磁盘上可能不一样,所以MySQL服务器从存储引擎独处得值可能需要转换另一种存储格式。下面是关于两种类型的一些比较VARCHAR:VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间(例如,越短的字符串使用越少的空间)。有一种情况例外,如果MySQL表使用ROW_FORMAT=FIXED创建的话,每一行都会使用定长存储,这会很浪费空间。VARCHAR需要使用1或者2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节。则只使用1个字节表示,否则使用2个字节。假设采用latin1字符集,一个VARCHAR(10)的列需要11个字节的存储空间。VARCHAR(1000)的列则需要1002个字节,因为需要2个字节存储长度信息。VARCHAR节省了存储空间,所以对性能也有帮助。但是,由于行是变长的,在UPDATE时可能使行变得比原来更长,这就导致需要做额外的工作,如果一个行占用的空间增长,并且在页内没有更多的的空间可以存储,在这种情况下,不同的存储引擎的处理方式是不一样的。例如,MyISAM会将行拆成不同的片段存储,InnoDB则需要分裂页来使行可以放进页内。,其他一些存储引擎也许从不在原数据为止更新数据。下面这些情况下使用VARCHAR是合适的:字符串列的最大长度比平均长度大很多;列的更新很少,所以碎片不是问题;使用了像UTF-8这样的复杂的字符集,每个字符都使用不同的字节数进行存储。在5.0或者更高版本。MySQL在存储和检索时会保留末尾空格。但在4.1或更老版本,MySQL会剔除末尾空格。InnoDB则更灵活,它可以把过长的VARCHAR存储为BLOBCHAR:CHAR类型是定长的:MySQL总是根据定义的字符串长度分配足够的空间。当存储CHAR值时,MySQL会删除所有的末尾空格(在MySQL4.1和更老版本中VARCHAR也是这样实现的——也就是说这些版本中CHAR和VARCHAR在逻辑上是一样的,区别只是在存储格式上)。CHAR会根据需要采用空格进行填充以方便比较。CHAR适合存储很短的字符串,或者所有的值都接近同一个长度。例如CHAR非常适合存储密码的MD5值,因为这是一个定长的值。对于经常变更的数据,CHAR也比VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHAR比VARCHAR在存储空间上也更有效率。例如使用CHAR(1)来存储只有Y和N的值,如果采用单字节字符集(记住字符串长度不是字节数,是字符数,多字节字符集会需要更多的空间存储单个字符)只需要一个字节,但是VARCHAR(1)却需要两个字节,因为还有一个记录长度的额外字节。CHAR类型的这些行为可能有一点难以理解,下面通过一个具体的例子来说明。
查看Data_length
使用枚举类型(ENUM)代替字符串类型有时候可以使用枚举列代替常用的字符串类型。枚举列可以把一些不重复的字符串存储成一个预定义的集合。MySQL在存储枚举时非常紧凑,会根据列表值得数量压缩到一个或者两个字节中。MySQL会在内部将每个值在列表中得为止保存为整数,并且在表的.frm文件中保存\"数字-字符串\"映射关系的\"查找表\
日期和时间类型MySQL可以使用许多类型来保存日期和时间值,例如YEAR和DATE.MySQL能存储的最小时间粒度为秒(MariaDB支持微秒级别的时间类型)。但是MySQL也可以使用微秒级别的粒度进行临时运算,接下来会展示如何绕开这种存储限制。大部分时间类型都没有替代品,因此没有什么事最佳选择的问题。唯一的问题是保存日期和时间的时候需要做什么。MySQL提供两种相似的日期类型:DATETIME和TIMESTAMP。对于很多应用程序,它们都能工作,但是在某些场景,一个比另一个工作得好。1.DATETIME这个类型能保存大范围的值,从1001年到9999年,精度为秒。它把日期和时间封装到格式YYYYMMDDHHMMSS的整数中,与时区无关。使用8个字节的存储空间。默认情况下,MySQL以一种可排序的、无歧义的格式显式DATETIME值,例如\"2008-01-16 22:37:08\"。这是ANSI标准帝国一的日期和时间表示方法。2.TIMESTAMP就像它的名字一样,TIMESTAMP类型保存了从1970年1月1日午夜(格林尼治标准时间)以来的秒数,它和UNIX时间戳相同。TIMESTAMP只使用4个字节的存储空间,因此它的范围比DATETIME小得多:只能表示1970年到2038年。MySQL提供了FROM_UNIXTIME()函数把Unix时间戳转换为日期,并提供了UNIX_TIMESTAMP()函数把七日转换为Unix时间戳。MySQL4.1以及更新的版本按照DATETIME的方式格式化TIMESTAMP的值,但是MySQL4.0以及更老的版本不会在各个部分之间显式任何标点符号。这仅仅是显式格式上的区别,TIMESTAMP的存储格式在各个版本都是一样的。TIMESTAMP显式地值也依赖于时区。MySQL服务器、操作系统,以及客户端连接都有时区设置。因此,存储值为0地TIMESTAMP在美国东部时区显式为\"1969-12-31 19:00:00\
选择标识符(identifier)为表示列(identifier column)选择合适的数据类型非常重要。一般来说更有可能用标识符与其他值进行比较(例如,在关联操作中),或者通过标识列寻找其他列。标识列也可能在另外的表中作为外键使用,所以为标识列选择数据类型时,应当选择跟关联表中的对应列一样的类型(在相关的表中使用相同的数据类型是个好注意,因为这些列很可能在关联中使用)。当选择标识列的类型时,不仅仅需要考虑存储类型,还需要考虑MySQL对这种类型怎么执行计算和比较。例如,MySQL在内部使用整数存储ENUM和SET类型,然后在做比较操作时转换为字符串。一旦选定了一种类型,要确保在所有关联表种都是用同样的类型。类型之间需要精确匹配,包括像UNSIGNED这样的属性(如果使用的是InnoDB存储引擎,将不能在数据类型不是完全匹配的情况下创建外键,否则会有保存信息:\"ERROR 1005(HY000):Can't create table\
特殊类型数据。某些类型的数据并不直接与内置类型一致。低于秒级精度的时间戳就是一个例子。另外一个例子是一个IPv4地址。人们经常使用VARCHAR(15)列存储IP地址。然而,它们实际上是32位无符号整数。不是字符串。用小数点将地址分成四段的表示方法只是为了让人们阅读容易。所以应该用无符号整数存储IP地址。MySQL提供INET_ATON()和INET_NTOA()函数在这两种表示方法之间转换
MySQL schema设计种的陷阱。虽然有一些普遍的好或坏的设计原则,但也有一些问题是由MySQL的实现机制导致的,这意味着有可能犯一些只在MySQL下发生的特定错误。接下来我们讨论下设计MySQL的schema的问题。这也许会帮助你避免错误,并且选择在MySQL特定实现下工作得更好的替代方案。1.太多的列。MySQL的存储引擎API工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列。从行缓冲种将编码过的列转换成行数据结构的操作代价是非常高的。MyISAM的定长行结构实际上与服务器层的行结构正好匹配,所以不需要转换。然而,MyISAM的变长行结构实际和INnoDB的行结构则总是需要转换。转换的代价依赖于列的数量。有一个CPU占用非常高的例子,发现客户使用了非常宽的表(数千个字段),然而只有一小部分列会实际用到,这时转换的代价就非常高。如果计划使用数千个字段,必须意识到服务器的性能运行特征会有一些不同。2.太多的关联所谓的\"实体 - 属性 - 值\"(EAV)设计模式是一个常见的糟糕设计模式,尤其是在MySQL下不能靠谱地工作。MySQL限制了每个关联操作最多只能有61张表,但是EAV数据库需要许多自关联。不少EAV数据库最后超过了这个限制。事实上在许多关联少于61张表地情况下,解析和优化查询地代价也会成为MySQL的问题。一个粗略的经验法则,如果希望查询执行得快速且并发性好,单个查询最好在12个表以内做关联font color=\"#000000\
范式的优点和缺点。当为性能问题而寻求帮助时,经常会被建议对schema进行范式化设计,尤其是写密集的场景。这通常是个好建议。因为下面这些原因,范式化通常能够带来好处:1.范式化的更新操作通常比反范式化要快2.当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少地数据3.范式化地表通常更小,可以更好地放在内存里,所以执行操作会更快4.很少有多余地数据意味着检索列表数据时更少需要DISTINCT或者GROUP BY 语句还是前面地例子:在非范式化的结构种鄙视使用DISTINCT 或者GROUP BY 才能获得一份唯一的部门列表,如果部门(DEPARTMENT)是一张单独的表,则只需要简单的查询这张表就行了.范式化设计的schema的缺点是通常需要关联,稍微复杂一些的查询语句在符合范式的schema上都可能需要至少一次的关联,也许更多。这不但代价昂贵,也可能使一些索引策略无效。例如,范式化可能将列放在不同的表种,而这些列如果在一个表种本可以属于同一个索引
混用范式化和反范式化。范式化和反范式化的schema各有优劣,怎么选择最佳的设计?事实是,完全的范式化和完全的反范式化schema都是实验室里才有的东西:在真实世界中很少会这么极端地使用。在实际应用中经常需要混用,可能使用部分范式化地schema、缓存表以及其他技巧。最常见地反范式化数据地方法是复制或者缓存,在不同地表中存储相同地特定列。在MySQL5.0和更新版本中,可以使用触发器更新缓存值,这使得实现这样地方案变得更简单。在网站实例中,可以在user表和message表中都存储account_type字段,而不用完全地范式化。这避免了完全反范式化地插入和删除问题,因为即使没有消息地时候也绝不会丢失用户地信息。这样也不会把user_message表搞得太大,有利于高效地获取数据。但是现在更新用户的账户类型的操作代价就高了,因为需要同时更新两张表。至于这会不会是一个问题,需要考虑更新的频率以及更新的时长,并和执行SELECT查询的频率进行比较。另一个从附表冗余一些数据到子表的理由是排序的需要,例如,在范式化的schema里通过作者的名字对消息排序的代价将会非常高,但是如果在message表中缓存author_name字段并且建好索引,则可以非常高效地完成排序。缓存衍生值也是有用的。如果需要显示每个用户发了多少消息(像很多论坛做的),可以没执行一个昂贵的子查询来计算并显示它;也可以在user表中建一个num_messages列,每当用户发新消息时更新这个值。
范式和反范式。对于任何给定的数据通常都有很多种表示方法,从完全的范式化到完全的反范式化,以及两者的折中。在范式化的数据库种,每个事实数据会出现并且只出现一次。相反,在反范式化的数据库种,信息是冗余的,可能会存储在多个地方。下面以经典的\"雇员,部门,部门领导\"的例子开始:如图所示,这个schema的问题是修改数据时可能发生不一致。假如Say Brown解人Accounting部门的领导,需要修改多行数据来反应这个变化,这是很痛苦的事并且容易引入错误。如果“Jones”这一行显示部门的领导跟\"Brown\"这一行的不一样,就没办法直到哪个是对的。这就像有句老话说的:\"一个人有两块手标就永远不知道实践\"。此外,这个设计在没有雇员信息的情况下就无法表示一个部门——如果我们删除了所有Accounting部门的雇员,我们就失去了关于这个部门本身的所有记录。要避免这个问题,我们需要对这个表进行范式化,方式是拆分雇员和部门项。拆分以后可以用下面两张表来分别存储雇员表:这样设计的两张表符合第二范式,在很多情况下做到这一步已经足够好了,然而,第二范式只是许多可能的范式种的一种在这个例子种我们使用姓(Last Name)作为主键,因为这是数据的\"自然标识\"。从实践来看,无论如何都不应该这么用。这既不能保证唯一性,而且用一个很长的字符串作为主键是很糟糕的主意。
更快地读,更慢地写。为了提升读查询的速度,经常会需要建一些额外索引,增加冗余列,甚至是创建缓存表和汇总表。这些方法会增加写查询的负担,也需要额外的维护任务,但在设计高性能数据库时,这些都是常见的技巧:虽然写操作变得更慢了,但更显著地提高了读操作的性能然而,写操作变慢并不是读操作变得更快所付出的唯一代价,还可能同时增加了读操作和写操作的开发难度
缓存表和汇总表有时提升性能最好的方法是在同一张表中保存衍生的冗余数据。然而,有时也需要创建一张完全独立的汇总表或缓存表(特别是为满足检索的需求时)。如果能容许少量的脏数据,这是非常好的方法,但是有时确实没有选择的余地(例如,需要避免复杂、昂贵的实时更新操作)。术语\"缓存表\"和\"汇总表\"没有标准的含义。我们用术语\"缓存表\"来标识存储那些可以比较简单地从schema其他表获取(但是每次获取的速度比较慢)数据的表(例如,逻辑上冗余的数据)。而术语\"汇总表\"是,则保存的是使用GROUP BY 语句聚合数据的表(例如,数据不是逻辑上冗余的)。也有人使用术语\"累积表(Roll-Up Table)\"称呼这些表,因为这些数据被\"累计\
快速创建MyISAM索引.为了高效地载入数据到MyISAM表中,有一个常用的技巧是先禁用索引、载入数据,然后重新启用索引:```sqlmysql>ALTER TABLE test.load_data DISABLE KEYS;mysql>ALTER TABLE test.load_data ENABLE KEYS;```这个技巧能够发挥作用,是因为构建索引的工作被延迟到数据完全载入以后,这个时候已经可以通过排序来构建索引了,这样做会快很多,并且使得索引树的碎片更少、更紧凑。不幸的是,这个办法对唯一索引无效,因为DISABLE KEYS只对非唯一索引有效。MyISAM会在内存中构造唯一索引,并且为载入的每一行检查唯一性。一旦索引的大小超过了有效内存大小,载入操作就会变得越来越慢。在现代版本的InnoDB版本中,有一个类似的技巧,这依赖于InnoDB的快速在线索引创建功能。这个技巧是,先删除所有的非唯一索引,然后增加新的列,最后重新创建删除掉的索引。Percona Server可以自动完成这些操作步骤。也可以使用像前面说的ALTER TABLE的骇客方法来加速这个操作,但需要多做一些工作并且承担一定的风险。这对备份中载入数据是很有用的,例如,当已经直到所有的数据都是有效的并且没有必要做唯一性检查就可以这么来操作。下面是操作步骤:1.用需要的表结构创建一张表,但是不包括索引。2.载入数据到表以后构建.MYD文件3.按照需要的结构创建另外一张空表,这次要包含索引。这回创建需要的.frm和.MYI文件4.获取读锁并刷新表5.重命名第二张表的.frm和MYI文件,让MySQL认为是第一张表的文件6.释放读锁7.使用REPAIR TABLE来重建表的索引。该操作会通过排序来构建所有索引,包括唯一索引
加快ALTER TABLE操作的速度。MySQL的ALTER TABLE操作的性能对大表来说是个大问题。MySQL执行大部分修改表结构操作的方法是用新的结构创建一个空表,从旧表中查出所有数据插入新表,然后删除旧表。这样操作可能需要花费很长时间,如果内存不足而表又很大,而且还有很多索引的情况下尤其如此。许多人都有这样的经验,ALTER TABLE操作需要花费数个小时甚至数天才能完成。MySQL5.1以及更新版本包含一些类型的\"在线\"操作的支持,这些功能不需要再整个操作过程中锁表。最近版本的InnoDB也支持通过排序来建索引,这使得建索引更快,并且有一个紧凑的索引布局。一般而言,大部分ALTER TABLE操作将导致MySQL服务中断。我们会展示一些在DDL操作时有用的技巧,但这是针对一些特殊的场景而言的。对常见的场景,能使用的技巧只有两种:一种是现在一台不提供服务的机器上执行ALTER TABLE操作,然后和提供服务的主库进行切换;另外一种是\"影子拷贝\
Schema与数据类型优化
概述。索引(在MySQL中也叫做\"键(key)\")是存储引擎用于快速找到记录的一种数据结构。这是索引的基本功能。索引对于良好的性能非常关键。尤其是当表中的数据量越来越大时,索引对性能的影响语法重要。在数据量较小时负载较低时,不恰当的索引对性能的影响可能还不明显,但当数据量逐渐增大时,性能则会急剧下降(除非特别说明,假设都是硬盘驱动器。固态硬盘驱动器有着完全不同的性能特性)。不过,索引却经常被忽略,有时候甚至被误解,所以在实际案例中经常会遇到由糟糕索引导致的问题。索引优化应该是对查询性能优化是最有效的手段,索引能够轻易将查询性能提高几个数量级,\"最优\"的索引有时比一个\"好的\"索引性能要好两个数量级。创建一个真正“最有”的索引经常需要重写查询
索引基础。在MySQL中,存储引擎用类似的方法使用索引,其先在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。假如要运行下面的查询:```sqlmysql> SELECT first_name FROM actor WHERE actor_id = 5;```如果在actor_id列上建有索引,则MySQL将使用该索引找到actor_id为5的行,也就是说,MySQL先在索引上按值进行查找,然后返回所有包含该值的数据行。索引可以包含一个或者多个列的值。如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效地使用索引的最左前缀列,创建一个包含两个列的索引,和创建两个只包含一列的索引是大不相同的。
如果使用的是ORM,是否还需要关心索引?简而言之:是的,仍然需要理解索引,即使是适用对象关系映射(ORM)工具。ORM工具能够生产符合逻辑的、合法的查询(多数时候),除非只是生成非常基本的查询(例如仅是根据主键查询),否则它很难生成适合索引的查询。无论是多个复杂的ORM工具,在精妙和复杂的索引面前都是\"浮云\"。很多时候,即使是查询优化技术专家也很难兼顾到各种情况,更别说ORM了
B-Tree(从技术上来说是B+Tree)索引树中的部分条目示例
可以使用B-Tree索引的查询类型。B-Tree索引使用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。(这是MySQL相关的特性,甚至和具体的版本也相关。其他有些数据库也可以使用索引的非前缀部分,虽然使用完全的前缀的效率更好。MySQL未来也可能会提供这个特性).前面所述的索引对如下类型的查询有效:1.全值匹配:全值匹配指的是和索引中的所有列进行匹配,例如前面提到的索引可用于查找行命为Cuba Allen、出生于1960-01-01的人2.匹配最左前缀:前面提到的索引可用于查找所有姓为Allen的人,即只使用索引的第一列3.匹配列前缀:也可以只匹配某一列的值的开头部分。例如前面提到的索引可用于查找所有以J开头的姓的人。这里页只使用了索引的第一列4.匹配范围值:例如前面提到的索引可用于查找姓在Allen和Barrymore之间的人。这里也只使用了索引的第一列5.精确匹配某一列范围并匹配另外一列:前面提到的索引也可用于查找所有姓Allen,并且名字是字母K开头(比如Kim、Karl等)。即第一列last_name全匹配,第二列first_name范围匹配。6.只访问索引的查询:B-Tree通常可以支持\"只访问索引的查询\",即查询只需要访问索引,而无须访问数据行。因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的ORDER BY操作(按顺序查找)。一般来说,如果B-Tree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。所有,如果ORDER BY 子句满足前面列出的几种查询类型,则这个索引也可以满足对应的排序需求。
下面是一些关于B-Tree索引的限制:1.如果不是按照索引的最左列开始查找,则无法使用索引。例如上面例子中的索引无法用于查找名字为Bill的人,也无法查找某个特定生日的人,因为这两列都不是最左数据列。类似地,也无法查找姓氏以某个字母结尾的人2.不能跳过索引中的列。也就是说,前面所述的索引无法用于查找姓为Smith并且在某个特定日期出生的人。如果不指定名(first_name),则MySQL只能使用索引的第一列3.如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。例如有查询WHERE last_name ='Simith' AND first_name LIKE 'J%' AND dob = '1976-12-23',这个查询只能使用索引的前两列,因为这里LIKE是一个范围条件(但是服务器可以把其余列用于其他目的)。如果范围查询列值的数量优先,那么可以通过使用多个等于条件来代替范围条件到这里大家应该可以明白,前面提到的索引列的顺序是多么的重要:这些限制都和索引列的顺序有关。在优化性能的时候,可能需要使用相同的列但顺序不同的索引满足不同类型的查询需求。也有些限制并不是B-Tree本身导致的,而是MySQL优化器和存储引擎使用索引的方式导致的。这部分限制在未来的版本中可能就不再是限制了
B-Tree索引当人们讨论索引的时候,如果没有特别指明类型,那多半说的是B-Tree索引,它使用B-Tree数据结构来存储数据。(实际上很多存储引擎使用的是B+Tree,既每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历),大多数MySQL引擎都支持折中索引。Archive引擎是一个例外:5.1之前Archive不支持任何索引,直到5.1才开始支持单个自增列(AUTO_INCREMENT)的索引。使用术语\"B-Tree\
空间数据索引(R-Tree)MyISAM表支持空间索引,可以用作地理数据存储。和B-Tree索引不同,这类索引无须前缀查询。空间索引会从所有维度来索引数据。查询时,可以有效地使用任意维度来组合查询。必须使用MySQL的GIS相关函数如MBRCONTAINS()等来维护数据。MySQL的GIS支持并不完善,所以大部分人都不会使用这个特性。开源关系数据库系统中对GIS的解决方案做得比较好的是PostgreSQL的PostGIS.
全文索引全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。全文搜索和其他几类索引的匹配方式完全不一样。它有许多需要注意的细节,如停用词、词干和复数、布尔搜索等。全文索引更类似于搜索引擎作得事情,而不是简单的WHERE条件匹配。在相同的列上同时创建全文索引和基于值得B-Tree索引不会有冲突,全文索引适用于MATCH AGAINST操作,而不是普通得WHERE条件操作
索引的类型索引有很多种类型,可以为不同的场景提供更好的性能。在MySQL中,索引是在存储引擎层而不是服务器层实现的。所以,并没有统一的索引标准:不同存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一类型的索引,其底层的实现也可能不同。下面我们先来看看MySQL支持的索引类型,以及它们的优点和缺点:
索引是最好的解决方案吗?索引并不总是最好的工具。总的来说,只有当索引帮助存储引擎快速查找到记录带来的好处大于其带来的额外工作时,索引才是有效地。对于非常小的表,大部分情况下简单的全表扫描更高效。对于中到大型的表,索引就非常有效。但对于大型的表,建立和使用索引的代价将随之增长。这种情况下,则需要一种技术可以直接区分出查询需要的一组数据,而不是一条记录一条记录地匹配。例如可以使用分区技术。如果表的数量特别多,可以建立一个元数据信息表,用来查询需要用到的某些特性。例如执行那些需要聚合多个应用分布在多个表的数据的查询,则需要记录\"哪个用户的信息存储在哪个表中\"的元数据,这样在查询时就可以直接忽略那些不包含指定用户信息的表。对于大型系统,这是一个常用的技巧,事实上,Infobright就是使用类似的实现。对于TB级别的数据,定位单条记录的意义不大,所以经常会使用块级别元数据技术来替代索引
索引的优点。索引可以让服务器快速地定位到表的指定位置。但是这并不是索引的唯一作用,到目前为止可以看到,根据创建索引的数据结构不同,索引也有一些其他的附加作用。最常见的B-Tree索引,按照顺序存储数据,所有MySQL可以用来做ORDER BY 和GROUP BY操作。因为数据是有序的,所以B-Tree也就会将相关的列值存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。据此特性,总结下来索引有如下三个优点:1.索引大大减少了服务器需要扫描的数据量2.索引可以帮助服务器避免排序和临时表3.索引可以将随机IO变为顺序IO索引这个主题完全值得单独写一本书,如果想深入理解这部分内容,强烈建议阅读由Tapio Lahdenmaki和Mike Leach编写的Relational Database Index Design and the Optimizers(Wiley出版社)一书,该书详细介绍了如何计算索引的成本和作用、如何评估查询速度、如何分析索引维护的代价和其带来的好处等.Lahdenmaki和Leach在书中介绍了如何评价一个索引是否适合某个查询的\"三星系统\"(three-star system):索引将相关的记录放到一起则获得一星;如果索引中的数据顺序和查找的排列顺序一致则获得二星;如果索引中的列包含了查询中需要的全部列则获得三星
独立的列我们通常会看到一些查询不当地使用索引,或者使得MySQL无法使用已有的索引。如果查询中的列不是独立的,则MySQL就不会使用索引。\"独立的列\
在上面地示例中,已经找到了合适的前缀长度,下面演示如何创建前缀素银:```sqlmysql> ALTER TABLE city_demo ADD KEY(city(7));```前缀索引是一种使索引更小、更快的有效方法,但另一方面也有其缺点:MySQL无法使用前缀索引做ORDER BY 和GROUP BY,也无法使用前缀索引做覆盖扫描。一个常见的场景使针对很长的十六进制唯一ID使用前缀索引。有时候后缀索引(suffix index)也有用途(例如,找到某个域名的所有电子邮件地址)。MySQL原生不支持反向索引,但是可以把字符串反转后存储,并基于词建立前缀索引
前缀索引和索引选择性有时候需要索引很长的字符列,这会让索引变得大且慢。一个策略是前面提到过的模拟哈希索引。但有时候这样做还不够,还可以做些什么呢?通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择姓。索引的选择性是指,不重复的所引致(也成为技术,cardinality)和数据表的记录总数(#T)的比值。范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。一般情况下某个列前缀的选择性也是足够高的,足以满足查询性能。对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。前缀应该足够长,以使得前缀索引的选择性接近于索引整个列。换句话说,前缀的\"技术\"应该接近于完整列的\"基数\
索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建的很糟糕:1.当出现服务器对多个索引做相交操作时(通常有多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引2.当服务器需要对多个索引做联合操作时(通常有多个OR条件)通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据的时候3.更重要的是,优化器不会把这些计算到\"查询成本\"(cost)中,优化器只关心随机页面读取。这会使得查询的成本被\"低估\",导致该执行计划还不如直接走全表扫描。这样做不但会消耗更多的CPU和内存资源,还可能会影响查询的并发性,但如果是单独运行这样的查询则往往会忽略对并发性的影响。通常来说,还不如像在MySQL4.1或者更早的时代一样,将查询改写成UNION的方式往往更好如果在中看到有索引合并,应该好好检查一下查询和表的结构,看是不是已经是最优的。可以通过参数optimizer_switch来关闭索引合并功能,也可以使用IGNORE INDEX提示让优化器忽略掉某些索引
当使用前缀索引的时候,在某些条件值的基数比正常值高的时候,问题就来了。例如,在某些应用程序中,对于没有登录的用户,都将其用户名记录为\"guest\",在记录用户行为的会话(session)表和其他记录用户活动的表中\"guest\
选择合适的索引列顺序我们遇到最容易引起困惑的问题就是索引列的顺序。正确的顺序依赖于使用该索引的查询,并且同时需要考虑如何更好地满足排序和分组的需要(以B-Tree索引为例)。在一个多列B-Tree索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。所以索引可以按照升序或者降序进行扫描,以满足精确符合列顺序的ORDER BY 、GROUP BY和DISTINCT等子句的查询需求。所以多列索引的列顺序至关重要。在Lahdenmaki和Leach的\"三星索引\"系统中,列顺序也决定了一个索引是否能够成为一个真正的\"三星索引\
MyISAM表layout_test的主键分布
MyISAM表layout_test的col2列索引的分布
MyISAM的数据分布。MyISAM的数据分布非常简单。MyISAM按照数据插入的顺序存储在磁盘上,如图所示。在行的旁边显式了行号,从0开始递增。因为行是定长的,所以MyISAM可以从表的开头跳过所需的字节找到需要的行(MyISAM并不总是使用上图中的\"行号\",而是根据定长还是变长的行使用不同策略)。这种分布方式很容易创建索引。下面显式的一系列图,隐藏了页的物理细节,只显示索引中的\"节点\",索引中的每个叶子节点包含\"行号\"。如图所示显式了表的主键。这里忽略了一些细节,例如前一个B-Tree节点有多少个内部节点,不过这并不影响对非聚簇存储引擎的基本数据分布的理解,那么col2l列上的索引又会如何呢?有什么特殊的吗?回答是否定的:它和其他的所以没有什么区别,如图所示显示了col2列上的索引.事实上,MyISAM中主键索引和其他索引在结构上没有什么不同,主键索引就是一个名为PRIMARY的唯一非空索引。
InnoDB表layout_test的主键分布
InnoDB表layout_test的二级索引分布
InnoDB的数据分布。因为InnoDB支持聚簇索引,所以使用非常不同的方式存储同样的数据。InnoDB以如图所示的方式存储数据。第一眼看上去,感觉该图和前面的图没有什么区别,但再仔细看细节,会注意道该图显示了整个表,而不是只有索引。因为在InnoDB中,聚簇索引\"就是\"表,所以不像MyISAM那样需要独立的行存储。聚簇索引的每一个叶子节点都包含了主键值、事务ID、用于事务和MVCC的回滚指针以及所有的剩余列(在这个例子中是col2).如果主键是一个列前缀索引,InnoDB也会包含完整的主键列和剩下的其他列。还有一点和MyISAM的不同是,InnoDB的二级索引和聚簇索引很不相同。InnoDB二级索引的叶子节点中存储的不是\"行指针\",而是主键值,并以此作为指向行的\"指针\"。font color=\"#a23735\
InnoDB和MyISAM的数据分布对比font color=\"#a23735\
向聚簇索引插入顺序的索引值
向聚簇索引种插入无序的值
顺序的主键什么时候会造成更坏的结果?对于高并发工作负载,在InnoDB中按主键顺序插入可能会造成明显的争用。主键的上界会成为\"热点\"。因为所有的插入都发生在这里,所以并发插入可能导致间隙锁竞争。另一个热点可能是AUTO_INCREMENT锁机制;如果遇到这个问题,则可能需要考虑重新设计表或者应用,或者更改innodb_autoinc_lock_mode配置。如果你的服务器版本还不支持innodb_autoinc_lock_mode参数,可以升级到新版本的InnoDB,可能对这种场景工作得更好
聚簇索引聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。具体的细节依赖于其实现方式但InnoDB得聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。当表有聚簇索引时,它的数据行实际上存放在索引的叶子页(leaf page)中。术语\"聚簇\"表示数据行和相邻的键值紧凑地存储在一起。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引(不过,覆盖索引可以模拟多个聚簇索引的情况)。因为是存储引擎负责实现索引,因此不是所有的存储引擎都支持聚簇索引。主要关注InnoDB.如图展示了聚簇索引中的记录是如何存放的。注意到,叶子页包含了行的全部数据,但是节点页只包含了索引列。该图中,索引包含的是整数值。一些数据库服务器允许选择哪个索引作为聚簇索引,但是目前市场上,还没有任何一个MySQL内建的存储引擎支持这一点。InnoDB将通过主键聚集数据,这也就是说上图中的\"被索引的列\"就是主键列。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引,InnoDB只聚集在同一个页面中的记录。包含相邻键值的页面可能会相距甚远。聚簇索引可能对性能有帮助,但也可能导致严重的性能问题。所以需要仔细地考虑聚簇索引,尤其是将表的存储引擎从InnoDB改成其他引擎的时候(反过来也一样)。聚集的数据有一些重要的优点:1.可以把相关数据保存在一起。例如实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少数的数据也就能获取某个用户的全部邮件。如果没有使用聚簇索引,则每封邮件都可能导致一次磁盘IO2.数据访问更快。聚簇索引将索引和数据保存在同一个B-Tree中,因此聚簇索引中获取数据通常比在非聚簇索引中查找要快。3.使用覆盖索引扫描的查询可以直接使用叶节点中的主键值如果在设计表和查询时能充分利用上面的优点,那就能极大地提升性能。同时,聚簇索引也有一些缺点:1.聚簇数据最大限度地提高了IO密集型应用的性能,但如果数据全部都存放在内存中,则访问的顺序就没那么重要了,聚簇索引也就没什么优势了2.插入速度严重依赖于插入顺序。按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。但如果不是按照主键顺序加载数据,那么在加载完成后最好使用OPTIMIZE TABLE命令重新组织一下表3.更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置4.基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临\"页分裂(page split)\"的问题。当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作。也分裂会导致表占用更多的磁盘空间5.聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候6.二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列7.二级索引访问需要两次索引查找,而不是一次最后一点可能让人有些疑惑,为什么二级索引需要两次索引查找?答案在于二级索引中保存的\"行指针\"的实质。要记住,二级索引叶子节点保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复的工作:两次B-Tree查找而不是一次(顺便提一下,并不是所有的非聚簇索引都能做到一次索引查询就找到行。当行更新的时候可能无法存储在原来的位置,这会导致表中出现行的碎片花或者移动行并在原位置保存\"向前指针\"。这两种情况都会导致查找行时需要更多的工作),对于InnoDB,自适应哈希索引能够减少这样的重复工作
我们把这种方式叫作延迟关联(deferred join),因为延迟了对列的访问。在查询的第一阶段MySQL使用覆盖索引,在FROM子句中的子查询中找到匹配的prod_id,然后根据这些prod_id值在外层查询匹配获取需要的所有列值。虽然无法使用索引覆盖整个查询,但总算比完全无法利用索引覆盖的好。这样优化的效果取决于WHERE条件匹配返回的行数。假设这个products表有100万行,我们来看一下上面两个初选在三个不同的数据集上的表现,每个数据集都包含100万行:1.第一个数据集,Sean Carrey出演了30 000部作品,其中有20 000部的标题中包含了Apollo2.第二个数据集,Sean Carrey出演了30 000部作品,其中40 部的标题中包含了Apollo3.第三个数据集,Sean Carrey出演了50部作品,其中10部的标题中包含了Apollo使用上面的三种数据集来测试两种不同的查询,得到的结果如表所示:下面是对结果的分析:1.在示例1中,查询返回了一个很大的结果集,因此看不到优化的效果。大部分时间都花在读取和发送数据上了2.在示例2中,经过索引过滤,尤其是第二个条件过滤后只返回了很少的结果集,优化的效果非常明显:在这个数据集上性能提高了5倍,优化后的查询的效率主要得益于只需要读取40行完整数据行,而不是原查询中需要的30 000行3.在示例3中,显示了子查询效率反而下降的情况。因为索引过滤时符合第一个条件的结果集已经很小,所以子查询带来的成本反而比从表中直接提取完整行要高。在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。不过,可以更进一步优化InnoDB。回想一下,InnoDB的二级索引的叶子节点都包含了主键的只,这意味着InnoDB的二级索引可以有效地利用这些\"额外\
索引下推优化的原理。我们先简单了解一下MySQL大概的架构:如图所示。MySQL服务层负责SQL语法解析、生成执行计划等,并调用存储引擎层去执行数据的存储和检索。索引下推的下推其实就是将部分上层(服务层)负责的事情,交给了下层(引擎层)去处理。我们来具体看一下,在没有使用ICP的情况下,MySQL的查询:1.存储引擎读取索引记录2.根据索引中的主键值,定位并读取完整的行记录3.存储引擎把记录交给Server层去检测该记录是否满足WHERE条件使用ICP的情况下,查询过程:1.存储引擎读取索引记录(不是完整的行记录);2.判断WHERE条件部分能否用索引中的列来做检查,条件不满足,则处理下一行索引记录3.条件满足,使用索引中的主键去定位并读取完整的行记录(就是所谓的回表)4.存储引擎把记录交给Server层,Server层检测该记录是否满足WHERE条件的其余部分
索引下推使用条件。1.只能用于range、ref、eq_ref、ref_or_null访问方法2.只能用于InnoDB和MyISAM存储及其分区表3.对InnoDB存储引擎来说,索引下推只适用于二级索引(也叫辅助索引),索引下推的目的为了减少回表次数,也就是要减少IO操作。对于InnoDB的聚簇索引来说,数据和索引是在一起的,不存在回表这一说4.引用了子查询的条件不能下推5.引用了存储函数的条件不能下推,因为存储引擎无法调用存储函数
未来MySQL版本的改进(索引下推)上面提到的很多限制都是由于存储引擎API涉及所导致的,目前的API设计不允许MySQL将过滤条件传到存储引擎层。如果MySQL在后续版本能够做到这一点,则可以把查询发送到数据上,而不是像现在这样只能把数据从存储引擎拉到服务器层,再根据查询条件过滤。MySQL将这个功能称之为索引下推(Index Condition Pushdown)
覆盖索引通常大家都会根据查询的WHERE条件来创建合适的索引,不过这只是索引优化的一个方面。设计优秀的索引应该考虑到整个查询,而不单单是WHERE条件部分。索引确实是一种查找数据的高效方式,但是MySQL也可以使用索引来直接获取列的数据,这样就不再需要读取数据行。如果索引的叶子几点钟已经包含要查询的数据,那么还有什么必要再回表查询呢?如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为\"覆盖索引\"。覆盖索引是非常有用的工具,能够极大地提高性能。考虑一下如果查询只需要扫描索引而无须回表,会带来多少好处:1.索引条目通常远小于数据行大小,所以如果只需要读取索引,那MySQL就会极大地减少数据访问量。这对缓存的负载非常重要,因为这种情况下相应时间大部分花费在数据拷贝上。覆盖索引对于IO密集型的应用也有帮助,因为索引比数据更小,更容易全部放入内存中(这对于MyISAM尤其正确,因为MyISAM能压缩索引以变得更小)2.因为索引是按照列值顺序存储的(至少在单个页内是如此),所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少得多。对于某些存储引擎,例如MyISAM和Percona XtraDB,甚至可以通过OPTIMIZE命令使得索引完全顺序排列,这让简单的范围查询能使用完全的顺序的索引访问3.一些存储引擎如MyISAM在内存中只缓存索引,数据则依赖于操作系统缓存,因此要访问数据需要一次系统调用。这可能会导致严重的性能问题,尤其是那些系统调用占了数据访问中的最大开销的场景4.由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询在所有这些场景中,在索引中满足查询的成本一般要比查询行要小得多。不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引。另外,不同的存储引擎实现覆盖索引的方式也不同,而且不是所有的引擎都支持覆盖索引(如Memory存储引擎就不支持覆盖索引),当发起一个被索引覆盖的查询(也叫做索引覆盖查询时),在EXPLAIN的Extra列可以看到\"Using index\"的信息(很容易把Extra列的\"Using index\"和type列的\"index\
使用索引扫描来做排序。MySQL有两种方式可以生成有序的结果:通过排序操作;或者按索引顺序扫描(MySQL有两种排序算法);如果EXPLAIN出来的type列的值为\"index\",则说明使用了索引扫描来做排序(不要和Extra列的\"Using index\
压缩(前缀压缩)索引MyISAM使用前缀压缩来减少索引的大小,从而让更多的索引可以放入内存中,这在某些情况下能极大地提高性能。默认只压缩字符串,但通过参数设置也可以对整数做压缩。MyISAM压缩每个索引块的方法是,先完全保存索引块中的第一个值,然后将其他值和第一个值进行比较得到相同前缀的字节数和剩余不同后缀部分,把这部分存储起来即可。例如,索引块中的第一个值是\"perform\",第二个值是\"performance\",那么第二个值得前缀压缩后存储得是类似\
未使用的索引。除了冗余索引和重复素银,可能还会有一些服务器永远不同的索引。这样的索引完全是累赘,建议考虑删除(有些索引的功能相当于唯一约束,虽然该索引一致没有被查询使用,却可能是用于避免产生重复数据的)有两个工具可以帮助定位未使用的索引。最简单有效的办法是在Percona Server或者MariaDB中线打开userstates服务器变量(默认是关闭的),然后让服务器正常运行一段时间,再通过查询INFORMATION_SCHEMA.INDEX_STATISTICS就能查到每个索引的使用频率。另外,还可以使用Percona Toolkit中的pt-index-usage,该工具可以读取查询日志,并对日志中的每条查询进行EXPLAIN操作,然后打印出关于索引和查询的报告。这个工具不仅可以找出哪些索引是未使用的,还可以了解查询的执行计划——例如在某些情况有些类似的查询的执行方式不一样,这可以帮助定位到哪些偶尔服务质量差的查询,优化它们以得到一致的性能表现。该工具也可以将结果写入到MySQL的表中,方便查询结果
高性能的索引策略正确地创建和使用索引是实现高性能查询的基础。前面已经介绍了各种类型的索引及其对应的优缺点。现在一起来看看如何真正地发挥这些索引的优势。高效地选择和使用索引有很多种方式,其中有些是针对特殊案例的优化方法,有些则是针对特定行为的优化。使用哪个索引,以及如何评估选择不同索引的性能影响的技巧,则需要持续不断地学习。
索引案例学习理解索引最好的办法是结合示例,假设要设计一个在线约会网站,用户信息表有很多列,包括国家、地区、城市、性别、眼睛颜色,等等。网站必须支持上面这些特征的各种组合来搜索用户,还必须允许根据用户的最后在线时间、其他会员对用户的评分等对用户进行排序并对结果进行限制。如何设计索引满足上面的复杂需求呢?出人意料的是第一件需要考虑的事情是需要使用索引来排序,还是先检索数据再排序。使用索引排序会严格限制索引和查询的设计。例如,如果希望使用索引做根据其他会员对用户的评分的排序,则WHER条件中的age BETWEEN 18 AND 25就无法使用索引。如果MySQL使用某个索引进行范围查询,也就无法再使用另一个索引(或者是该索引的后续字段)进行排序了。如果这是很常见的WHERE条件,那么我们当然就会认为很多查询需要做排序操作(例如文件排序filesort)
找到并修复损坏的表表损坏(corruption)是很糟糕的事情。对于MyISAM存储引擎,表损坏通常是系统崩溃导致的。其他的引擎也会由于硬件问题、MySQL本身的缺陷或者操作系统的问题导致索引损坏。损坏的索引会导致查询返回错误的结果或者莫须有的主键冲突等问题,严重时甚至还会导致数据库的崩溃。如果你遇到了古怪的问题——例如一些不应该发生的错误——可以尝试运行CHECK TABLE来检查是否发生了表损坏(注意有些存储引擎不支持该命令;二有些引擎则支持以不同的选项来控制完全检查表的方式)。CHECK TABLE通常能够找出大多数的表和索引的错误。可以使用REPAIR TABLE命令来修复损坏的表,但同样不是所有的存储引擎都支持该命令。如果存储疫情不支持,也可通过一个不做任何操作(no-op)的ALTER操作来重建表,例如修改表的存储引擎为当前欸度引擎。下面是一个针对InnoDB表的例子:```sqlmysql> ALTER TABLE actor ENGINE=INNODB;```此外,也可以使用一些存储引擎相关的离线工具,例如myisamchk;或者将数据导出一份,然后再冲洗内倒入。不过,如果损坏的是系统区域,或者是表的\"行数据\"区域,而不是索引,那么上面的办法就没有用了。在这种情况下,可以从备份中恢复表,或者尝试从损坏的数据文件中尽可能地恢复数据。如果InnoDB引擎的表出现了损坏,那么一定是发生了严重的错误,需要立刻调查一下原因。InnoDB一般不会出现损坏。InnoDB的设计保证了它并不容易被损坏。如果发生损坏,一般要么是数据库的硬件问题例如内存或者磁盘问题(有可能),抑或是InnoDB本身的缺陷(不太可能)。常见的类似错误通常是由于尝试使用rsync备份InnoDB导致的。不存在什么查询能够让InnoDB表损坏,也不用担心暗处有\"陷阱\
InnoDB的统计信息值得深入研究。InnoDB引擎通过抽象的方式来计算统计信息,首先随机地读取少量的索引页面,然后以此为样本计算索引的统计信息。在老的InnoDB版本中,样本页面数是8,新版本的InnoDB可以铜鼓哦参数innodb_stats_sample_pages来设置样本页的数量.设置更大的值,理论上来说可以帮助生成更准确的索引信息,特别是对于某些超大的数据表来说,但具体设置多大合适依赖于具体的环境```sqlmysql> SHOW VARIABLES LIKE '%innodb_stats_sample_pages%';+---------------------------+-------+| Variable_name | Value |+---------------------------+-------+| innodb_stats_sample_pages | 8 |+---------------------------+-------+1 row in set (0.01 sec)```InnoDB会在表首次打开,或者执行ANALYZE TABLE,抑或表的大小发生非常大的变化大小变化超过十六分之一或者新插入了20亿行都会触发的时候计算索引的统计信息。InnoDB在打开某些INFORMATION_SCHEMA表,或者使用SHOW TABLE STATUS和SHOW INDEX,抑或在MySQL客户端开启自动补全功能的时候都会触发索引统计信息的更新。如果服务器上有大量的数据,这可能就是个很严重的问题,尤其是当IO比较慢的时候,客户端或者监控程序触发索引信息采样更新时可能会导致大量的锁,并给服务器带来很多的额外压力,这会让用户因为启动时间漫长而沮丧。只要SHOW INDEX查看索引统计信息,就一定会触发统计信息的更新。可以关闭innodb_stats_on_metadata参数来避免上面提到的问题```sqlmysql> SHOW VARIABLES LIKE '%innodb_stats_on_metadata%';+--------------------------+-------+| Variable_name | Value |+--------------------------+-------+| innodb_stats_on_metadata | OFF |+--------------------------+-------+1 row in set (0.00 sec)```如果使用Percona版本,使用的就是XtraDB引擎而不是原生的InnoDB引擎,那么可以通过innodb_auto_update参数来禁止通过自动采样的方式更新索引统计信息,这时需要手动执行ANALYZE TABLE命令来更新统计信息。如果某些查询执行计划很不稳定的话,可以用该办法固话查询计划。如果想要更稳定的执行计划,并在系统重启后更快地生成这些统计i信息,那么可以使用系统表来持久化这些索引统计信息。甚至还可以在不同的机器间迁移索引统计信息,这样新环境启动时就无须再收集这些数据。在Percona5.1版本和官方的5.6版本都已经加入这个特性。一旦关闭索引统计信息的自动更新,那么久需要周期性地使用ANALYZE TABLEE来手动更新。否则,索引统计信息就会永远不变。如果数据分布发生大地变化,可能会出现一些很糟糕的执行计划。
更新索引统计信息MySQL的查询优化器会通过两个API来了解存储引擎的索引值的分布信息,以决定如何使用索引。第一个API是recors_in_rage(),通过向存储引擎传入两个边界值获取在这个范围大概有多少条记录。对于某些存储引擎,该接口返回精确值,例如MyISAM;但对于另一些存储引擎则是一个估算值。例如InnoDB.第二个API是info(),该接口返回各种类型的数据,包括索引的基数(每个键值有多少条记录).如果存储引擎向优化器提供的扫描行数信息是不准确的数据,或者执行计划本身太复杂以致无法准确地获取各个阶段匹配地行数,那么优化器会使用索引统计信息来估算扫描行数。MySQL优化器使用地是基于成本的模型,而衡量成本的主要指标就是一个查询需要扫描多少行。如果表没有统计信息,或者统计信息不准确,优化器就很有可能做出错误的决定。可以通过运行ANALYZE TABLE来重新生成统计信息解决这个问题。每种存储引擎实现索引统计信息的方式不同,所以需要进行ANALYZE TABLE的频率也因不同的引擎而不同,每次运行的成本也不同:1.Memory引擎根本存储索引统计信息2.MyISAM将索引统计信息存储在磁盘中,ANALYZE TABLE需要进行一次全索引扫描来计算索引基数。在整个过程需要锁表3.直到MySQL5.5版本,InnoDB也不在磁盘存储索引统计信息,而是通过随机的索引访问进行评估并将其存储在内存中可以使用SHOW INDEX FROM命令来查看索引的基数(Cardinality)。例如:```sqlmysql> SHOW INDEX FROM actor\\G*************************** 1. row *************************** Table: actor Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: actor_id Collation: A Cardinality: 200 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: actor Non_unique: 1 Key_name: idx_actor_last_name Seq_in_index: 1 Column_name: last_name Collation: A Cardinality: 121 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec)```这个命令输出了很多关于索引的信息,在MySQL手册中对上面每个字段的含义都有详细的解释。这里需要特别提及的是索引列的基数(Cardinality),其显示了存储引擎估算索引列有多少个不同的取值(离散程度)。在MySQL5.0和更新的版本中,还可以通过INFORMATION_SCHEMA.STATISTICS表很方便地查询到这些信息。例如基于INFORMATION_SCHEMA的表,可以编写一个查询给出当前选择性比较低的索引。需要注意的是,如果服务器上的库表非常多,则从这里获取元数据的速度可能会非常慢,而且会给MySQL带来额外的压力
减少索引和数据的碎片。B-Tree索引可能会碎片化,这会降低查询的效率。碎片化的索引可能会以很差或者无序的方式存储在磁盘上。根据设计,B-Tree需要随机磁盘访问才能定位到叶子页,所以随机访问是不可避免地。然而,如果叶子页在物理分布上是顺序且紧密地,那么查询的性能就会更好。否则,对于范围查询、索引覆盖扫描等操作来说,速度可能会降低很多倍;对于索引覆盖扫描这一点更加明显。表的数据存储也可能碎片化。然而,数据存储的碎片化比索引更加复杂。有三种类型的数据碎片:1.行碎片(Row fragmentation)这种碎片指的是数据行被存储在多个地方的片段中,即使查询只从索引中访问一行记录,行碎片也会导致性能下降2.行间碎片(Intra-row fragmentation)行间碎片是指逻辑上顺序的页,或者行在磁盘上不是顺序存储的。行间碎片对诸如全表扫描和聚簇索引扫描之类的操作有很大的影响,因为这些操作原本能从磁盘上顺序存储的数据中获益3.剩余空间碎片(Free space fragmentation)剩余空间碎片是指数据页中有大量的空余空间。这会导致服务器读取大量不需要的数据,从而造成浪费。对于MyISAM表,这三类碎片化都可能发生。但InnoDB不会出现短小的行碎片;InnoDB会移动短小的行并重写到另一个片段中。可以通过执行OPTIMIZE TABLE或者导出再导入的方式来重新整理数据。这对多数存储引擎都是有效地。对于一些存储引擎如MyISAM,可以通过排序算法重建索引的方式来消除碎片。老版本的InnoDB没有什么消除碎片化的方法。不过最新版本InnoDB新增了\"在线\"添加和删除索引的功能,可以通过先删除,然后再重新创建索引的方式来消除索引的碎片化。对于那些不支持OPTIMIZE TABLE的存储引擎,可以通过一个不做任何操作(no-op)的ALTER TABLE操作来重建表。只需要将表的存储引擎修改为当前的存储引擎即可:```sqlmysql>ALTER TABLE <table> ENGINE=<engine>;```对于开启了expand_fast_index_creation参数的Percona Server,按这种方式重建表,则会同时消除表和索引的碎片化。但对于标准版本的MySQL则只会消除表(实际上是聚簇索引)的碎片化。可用先删除所有索引,然后重建表,最后重新创建索引的方式模拟Percona Server的这个功能。应该通过一些实际测量而不是随意假设来确定是否需要消除索引和表的碎片化。Percona的XtraBackup有个--stats参数以非备份的方式运行,而只是打印索引和表的统计情况。包括页中的数据量和空余空间。这可以用来确定数据的碎片化程度。另外也要考虑数据是否已经达到稳定状态,如果你进行碎片整理将数据压缩到一起,可能反而会导致后续的更新操作触发一系列的页分裂和重组,这会对性能造成不良的影响(直到数据再次达到新的稳定状态)
维护索引和表。即使使用正确的类型创建了表并加上了合适的索引,工作也没有结束:还需要维护表和索引来确保它们都正常工作。维护表有三个主要的目的:找到并修复损坏的表,维护准确的索引统计信息,减少碎片。
创建高性能的索引
概述。前面介绍了如何设计最优的库表结构、如何建立最好的索引,这些对于高性能来说是必不可少的。但这些还不够——还需要合理的设计查询。如果查询写的很糟糕,即使库表结构再合理、索引再合适,也无法实现高性能。查询优化、索引优化、库表结构优化需要齐头并进,一个不落。在获得编写MySQL查询的经验的同时,也将学习到如何为高效的查询设计表和索引。同样的,也可以学习到在优化库表结构时会影响到那些类型的查询。这个过程需要时间。接下来激昂从查询设计的一些基本原则开始——这也是在发现查询效率不高的时候首先需要考虑的因素。然后会介绍一些更深的查询优化技巧,并会介绍一些MySQL优化器内部的机制。将展示MySQL时如何执行查询的,如何改变一个查询的执行计划
为什么查询速度会慢。在尝试编写快速的查询之前,需要清楚一点,真正重要的是响应时间。如果把查询看作是一个任务,那么它由一系列子任务组成,每个子任务都会消耗一定的时间。如果要优化查询,实际上要优化其子任务,要么消除其中一些子任务,要么减少子任务的执行次数,要么让子任务运行得更快(有时候你可能还需要修改一些查询,减少这些查询对系统中运行的其他查询的影响,这种情况下,你是在减少一个查询的资源消耗)。MySQL在执行查询的时候有那些子任务,哪些子任务运行的速度很慢?这里很难给出完整的列表。通常来说,查询的生命周期大致可以按照顺序来看:从客户端,到服务器,然后再服务器上进行解析,生成执行计划,执行,并返回结果给客户端。其中\"执行\"可以认为是整个生命周期中最重要的阶段,这其中包括了大量为了检索数据到存储引擎以及调用后的数据处理,包括排序、分组等。在完成这些任务的时候,查询需要在不同的地方花费时间,包括网络、CPU计算,生成统计信息和执行计划、锁等待(互斥等待)等操作,尤其是向底层存储引擎检索数据的调用操作,这些调用需要在内存操作、CPU操作和内存不足时导致的IO操作上消耗时间,根据存储引擎不同,可能还会产生大量的上下文切换以及系统调用。在每一个消耗大量时间的查询案例中,都能看到一些不必要的额外操作、某些操作被额外地重复了很多次、某些操作执行得太慢等。优化查询的目的就是减少和消除这些操作上所花费的时间.对于一个查询的全部生命周期,上面描述的并不完整。这里只是向说明:了解查询的生命周期、清楚查询的时间消耗情况对于优化查询有很大的意义
响应时间。要记住, 响应时间只是一个表面的值。这样说可能看起来和前面关于响应时间的说法有矛盾?其实并不矛盾,响应时间仍然是最重要的指标,这有一点复杂,后面细细道来。响应时间是两个部分之和:服务时间和排队时间。服务时间是指数据处理这个查询真正花了多长时间。排队时间是指服务器因为等待某些资源而没有真正执行查询的时间——可能是等待IO操作完成,也可能是等待行锁,等等。遗憾的是,我们无法把响应时间细分到上面这些部分,除非有什么办法能够逐个测量上面这些消耗,不过很难做到,一般最常见和重要的等待是IO和锁等待,但实际情况更加复杂。所以在不同类型的应用压力下,响应时间并没有什么一致的规律或者共识。诸如存储引擎的锁(表锁、行锁)、高并发资源竞争、硬件响应等诸多因素都会影响到响应时间。所以,响应时间既可能是一个问题的结果也可能是一个问题的原因,不同案例情况不同,除非我们能深入测量出每个环节。当你看到一个查询的响应时间的时候,首先需要问问自己,这个响应时间是否是一个合理的值。实际上可以使用\"快速上限估计\"法来估算查询的响应时间,这是由Lahdenmaki和Mike Leach编写的Relational Database Index Design and the Optimizers一书中提到的技术。概括地说,了解这个查询需要哪些索引以及它的执行计划是什么,然后计算大概需要多少个顺序和随机IO,再用其乘以在具体硬件条件下一次IO的消耗。最后把这些消耗都加起来,就可以获得一个大概参考值来判断当前响应时间是不是一个合理得值
扫描的行数和返回的行数。分析查询时,查看该查询扫描的行数时非常有帮助的。这在一定程度上能够说明该查询找到需要的数据的效率高不高。对于找出哪些\"糟糕\"的查询,这个指标可能还不够完美,因为并不是所有的行的访问代价都是相同的。较短的行的访问速度更快,内存中的行也比磁盘中的行访问速度要快得多。理想情况下扫描得行数和返回的行数应该是相同的。但实际情况中这种\"美事\"并不多。例如在做一个管来奶查询时,服务器必须要扫描多行才能生成结果集中的一行。扫描的行数对返回的行数的比率通常很小,一般在1:1和10:1之间,不过有时候这个值也可能非常非常大
扫描的行数和访问类型。在评估查询开销的时候,需要考虑一下从表中找到某一行数据的成本。MySQL有好几种访问方式可以查找并返回一行结果。有些访问方式可能需要扫描很多行才能返回一行结果,也有些访问方式可能无须扫描就能返回结果。在EXPLAIN语句中的type列反应了访问类型。访问类型有很多种,从全表扫描到索引扫描、范围扫描、唯一索引查询、常数引用等。这里列的这些,速度时从慢到快,扫描的行数也是从多到少。你不需要记住这些访问类型,font color=\"#a23735\
MySQL是否扫描额外的记录。在确定查询只返回需要的数据以后,接下来应该看看查询为了返回结果是否扫描了许多的数据。对于MySQL,最简单的衡量查询开销的三个指标如下:1.响应时间2.扫描的行数3.返回的行数没有哪个指标能够完美地衡量查询的开销,但它们大致反映了MySQL在内部执行查询时需要访问多少数据,并可以大概推算出查询运行的时间。这三个指标都会记录到MySQL的慢日志中,所以检查慢日志记录是找出扫描行数过多的查询的好办法
慢查询基础:优化数据访问查询性能低下最基本的原因是访问的数据太多。某些查询可能不可避免地需要筛选大量数据,但这并不场景。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效的查询,我们发现通过下面两个步骤来分析总是很有效:1.确认应用程序是否在检索大量超过需要的数据。这通常意味着访问了太多的行,但有时候也可能是访问了太多的列2.确认MySQL服务器是否在分析大量超过需要的数据行
一个复杂查询还是多个简单查询。设计查询的时候一个需要考虑的重要问题是,是否需要将一个复杂的查询分成多个简单的查询,在传统实现中,总是强调需要数据库层完成尽可能多的工作,这样做的逻辑在于以前总是认为网络通信、查询解析和优化是一件代价很高的事情。但是这样的想法对于MySQL并不适用,MySQL从设计上让连接和断开连接都很轻量级,在返回一个小的查询结果方面很高效。现代的网络速度比以前要快很多,无论是贷款还是延迟。在某些版本的MySQL上,即使在一个通用服务器上,也能够运行超过10万的查询,即使是一个千兆网卡(1000Mbps / 8 bit = 125M/s)也能轻松满足每秒超过2000次的查询。所以运行多个小查询现在已经不是大问题了。MySQL内部每秒能够扫描内存中上百万行数据,相比之下,MySQL响应数据给客户端就慢得多了。在其他条件都相同的时候,适用尽可能少的查询当然是更好地。但是有时候,将一个大查询分解为多个小查询是很有必要的。别害怕这样做,好好衡量一下这样做是不是会减少工作量。不过,在应用设计的时候,如果一个查询能够胜任时还写成多个独立查询是不明智的。例如,有些应用对一个数据表做10次独立的查询来返回10行数据,每个查询返回一条结果,查询10次
切分查询。有时候对于一个大查询我们需要\"分而治之\
重构查询的方式。在优化有问题的查询时,目标应该是找到一个更优的方法获得实际需要的记过——而不是一定总是需要从MySQL获取一模一样的结果集。有时候,可以将查询转换一种写法让其返回一样的结果,但是性能更好。但也可以通过修改应用代码,用另一种方式完成查询,最终达到一样的目的。
查询状态对于一个MySQL连接,或者说一个线程,任何时刻都有一个状态,该状态标识了MySQL当前正在做什么。有很多种方式能查看当前的状态,最简单的是使用SHOW FULL PROCESSLIST命令(该命令返回结果中的Command列就表示当前的状态)。在一个查询的生命周期中,状态会变化很多次。MySQL官方手册中对这些状态值的含义有权威的解释,下面将这些状态列出来,并做一个简单的解释.1.Sleep线程正在等待客户端发送新的请求2. Query线程正在执行查询或者正在将结果发送给客户端3.Locked在MySQL服务器层,该线程正在等待表锁。存储引擎级别实现的锁,例如InnoDB的行锁,并不会体现在线程状态中。对于MyISAM来说这是一个比较典型的状态,但在其他没有行锁的一你请中也会经常出现4.Analyzing and statistics线程正在收集存储引擎的统计洗脑洗,并生成查询的执行计划5.Copying to tmp table [on disk]线程正在执行查询,并且将结果集都复制到一个临时表中,这种状态一般要么是在做GROUP BY操作,要么是文件排序操作,或者UNION操作。如果这个状态后面还有\"on disk\"标记,那表示MySQL正在将一个内存临时表放到磁盘上6.Sorting result线程正在对结果集进行排序7.Sending data这表示多种情况:线程可能在多个状态之间传送数据,或者在生成结果集,或者在向客户端返回数据。了解这些状态的基本含义非常有用,这可以让我们很快地了解当前\"谁正在持球\"。在一个繁忙的服务器上,可能会看到大量的不正常的状态。例如statstics正占用大量的时间。这通常表示,某个地方有异常了
MySQL客户端/服务器通信协议。一般来说,不需要去理解MySQL通信协议的内部实现细节,只需要大致理解通信协议是如何工作的。MySQL客户端和服务器之间的通信协议是\"半双工\"的,这意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动作不能同时发生。所以,我们也无法也无须将一个消息切成小块独立来发送。这种协议让MySQL通信简单快速,但是也从很多地方限制了MySQL.一个明显的限制是,这意味着没法进行流量控制。一旦一端开始发送消息,另一端要接收完整个消息才能响应它。这就像来回抛球的游戏:在任何时刻,只有一个人能控制球,而且只有控制球的人才能将球抛回去(发送消息)。一旦客户端发送了请求,它能做的事情就只是等待结果了。相反的,一般服务器响应给用户的数据通常很多,由多个数据包组成。当服务器开始响应客户端请求时,客户端必须完整接收整个返回结果,而不能简单地只取前面几条结果,然后让服务器停止发送数据。这种情况下,客户端若接收完整的结果,然后取前面几条需要的结果,或者接收完几条结果后就\"粗暴\"地断开连接,都不是好注意。这也是在必要的时候一定要在查询中加上LIMIT限制的原因。换一种方式解释这种行为:当客户端从服务器取数据时,看起来是一个拉数据的过程,但实际上是MySQL在向客户端推送数据的过程。客户端不断地接收从服务器推送的数据,客户端也没法让服务器停下来。客户端像是\"从消防管喝水\
查询缓存(这里是指Query Cache).在解析一个查询语句之前,如果查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据。这个检查是通过一个对大小写敏感的哈希查找实现的。查询和缓存中的查询即使只有一个字节不同,那也不会匹配缓存结果(Percona版本的MySQL中提供了一个新的特性,可以在计算查询语句哈希值时,先将注释移除再计算哈希值,这对于不同注释的相同查询可以命中相同的查询缓存结果)。这种情况下查询就会进入下一个阶段的处理。如果当前的查询恰好命中了查询缓存,那么再返回查询结果之前MySQL会检查一次用户权限。这仍然时无须解析查询SQL语句的,因为在查询缓存中已经存放了当前查询需要访问的表信息。如果权限没有问题,MySQL会跳过所有其他阶段,直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行
语法解析器和预处理首先,MySQL通过关键字将SQL语句进行解析,并生成一棵对应的\"解析树\",MySQL解析器将使用MySQL语法规则验证和解析查询,例如,它将验证是否使用错误的关键字,或者使用关键字的顺序是否正确等,再或者它还会验证引号是否能前后匹配。预处理器则根据一些MySQL规则进一步检查解析树是否合法,例如,这里将检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义,下一步预处理器会验证权限。这通常很快,除非服务器上有很多的权限配置。
下面是一些MySQL能够处理的优化类型:1.重新定义关联表的顺序数据表的关联并不总是按照在查询中指定的顺序进行。决定关联的顺序是优化器很重要的一部分功能2.将外连接转化为内连接(外连接:左、右连接)并不是所有的OUTER JOIN语句都必须以外连接的方式执行。诸多因素,例如WHERE条件、库表结构都可能会让外连接等价于一个内连接。MySQL能够识别这点并重写查询,让其可以调整关联顺序3.使用等价变化规则MySQL可以使用一些等价变化来简化并规范表达式。它可以合并和减少一些比较,还可以移除一些恒成立和一些恒不成立的判断。例如(5=5 AND a > 5) 将被改写为a>5。类似的,如果有(a<b AND b = c)AND a =5则会改写为 b>5 AND b =c AND a =5.这些规则对于我们编写条件语句很有用4.优化COUNT()、MIN()和MAX()索引和列是否可为空通常可以帮助MySQL优化这类表达式。例如,要找到某一列的最小值,只需要查询对应B-Tree索引最左端的记录,MySQL可以直接获取索引的第一行记录。在优化器生成执行计划的时候可以利用这一点,在B-Tree索引中,优化器会将这个表达式作为一个常数对待。类似的,如果要查找一个最大值,也只需读取B-Tree索引的最后一条记录。如果MySQL使用了这种类型的优化,那么在EXPLAIN中就可以看到\"Select tables optimized away\
6.覆盖索引扫描当索引中的列包含所有查询中需要使用的列的时候,MySQL就可以使用索引返回需要的数据,而无须查询对应的数据行7.子查询优化MySQL在某些情况下可以将子查询转换一种效率更高的形式,从而减少多个查询多次对数据进行访问8.提前终止查询在发现已经满足查询需求的时候,MySQL总是能够立刻终止查询。一个典型的例子就是当使用了LIMIT子句的时候,除此之外,MySQL还有几类情况也会提前终止查询,例如发现了一个不成立的条件,这时MySQL可以立刻返回一个空结果。从下面的例子可以看到这一点:```sqlmysql> EXPLAIN SELECT film.film_id FROM sakila.film WHERE film_id = -1;+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | no matching row in const table |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+1 row in set (0.05 sec)```从这个例子看到查询在优化阶段就已经终止。除此之外,MySQL在执行过程中,如果发现某些特殊的条件,则会提前终止查询。当存储引擎需要检索\"不同取值\"或者判断存在性的时候,MySQL都可以使用这类优化。例如,我们现在需要找到没有演员的所有电影```sqlmysql> SELECT film.film_id -> FROM sakila.film -> LEFT OUTER JOIN sakila.film_actor USING(film_id) -> WHERE film_actor.film_id IS NULL;+---------+| film_id |+---------+| 257 || 323 || 803 |+---------+3 rows in set (0.05 sec)```这个查询将会过滤掉所有有演员的电影。每一部电影可能会有很多的演员,但是上面的查询一旦找到任何一个,就会停止并立刻判断下一部电影,因为只要有一名演员,那么WHERE条件则会过滤掉这类电影。类似这种\"不同值/不存在\"的优化一般可用于DISTINCT、NOT EXIST()或者LEFT JOIN类型的查询
查询优化器现在语法书被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。MySQL使用基于成本的优化器,它将尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。最初,成本的最小单位是随机读取一个4K数据页的成本,后来(成本计算公式)变得更加复杂,并且引入了一些\"因子\"来估算某些操作的代价,如当执行一次WHERE条件比较的成本.可以通过查询当前会话的Last_query_cost的值来得知MySQL计算的当前查询的成本;```sqlmysql> SELECT SQL_NO_CACHE COUNT(*) FROM film_actor;+----------+| COUNT(*) |+----------+| 5462 |+----------+1 row in set (0.07 sec)mysql> SHOW STATUS LIKE 'Last_query_cost';+-----------------+-------------+| Variable_name | Value |+-----------------+-------------+| Last_query_cost | 1104.399000 |+-----------------+-------------+1 row in set (0.07 sec)```这个结果表示MySQL的优化器认为大概需要做1040个数据页的随机查找才能完成上面的查询。这是根据一系列的统计信息计算得来的:每个表或者索引的页面个数、索引的基数(索引中不同值得数量)、索引和数据行得长度、索引分布情况。优化器再评估成本的时候并不考虑任何层面的缓存,它假设读取任何数据都需要一次磁盘IO.有很多种原因会导致MySQL优化器选择错误的执行计划,如下所示:1.统计信息不准确。MySQL依赖存储引擎提供的统计信息来评估成本,但是有的存储引擎提供的信息是准确的,有的偏差可能非常大。例如,InnoDB因为其MVCC的架构,并不能维护一个数据表的行数的精确统计信息2.执行计划种的成本估算不等同于实际执行的成本,所以即使统计信息准确,优化器给出的执行计划也可能不是最优的。例如有时候某个执行计划虽然需要读取更多的页面,但是它的成本却更小。因为如果这些页面都是顺序读或者这些页面已经在内存种的花,那么它的访问成本将很小。MySQL层面并不知道哪些页面在内存中、哪些在磁盘上,所以查询实际执行过程中到底需要多少次物理IO是无法得知的。3.MySQL的最优可能和你想的最优不一样。你可能希望执行时间尽可能的短,但是MySQL只是基于其成本模型选择最优的执行计划,而有些时候这并不是最快的执行方式。所以,这里我们看到的根据执行成本来选择执行计划并不是完美的模型4.MySQL从不考虑其他并发执行的查询,这可能会影响到当前查询的速度5.MySQL也并不是任何时候都是基于成本的优化。有时也会给予一些固定的规则,例如,如果存在全文搜索的MATCH()子句,则在存在全文索引的时候就使用全文索引。即使有时候使用别的索引和WHERE条件可以远比这种方式要快,MySQL也仍然会使用对应的全文索引。6.MySQL不会考虑不受其控制的操作的成本,例如执行存储过程或者用户自定义函数的成本7.后面我们还会看到,优化器有时候无法去估算所有可能的执行计划,所以它可能错过实际上最优的执行计划MySQL的查询优化器是一个非常复杂的部件,它使用了很多优化策略来生成一个最优的执行计划。优化策略可以简单地分为两种,一种是静态优化,一种是动态优化。静态优化可以直接对解析树进行分析,并完成优化。例如,优化器可以通过一些简单的代数变换将WHERE条件转换成另一种等价形式。静态优化不依赖于特别的数值,如WHERE条件中带入的一些常数等。静态优化在第一次完成后就一直有效,即使使用不同的参数重复执行查询也不会发生变化。可以认为这是一种\"编译优化\"。相反,动态优化则和查询的上下文有关,也可能和很多其他因素有关,例如WHERE条件中的取值、索引中条目对应的数据行数等。这需要在每次查询的时候都重新评估,可以认为这是\"运行时优化\"
数据和索引的统计信息。重新回忆一下MySQL的架构,MySQL架构由多个层次组成。在服务器层有查询优化器,却没有保存数据和索引的统计信息。统计信息由存储引擎实现,不同的存储引擎可能会存储不同的统计信息(也可以按照不同的格式存储统计信息)。某些引擎,例如Archive引擎,则根本没有存储任何统计信息。因为服务器层没有任务统计信息,所以MySQL查询优化器在生成查询的执行计划时,需要向存储引擎获取相应的统计信息。存储引擎则提供给优化器对应的统计信息,包括:每个表或者索引有多少个页面、每个表的每个索引的基数是多少、数据行和索引长度、索引的分布信息等。优化器根据这些信息来选择一个最优的执行计划。
另一种可视化查询执行计划的方法是根据优化器执行的路径绘制出对应的\"泳道图\"。如图所示,绘制了前面示例中内连接的泳道图。如图所示,从本质上说,MySQL对所有的类型的查询都以同样的方式运行。例如,MySQL在FROM子句中遇到子查询时,先执行子查询并将其结果放到一个临时表中(MySQL的临时表是没有任何索引的,在编写复杂的子查询和关联查询的时候需要注意这一点。这一点对UNION查询也一样)。然后将这个临时表当作一个普通表对待(正如其名\"派生表\")。MySQL在执行UNION查询时也使用类似的临时表,在遇到右外连接的时候,MySQL将其改写成等价的左外连接。简而言之,当前版本的MySQL会将所有的查询类型都转换成类似的执行计划。不过,不是所有的查询都可以转换成上面的形式。例如,全外连接就无法通过嵌套玄幻和回溯的方式完成,这是当发现关联表中没有找到任何匹配行的时候,则可能是因为关联是恰好从一个没有任何匹配的表开始。这大概也是MySQL并不支持全外连接的原因,还有些场景,虽然可以转换成嵌套循环的方式,但是效率却非常差。
MySQL如何执行关联查询。MySQL中\"关联\"(join)一次所包含的意义比一般意义上理解的要更广泛。总的来说,MySQL认为任何一个查询都是一次\"关联\
排序优化。无论如何排序都是一个成本很高的操作,所以从性能角度考虑,应尽可能避免排序或者尽可能避免对大量数据进行排序。前面已经提到了,当不能使用索引生成排序结果的时候,MySQL需要自己进行排序,如果数据量小则在内存种进行,如果数据量大则需要使用磁盘,不过MySQL将这个过程统一称为文件排序(filesort),即使完全是内存排序不需要任何磁盘文件时也是如此。如果需要排序的数据量小于\"排序缓冲区\",MySQL使用内存进行\"快速排序\"操作。如果内存不够排序,那么MySQL会先将数据分块,对每个独立的块使用\"快速排序\
查询优化处理查询的生命周期的下一步时将一个SQL转换成一个执行计划,MySQL再依照这个执行计划和存储引擎进行交互。这包括多个子阶段:解析SQL、预处理、优化SQL执行计划。这个过程中任何错误(例如语法错误)都可能终止查询。接下来选择性地介绍其中几个独立的部分,在实际执行中,这几部分可能一起执行也可能单独执行。目的是帮助大家理解MySQL是如何执行查询,以便写出更优秀的查询。
查询执行引擎在解析和优化阶段,MySQL将生成查询对应的执行计划,MySQL的查询执行引擎根据这个执行计划来完成整个查询。这里执行计划是一个数据结构,而不是和其他的关系型数据库那样生成对应的字节码。相对于查询优化阶段,查询执行阶段不是那么复杂:MySQL只是简单地根据执行计划给出的指令逐步执行。在根据执行计划逐步执行的过程中,有大量的操作需要通过调用存储引擎实现的接口来完成,这些接口也就是我们成为\"handler API\"的接口。查询中的每一个表由一个handler的实例表示。实际上,MySQL在优化阶段就为每个表创建了一个handler实例,优化器根据这些实例的接口可以获取表的相关信息,包括表的所有列名、索引统计信息,等等。存储引擎接口有着非常丰富的功能,但是底层接口却只有几十个,这些接口像\"搭积木\"一样能够完成查询的大部分操作。例如,有一个查询某个索引的第一行的解耦,再有一个查询某个索引条目的下一个条目的功能,有了这两个功能我们就可以完成全索引扫描的操作了。这种简单的接口模式,让MySQL的存储引擎插件式架构成为可能,但是正如前面的讨论,也给优化器带来了一定的限制。并不是所有的操作都有handler完成。例如,当MySQL需要进行表锁的时候,handler可能会实现自己的级别的、更细粒度的锁,如InnoDB就实现了自己的行基本锁,但这并不能代替服务器层的表锁。如果是所有存储引擎共有的特性则由服务器层实现,比如时间和日期函数、视图、触发器等等。为了执行查询,MySQL只需要重复执行计划中的各个操作,知道完成所有的数据查询。
返回结果给客户端。查询执行的最后一个阶段是将结果返回给客户端。即使查询不需要返回结果集给客户端,MySQL仍然会返回这个查询的一些信息,如该查询影响到的行数。如果查询可以被缓存,那么MySQL在这个阶段也会将结果存放到查询缓存中。MySQL将结果集返回客户端是一个增量、逐步返回的过程。例如,我们回头看看前面的关联操作,一旦服务器处理完成最后一个关联表,开始生成第一条结果时,MySQL就可以开始向客户端逐步返回结果集了。这样处理有两个好处:服务器端无须存储太多的结果,也就不会因为要返回太多结果而消耗太多内存。另外,这样的处理也让MySQL客户端第一时间获得返回的结果。结果集中的每一行会以一个满足MySQL客户端/服务器通信协议的封包发送,再通过TCP协议进行传输,在TCP传输的过程中,可能对MySQL的风暴进行缓存然后批量传输。
查询执行的基础。当希望MySQL能够以更高效的性能运行查询时,最好的办法就是弄清楚MySQL是如何优化和执行查询的。一旦理解这一点,很多查询优化工作实际上就是遵循一些原则让优化器能够按照预想的合理的方式运行。MySQL执行一个查询的过程。根据如图所示,我们可以看到当向MySQL发送一个请求的时候,MySQL到底做了些什么。1.客户端发送一条查询给服务器2.服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段3.服务器端进入SQL解析、预处理,再由优化器生成对应的执行计划4.MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询5.将结果返回给客户端上面的每一步都比想象的复杂,接下来我们会看到在每一个阶段查询处于何种状态。查询优化器是其中特别复杂也特别难以理解的部分。还有很多的例外情况,例如,当查询使用绑定变量后,执行路径会有所不同
如何用好关联子查询。并不是所有关联子查询的性能都回很差。如果有人跟你说:\"别用关联子查询\
所以,从理论上来讲,MySQL将使用完全相同的执行计划来完成这个查询。现实世界中,建议通过一些测试来判断使用哪种写法速度会更快。针对上面的案例,测试结果也是不同的,如表所示.测试结果显示,使用子查询的写法要略微慢些!不过每个具体的案例会各有不同,有时候子查询写法也会快些。例如,当返回结果中只有一个表中的某些列的时候。听起来,这种情况对于关联查询效率也会更好。具体情况具体分析,例如下面的关联,我们希望返回所有演员参演的电影,因为一个电影会有很多演员参演,所以可能会返回一些重复的记录:```sqlmysql> SELECT film.film_id FROM sakila.film INNER JOIN sakila.film_actor USING(film_id);```我们需要使用DISTINCT和GROUP BY来移除重复的记录:```sqlmysql> SELECT DISTINCT film.film_id FROM sakila.film INNER JOIN sakila.film_actor USING(film_id);```但是,回头看看这个查询,到底这个查询返回的结果集意义是什么?至少这样的写法回访SQL的意义很不明显。如果使用EXISTS则很容易表达\"有演员参演\"的逻辑,而且不需要使用DISTINCT和GROUP BY,也不会产生重复的结果集,我们知道一旦使用了DISTINCT和GROUP BY,那么在查询的执行过程中,通常需要产生临时中间表。下面我们用子查询的写法替换上面的关联:```sqlmysql> SELECT film_id FROM sakila.film WHERE EXISTS(SELECT * FROM sakila.film_actor WHERE film.film_id = film_actor.film_id);```再一次,我们需要通过测试来比对这两种写法,哪个更快一些,测试结果如表所示.在这个案例中,我们看到子查询速度要比关联查询更快些。通过上面这个案例,主要想说明两点:一时不需要听取那些关于子查询的\"绝对真理\",二十应该用测试来验证对子查询的执行计划和相应时间的假设。我们应该通过测试来验证猜想
UNION的限制。有时,MySQL无法将限制条件从外层\"下推\
索引合并优化。前面已经讨论过,在5.0和更新的版本中,当WHERE子句中包含多个复杂条件的时候,MySQL能够访问单个表的多个索引以合并和交叉过滤的方式来定位需要查找的行(索引下推)
等值传递。某些时候,等值传递会带来一些意想不到的额外消耗。例如,有一个非常大的IN()列表,而MySQL优化器发现存在WHERE、ON或者USING的子句,将这个人列表值和另一个表的某些列相关联。那么优化器会将IN()列表都复制应用到关联的各个表中。通常,因为各个表新增了过滤条件,优化器可更高效地从存储引擎过滤记录,但是如果这个列表非常大,则会导致优化和执行都会变慢。
font color=\"#a23735\
MySQL通过全表扫描找到需要的记录
使用松散索引扫描效率会更高,但是MySQL现在还不支持这么做
MySQL查询优化器的局限性。MySQL的万能\"嵌套循环\"并不是对每种查询都是最优的。不过还好,MySQL查询优化器只对少部分查询不适用,而且我们往往可以通过改写查询让MySQL高效地完成工作。还有一个好消息,MySQL5.6版本正式发布后,会消除很多MySQL原本的限制,让更多的查询能够以尽可能高的效率完成。
6.SQL_CACHE和SQL_NO_CACHE这个提示告诉MySQL这个结果集是否应该缓存在查询缓存中7.SQL_CALC_FOUND_ROWS严格来说,这并不是一个优化器提示。它不会告诉优化器任何关于执行计划的东西,它会让MySQL返回的结果集包含更多的信息。查询中加上该提示MySQL会计算除去LIMIT子句后这个查询要返回的结果集的总数,而实际上只返回LIMIT要求的结果集。可以通过函数FOUND_ROW()获得这个值8.FOR UPDATE和LOCK IN SHARE MODE这也不是真正的优化器提示。这两个提示主要控制SELECT语句的所机制,但只对实现了行级锁的存储引擎有效。使用该提示会对符合查询条件的数据行加锁。对于INSERT...SELECT语句是不需要这两个提示的,因为对于MySQL5.0和更细那版本会默认给这些记录加上读锁(可以禁用该默认行为,但不是个好主意)。唯一内置的支持这两个提示的引擎就是InnoDB。另外需要记住的是,这两个提示会让某些优化无法正常使用,例如索引覆盖扫描。InnoDB不能再不访问主键的情况下排他地锁定行,因为行的版本信息保存在主键中。糟糕的是,这两个提示经常被滥用,很容易造成服务器的锁争用问题应该尽可能地避免使用这两个提示,通常都有其他更好的方式可以实现同样的目的。9.USE INDEX、IGNORE INDEX和FORCE INDEX这几个提示会告诉优化器使用或者不适用哪些索引来查询记录(例如,在决定关联顺序的时候使用哪个索引)。在MySQL5.0和更早的版本,这些提示并不会影响到优化器选择哪个索引进行排序和分组,在MySQL5.1和之后的版本可以通过新增选项FOR ORDER BY和FOR GROUP BY来指定是否对排序和分组有效。FORCE INDEX和USE INDEX基本相同,除了一点:FORCE INDEX会告诉优化器全表扫描的成本会远远高于索引扫描,哪怕实际上该索引用处不大。当发现优化器选择了错误的索引,或者因为某些原因(比如在不适用ORDER BY的时候希望结果有序)要使用另一个索引时,可以使用该提示。
MySQL5.0和更新版本中,新增了一些参数用来控制优化器的行为:1.optimizer_search_depth这个参数控制优化器在穷举执行计划时的限度。如果查询长时间处于\"Statistics\"状态,那么可以考虑调低此参数。2.optimizer_prune_level该参数默认是打开的,这让优化器会根据需要扫描的行数来决定是否跳过某些执行计划3.optimizer_switch这个变量包含了一些开启/关闭优化器特性的标志位。例如在MySQL5.1中可以通过这个参数来控制禁用索引合并的特性前两个参数是用来控制优化器可以走的一些\"结晶\"。这些结晶可以让优化器在处理非常复杂的SQL语句时,仍然可以很搞笑,但这也可能让优化器错过一些真正最优的执行计划。所以应该根据实际需要来修改这些参数。
MySQL升级后的验证。在优化器面前耍一些\"小聪明\"是不好的。这样做收效甚小,但是却给维护带来了很多额外的工作量。在MySQL版本升级的时候,这个问题就很突出了,你设置的\"优化器提示\"很可能会让新版的优化策略失效。MySQL5.0版本引入了大量优化策略,在还没有正式发布的5.6版本中,优化器的改进也是近些年来最大的一次改进。如果要更新到这些版本,当然希望能够从这些改进中受益。新版MySQL基本上在各个方面都有非常大的改进,5.5和5.6这两个版本尤为突出。升级操作一般来说都很顺利,但仍然建议仔细检查各个细节,以防止一些边界情况影响你的应用程序。不过还好,要避免这些,你不需要符出太多的经历,使用Percona ToolKit中的pt-upgrade工具,就可以检查在新版本中运行的SQL是否和老版本一样,返回相同的结果
查询优化器的提示(hint).如果对优化器选择的执行计划不满意,可以使用优化器提供的几个提示(hint)来控制最终的执行计划。下面将列举一些常见的提示,并简单地给出什么时候使用该提示。通过在查询中加入相应的提示,就可以抗旨该查询的执行计划。关于每个提示的具体用法,建议直接阅读MySQL官方手册。有些提示和版本有直接关系。可以使用的一些提示如下:1.HIGH_PRIORITY和LOW_PRIORITY这个提示告诉MySQL,当多个语句同时访问某一个表的时候,那些语句的优先级相对高些、哪些语句的优先级相对低些。HIGH_PRIORITY用于SELECT语句的时候,MySQL会将此SELECT语句重新调度到所有正在等待表锁以便修改的语句之前。实际上MySQL是将其放在表的队列的最前面,而不是按照常规顺序等待。HIGH_PRIORITY还可以用于INSERT语句,其效果只是简单地抵消了全局LOW_PRIORITY设置对该语句的影响。LOW_PRIORITY则正好相反:它会让该语句一直处于等待状态,只要队列中还有需要访问同一个表的语句——即使是哪些比该语句还晚提交到服务器的语句。这就像一个过于礼貌地人站在餐厅门口,只要还有其他顾客在等待就一直不进去,很明显这容易把自己饿坏。LOW_PRIORITY提示在SELECT、INSERT、UPDATE和DELETE语句中都可以使用。这两个提示只对使用表锁地存储引擎有效,千万不要在InnoDB或者其他有细粒度地锁机制和并发控制的引擎中使用。即使是在MyISAM中使用也要注意,因为这两个提示会导致并发插入被禁用,可能会严重降低性能。HIGH_PRIORITY和LOW_PRIORITY经常让人感到困惑。这两个提示并不会获取更多资源让查询\"积极\"工作,也不会让少获取资源让查询\"消极\
4.使用近似值有时候某些业务场景并不要求完全精确的COUNT值,此时可以用近似值来代替。EXPLAIN出来的优化器估算的行数就是一个不错的近似值,执行EXPLAIN并不需要真正地去执行查询,所以成本很低。很多时候,计算精确值的成本非常高,而计算近似值则非常简单。曾经有一个人希望统计他的网站的当前活跃用户数是多少,这个活跃用户数保存在缓存中,过期时间为30分钟,所以每隔30分钟需要重新计算并放入缓存。因此这个活跃用户数本身就不是精确值,所以使用近似值代替是可以接受的。另外,如果要精确统计在线认数,通常WHERE条件会很复杂,一方面需要剔除当前非活跃用户,另一方面还要剔除系统中某些特定ID的\"默认\
优化COUNT()查询.COUNT()聚合函数,以及如何优化使用了该函数的查询,很可能是MySQL中最容易被误解的10个话题之一。在网上随便一搜就能看到很多错误的理解,可能比我们想象的多得多。在做优化之前,先来看看COUNT()函数真正的作用是什么1.COUNT()的作用COUNT()是一个特殊的函数,有两种非常不同的作用:它可以统计某个列值的数量,也可以统计行数,在统计列值时要求列值是非空的(不统计NULL).如果在COUNT()的括号中指定了列或者列的表达式,则统计的就是这个表达式有值的结果数(而不是NULL).因为很多人对NULL理解有问题,所以这里很容易产生误解。COUNT()的另一个作用是统计结果集的行数。当MySQL确认括号内的表达式值不可能为空时,实际上就是在统计行数。最简单的就是当我们使用font color=\"#a23735\
优化子查询关于子查询优化给出的最重要的优化建议就是尽可能使用关联查询,至少当前的MySQL版本需要这样,\"尽可能使用关联\
优化GROUP BY WITH ROLLUP分组查询的一个变种就是要求MySQL对返回的分组结果再做一次超级聚合。可以使用WITH ROLLUP子句来实现这种逻辑,但可能会不够优化。可以通过EXPLAIN来观察其执行计划,特别要注意是否通过文件排序或者临时表实现的,然后再去掉WITH ROLLUP子句看执行计划是否相同。也可以通过前面介绍的优化器提示来固定执行计划。很多时候,如果可以,在应用程序中做超级聚合是更好的,虽然这需要返回给客户端更多的结果。也可以在FROM子句中嵌套使用子查询,或者是通过一个临时表存放中间数据,然后和临时表执行UNION来得到最终结果。最好的办法是尽可能地将WITH ROLLUP功能转移到应用程序中处理
优化特定类型的查询。接下来将介绍如何优化特定类型的查询,对于未来MySQL的版本未必适用。毫无疑问,某一天优化器自己也会实现这里列出的部分或者全部优化技巧。
优化SQL_CALC_FOUND_ROWS分页的时候,另一个常用的技巧是在LIMIT语句中加上SQL_CALC_FOUND_ROWS提示(hint),这样就可以获得去掉LIMIT以后满足条件的行数,因此可以作为分页的总数。看起来,MySQL做了一些非常\"高深\"的优化,像是通过某种方法预测了总行数。但实际上,MySQL只有在扫描了所有满足条件的行以后,才会知道行数,所以加上这个提示以后,不管是否需要,MySQL都会扫描所有满足条件的行,然后再抛弃掉不需要的行,而不是在满足LIMIT的行数后就会终止扫描。所以该提示的代价可能非常高。一个更好的设计是将具体的页数换成\"下一页\"按钮,假设每页显示20条记录,那么我们每次查询时都是用LIMIT返回21条记录并只显示20条,如果第21条存在,那么我们就显示\"下一页\"按钮,否则就说明没有更多的数据,也就无须显示\"下一页\"按钮了。另一种做法时先获取并缓存较多的数据——例如,缓存1 000条——然后每次分页都从这个缓存中获取。这样做可以让应用程序根据结果集的大小采取不同的策略,如果结果集少于1000,就可以在页面上显示所有的分页链接,因为数据都在缓存中,所以这样做性能不会有问题。如果结果集大于1 000,则可以在页面上设计一个额外的\"找到的结果多于1 000条\"之类的按钮。这两种策略都比每次生成全部结果集再抛弃掉不需要的数据的效率要高很多。有时候也可以考虑使用EXPLAIN的结果中的rows列的值来作为结果集总数的近似值(实际上Google的搜索结果总数也是个近似值)。当需要精确结果的时候,再单独使用COUNT(*)来满足需求,这时如果能够使用索引覆盖扫描则通常也会比SQL_CALC_FOUND_ROWS块得多
优化UNION查询。MySQL总是通过创建并填充临时表的方式来执行UNION查询。因此很多优化策略在UNION查询中都没法很好地使用。经常需要手工地将WHERE、LIMIT、ORDER BY等子句“下推”到UNION的各个子查询中,以便优化器可以充分利用这些条件进行优化(例如,直接将这些子句冗余地写一份到各个子查询)。除非确实需要服务器消除重复的行,否则就一定要使用UNION ALL,这一点很重要。如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这回导致对整个临时表的数据做唯一性检查。这样做的代价非常高。即使有ALL关键字,MySQL仍然会用临时表存储结果。事实上,MySQL总是将结果放入临时表,然后再独处,再返回给客户端。虽然很多时候这样做是没有必要的(例如,MySQL可以直接把这些结果返回给客户端)
静态查询分析。Percona Toolkit中的pt-query-advisor能够解析查询日志、分析查询模式,然后给出所有可能存在潜在问题的查询,并给出足够详细的建议。这像是给MySQL所有的查询做一次全面的健康检查。它能检测出许多常见的问题
优化排名语句。使用用户自定义变量的一个特性是你可以在给一个变量赋值的同时使用这个变量,换句话说,用户自定义变量的赋值具有\"左值\"特性。下面的例子展示了如何使用变量来实现一个类似\"行号(row number)\
避免重复查询刚刚更新的数据。如果在更新行的同时又希望获得该行的信息,要怎么做才能避免重复的查询呢?不幸的是,MySQL并不支持像PostgreSQL那样的UPDATE RETURNING语法,这个语法可以帮你在更新行的时候同时返回该行的信息。还好在MySQL中你可以使用变量来解决这个问题。例如,一个用户希望能够更高效地更新一条记录地时间戳,同时希望查询当前记录中存放地时间戳是什么。简单地,可以用下面地代码来实现:```sqlUPDATE t1 SET lastUpdated = NO() WHERE id =1;SELECT lastUpdated FROM t1 WHERE id = 1;```使用变量,我们可以按如下方式重写查询:```sqlUDPATE t1 SET lastUpdated = NOW() WHERE id = 1 AND @now:=NOW();```上面查询看起来仍然需要两个查询,需要两次网络来回,但是这里的第二个查询无须访问任何数据表,所以会快非常多。(如果网络延迟非常大,那么这个优化的意义可能不大,不过这对这个用户,这样做的效果很好)
编写偷懒的UNIO。假设需要编写一个UNION查询,其第一个子查询作为分支条件先执行,如果找到了匹配的行,则跳过第二个分支。在某些业务场景中确实会有这样的需求,比如先在一个频繁访问的表中查找\"热\"数据,找不到再去另外一个较少访问的表中查找\"冷\
用户自定义变量的其他用处不仅是在SELECT语句中,在其他任何类型的SQL语句中都可以对变量进行赋值。事实上,这也是用户自定义变量最大的用途。例如,可以像前面使用子查询的方式改进排名语句一样改进UPDATE语句。不过我们需要使用一些技巧来获得我们希望的效果。有时,优化器会把变量当作一个编译时常量来对待,而不是对其进行赋值。将函数放在类似于LEAST()这样的函数中通常可以避免这样的问题。另一个办法是在查询被执行前检查变量是否被赋值。不同的场景下使用不同的办法。通过一些实际,可以了解所有用户自定义变量能够做的有趣的事情,例如下面这些用法:1.查询运行时计算总数和平均值2.模拟GROUP 语句中的函数FIRST()和LAST()3.对大量数据做一些数据计算4.计算一个大表的MD5散列值5.编写一个样本处理函数,当样本中的数值超过某个边界值的时候将其变成06.模拟读/写游标7.在SHOW语句的WHERE子句中加入变量值
案例学习。通常我们要做的不是查询优化,不是库表结构优化,不是索引优化也不是应用设计优化——在实践中可能要面对所有这些搅和在一起的情况。
查询性能优化
外键约束。InnoDB是目前MySQL中唯一支持外键的内置存储引擎,所以如果需要外键支持那选择就不多了。使用外键是有成本的。比如外键通常都要求每次在修改数据时都要在另一张表中多执行一次查找操作。虽然InnoDB强制外键使用索引,但还是无法消除这种约束检查的开销。如果外键列的选择性很低,则会导致一个非常大且选择性很低的索引。例如,在一个非常大的表上有status列,并希望限制这个状态列的取值,如果该列只能取三个值——虽然这个列本身很小,但是如果主键很大,那么这个索引就会很大——而且这个索引除了做这个外键限制,也没有任何其他的作用了。不过,在某些场景下,外键会提升一些性能。如果想确保两个相关表始终有一致的数据,那么使用外键比在应用程序中检查一致性的性能要高得多,此外,外键在相关数据得删除和更新上,也比在应用中维护要更高效,不过,外键维护操作是逐行进行得,所以这样得更新会比批量删除和更新要慢些。外键约束使得查询需要额外访问一些别的表,这也意味着需要额外的锁。如果向子表中写入一条记录,外键约束会让InnoDB检查对应的父表的记录,也就需要对父表对应记录进行加锁操作,来确保这条记录不会在这个事务完成之时就被删除了。这会导致额外的锁等待,甚至会导致一些死锁。因为没有直接访问这些表,所以这类死锁问题往往难以排查。有时,可以使用触发器来代替外键。对于相关数据的同时更新外键更合适,但是如果外键只是用作数值约束,那么触发器或者显式地限制取值会更好些。如果只是使用外键做约束,那通常在应用程序里实现该约束会更好。外键会带来很大的额外消耗。碰到过很多案例,在对性能进行剖析时发现外键约束就是瓶颈所在,删除外键后性能立即大幅提升。
布尔全文索引。在布尔搜索中,用户可以查询中自定义某个被搜索的词语的相关性。布尔搜索通过停用词列表过滤掉那些\"噪声\"词,除此之外,布尔搜索还要求搜索关键词长度必须大于ft_min_word_len,同时小于ft_max_word_len(事实上,全文索引根本不会对太短或者太长的词语进行索引,但是这里说的不是一回事。一般地,MySQL本身并不会因为搜索关键词过长或过短而忽略这些词语,但是查询优化器的某些部分却可能这样做)。搜索返回的结果是未经排序的。当编写一个布尔搜索查询时,可以通过一些前缀修改时符来定制搜索,表中列出了最常用的修饰符。如表所示。还可以使用其他的操作,例如使用括号分组。基于此,就可以构造出一些复杂的搜索查询。还是继续使用sakila.film_text来举例,现在我们需要搜索既包含词\"factory\"又包含\"casualties\"的记录。在前面我们已经使用自然语言搜索查询实现找到这两个词中的任何一个SQL写法。使用布尔搜索查询,我们可以指定返回结果必须同时包含\"factory\"和\"casualties\
全文索引的限制和替代方案。MySQL的全文索引实现有很多的设计本身带来的限制。在某些场景下这些限制是致命的,不过也有很多办法绕过限制。例如,MySQL全文索引中只有一种判断相关性的方法:词频。索引也不会记录索引词在字符串中的位置,所以位置也就无法用在相关性上。虽然大多数情况下,尤其是数据量很小的时候,这些限制都不会影响使用,但也可能不是你锁想要的。而且MySQL的全文索引也没有提供其他可选的相关性排序算法(它无法存储基于相对位置的相关性排序数据)。数据量的大小也是一个问题。MySQL的全文索引只有全部在内存的时候,性能才非常好。如果内存无法装在全部索引,那么搜索速度可能会非常慢。当你使用精确短语搜索时,想要好的性能,数据和索引都需要在内存中。相比其他的索引类型,当INSERT、UPDATE和DELETE操作进行时,全文索引的操作代价都很大:1.修改一段文本中的100个单词,需要100词索引操作,而不是一次2.一半来说列长度并不会太影响其他的索引类型,但是如果是全文索引,三个单词的文本和10 000个单词的文本,性能可能会相差几个数量级3.全文索引会有更多的碎片,可能需要做跟更多的OPTIMIZE TABLE操作全文索引还会影响查询优化器的工作。索引选择、WHERE子句、ORDER BY都有可能不是按照你所预想的方式来工作:1font color=\"#a23735\
全文索引的配置和优化。全文索引的日常维护通常能够大大提升性能。\"双B-Tree\"的特殊结构、再某些文档中比其他文档要包含多得多的关键字,这都使得全文索引比起普通索引有更多的碎片问题,所以需要经常使用OPTIMIZE TABLE来减少碎片。如果应用时IO密集型的,那么定期地进行全文索引重建可以让性能提升很多。如font color=\"#a23735\
全文索引。通过数值比较、范围过滤等就可以完成绝大多数我们需要的查询了。但是,如果你希望通过关键字的匹配来进行查询过滤,那么就需要基于相似度的查询,而不是原来的精确数值比较。全文索引就是为这种场景设计的。全文索引有着自己独特的语法。没有索引也可以工作,如果有索引效率会更高。用于全文搜索的索引有着独特的结构,帮助这类查询找到匹配某些关键字的记录。你可能没有在意过全文索引,不过至少应该对一种全文索引技术比较熟悉:互联网搜索引擎。font color=\"#a23735\
RedoLog日志和Binlog日志
内部XA事务。MySQL本身的插件式架构导致在其内部需要使用XA事务。MySQL中各个存储引擎是完全独立的,彼此不知道对方的存在,所以一个跨存储引擎的事务就需要一个外部的协调者,如果不使用XA协议,例如,跨存储引擎的事务提交就只是顺序地要求每个存储引擎各自提交。如果在某个存储提交过程中发生系统崩溃,就会破坏事务的特性(要么全部提交,要么就不做任何操作)如果将MySQL记录的二进制日志操作看作一个独立的\"存储引擎\",就不难理解为什么即使是一个存储引擎参与的事务仍然需要XA事务了。在存储引擎提交的同时,需要将\"提交\"的信息写入二进制日志,这就是一个分布式事务,只不过二进制日志的参与者是MySQL本身。XA事务为MySQL带来巨大的性能下降。从MySQL5.0开始,它破坏了MySQL内部的\"批量提交\"()一种通过单磁盘IO操作完成多个事务提交的技术),使得MySQL不得不进行多次额外的fsync()调用。具体的,一个事务如果开启了二进制日志,则不仅需要对二进制日志进行持久化操作,InnoDB事务日志还需要两次日志持久化操作。换句话说,如果希望有二进制日志安全的事务实现,则至少需要做三次fsync()操作。唯一避免这个问题的办法就是关闭二进制日志,并将innodb_support_xa设置为0.(一个常见的误区是认为innodb_support_xa只有在需要XA事务的时候才需要打开。这是醋无的:该参数还会控制MySQL内部存储引擎和二进制日志之间的分布式事务。如果你真正关心你的数据,你需要将这个参数打开)。但这样的设置是非常不安全的,而且这回导致MySQL赋值也没法正常工作。复制需要二进制日志和XA事务的支持,另外——如果希望数据尽可能安全——最好还要将sync_binlog设置成1, 这时存储引擎和二进制日志才是真正同步的(否则,XA事务支持就没有意义了,因为事务提交了二进制日志却可能没有\"提交\"到磁盘)。这也是为什么强烈建议使用带电池保护的RAID卡写缓存:这个缓存可以大大加快fsync()操作的效率
MySQL如何判断缓存命中。MySQL判断缓存命中的办法很简单:缓存放在一个引用表,通过一个哈希值引用,整个哈希值包括了如下因素,即查询本身、当前要查询的数据库、客户端协议的版本等一些其他可能会影响返回结果的信息。当判断缓存是否命中时,MySQL不会解析、\"正规化\"或者参数化查询语句,而是直接使用SQL语句和客户端发送过来的其他原始信息。任何字符上的不同,例如空格、注释——任何的不同——都会导致缓存的不命中。(对于这个规则,Percona Server是个例外。它会先将所有的注释语句删除,然后再比较查询语句是否有缓存。这是一个通用的需求,这样可以在查询语句中带入更多的处理过程信息)。所以在编写SQL语句的时候,需要特别注意这点。通常使用统一的编码规则是一个好的习惯,在这里这个好习惯会让你系统运行得更快。当查询语句中有一些不确定的数据时,则不会被缓存。例如包含函数NOW()或者CURRENT_DATE()的查询不会被缓存。类似的,包含CURRENT_USER或者CONNECTION_ID()的查询语句因为会根据不同的用户返回不同的结果,所以也不会被缓存。事实上,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql库中的系统表,或者任何包含列级别权限的表,都不会被缓存。我们常听到:\"如果查询中包含一个不确定的函数,MySQL则不会检查查询缓存\"。这个说法是不正确的。因为在检查查询缓存的时候,还没有解析SQL语句,所以MySQL并不知道查询语句中是否包含这类函数。在检查查询缓存之前,MySQL只做一件事情,就是通过一个大小写不敏感的检查看看SQL语句是不是以SEL开头。准确的说法应该是:\"如果查询语句中包含任何的不确定函数,那么在查询缓存中是不可能找到缓存结果的\
如图所示。在收缩第一个查询结果使用的缓存空间时,就会在第二个查询结果之间留下一个\"空隙\"——一个非常小的空闲空间,因为小于query_cache_min_res_unit而不能再次被查询缓存使用。这类\"空隙\"我们成为碎片,这在内存管理、文件系统管理上都是经典问题。有很多种情况都会导致碎片,例如缓存失效时,可能导致留下太小的数据块无法在后续缓存中管使用
查询缓存如何使用内存。查询缓存是完全存储在内存中的,所以在配置和使用它之前,我们需要先了解它是如何使用内存的。除了查询结果之外,需要缓存的还有很多别的维护相关的数据。这和文件系统有些类似:需要一些内存专门用来确定哪些内存目前是可用的、哪些是已经用掉的、哪些用来存储数据表和查询结果之前的映射、哪些用来存储查询字符串和查询结果。这些基本的管理维护数据结构大需要需要40KB的内存资源,除此之外,MySQL用于查询缓存的内存被分成一个个的数据块,数据块是变长的。每一个数据块中,存储了自己的类型、大小和存储的数据本身,还外加指向前一个和后一个数据块的指针。数据块的类型有:存储查询结果、存储查询和数据表的映射、存储查询文本,等等。不同的存储快,在内存使用上并没有什么不同,从用户角度来看无须区分它们。当服务器启动的时候,它先初始化查询缓存需要的内存。这个内存池初始是一个完整的空闲块。这个空闲块的大小就是你所配置的查询缓存大小再减去用于维护元数据的数据结构所消耗的空间。当有查询结果需要缓存的时候,MySQL先从大的空间块中申请一个数据块用于存储结果。这个数据块需要大于参数query_cache_min_res_unit的配置,即使查询结果远远小于此,仍需要至少申请query_cache_min_res_unit空间。因为需要在查询开始返回结果的时候就分配空间,而此时是无法预知查询结果到底多大的,所以MySQL无法为每一个查询结果精确分配大小恰好匹配的缓存空间。因为需要先锁住空间块,然后找到合适大小数据块,所以相对来说,分配内存块是一个非常慢的操作。MySQL尽量避免这个操作的次数。当需要缓存一个查询结果的时候,它先选择一个尽可能小的内存块(也可能选择较大的),然后将结果存入其中。如果数据块全部用完,但仍有剩余数据需要存储,那么MySQL会申请一块新数据块——仍然是尽可能小的数据块——继续存储结果数据。当查询完成时,如果申请的内存空间还有剩余,MySQL会将其释放,并放入空闲内存部分。该过程如图所示。我们上面说的\"分配内存块\",并不是指通过函数malloc()向操作系统申请内存,这个操作只在初次创建查询缓存的时候执行一次。这里\"分配内存块\"是指在空闲块列表中找到一个合适的内存块,或者从正在使用的、待淘汰的内存块中回收再使用。也就是说这里MySQL自己管理一大块内存,而不依赖操作系统的内存管理。至此,一些都看起来很简单。不过实际情况要比上图更复杂。例如,我们假设平均查询结果非常小,服务器在并发地向不同的两个连接返回结果,返回完结果MySQL回收剩余数据块空间时发现,回收的数据块小于query_cache_min_res_unit,所以不能够直接在后续的内存块分配中使用。如果考虑到这种情况,数据块的分配就更复杂些,
如果缓存的结果在失效前没有被任何其他的SELECT语句使用,那么这次缓存操作就是浪费时间和内存。我们可以通过查看Com_select和Qcache_inserts的相对值来看看是否一直有这种情况发生。如果每次查询查询都是缓存未命中,然后需要将查询结果放到缓存中,那么Qcache_inserts的大小应该和Com_select相当。所以在缓存完成预热后,我们总希望看到Qcache_inserts远远小于Com_select。不过由于缓存和服务器内部的复杂和多样性,仍然很难说,这个比率是多少才是一个合适的值。所以,上面的\"命中率\"和\"INSERTS和SELECT比率\"都无法直观地反应查询缓存的效率,那么还有什么直观的办法能够反应查询缓存是否对系统有好处?这里推荐查看一个指标\"命中和写入\"的比率。即Qcache_hits和Qcache_inserts的壁纸。根据经验来看,当这个比值大于3:1时通常查询缓存是有效的,不过这个比率最好能够达到10:1.如果你的应用没有达到这个比率,那么就可以考虑禁用查询缓存了。除非你能够通过精确的计算得知:命中带来的性能提升大于缓存失效的消耗,并且查询缓存并没有成为系统的瓶颈。每一个应用程序都会有一个\"最大缓存空间\",甚至对一些纯读的应用来说也一样。最大缓存空间是能够缓存所有可能查询结果的缓存空间综合。理论上,对多数应用来说,这个数值都会非常大。而实际上,由于缓存失效的原因,大多数应用最后使用的缓存空间都比预想的要小。即使你配置了足够大的缓存空间,由于不断地失效,导致缓存空间一直都不会接近\"最大缓存空间\"。通常可以观察查询缓存内存的实际使用情况,来确定是否需要缩小或者扩大查询缓存。如果查询缓存空间长事件都有剩余,那么建议索引;如果经常由于空间不足而导致查询缓存失效,那么则需要增大查询缓存。不过需要注意,如果查询缓存达到了几十兆这样的数量级,是有潜在危险的。另外,可能还需要和系统的其他缓存一起考虑,例如InnoDB的缓存池,或者MyISAM的索引缓存。关于这点是没法简单给出一个公式或者比率来判断的,因为真正的平衡点与应用程序有很大的关系。最好的判断查询缓存是否有效的办法还是通过查询某类查询时间消耗是否增大或者减少来判断。Percona Server通过扩展慢查询可以观察到一个查询是否命中缓存,如果查询缓存没有为系统节省时间,那么最好禁用它
什么情况下查询缓存能发挥作用。并不是什么情况下查询缓存都会提高系统性能的。缓存和失效都会带来额外的消耗,所以只有当缓存带来的资源节约大于本身的资源消耗时才会给系统带来性能提升。这跟具体的服务器压力模型有关。理论上,可以通过观察打开或者关闭查询缓存时候的系统效率来决定是否需要开启查询缓存。关闭查询缓存时,每个查询都需要完整的执行,每一次写操作执行完成后立刻返回;打开查询缓存时,每次读请求先检查缓存是否命中,如果命中则立刻返回,否则就完整地执行查询,每次写操作则需要检查查询缓存中是否有需要失效的缓存,然后再返回。这个过程还比较简单明了,但是要评估打开查询缓存是否能够带来性能提升却并不容易。还有一些外部的因素需要考虑,例如,查询缓存可以降低查询执行的时间,但是却不能减少查询结果传输的网络消耗,如果这个消耗是系统的主要瓶颈,那么查询缓存的作用也很小。因为MySQL再SHOW STATUS中只能提供一个全局的性能指标,所以很难根据此来判断查询缓存是否能够提升性能(Percona和MariaDB对MySQL慢日志进行了改进,会记录慢日志中的查询是否命中查询缓存)。很多时候,全局平均不能反应实际情况。例如,打开查询缓存可以使得一个很慢的查询变得非常快,但是也会让其他查询稍微慢一点点。有时候如果能够让某些关键的查询速度更快,稍微降低一下其他查询的速度是值得的。不过这种情况我们推荐使用SQL_CACHE来优化对查询缓存的使用。对于哪些需要消耗大量资源的查询通常都是非常适合缓存的。例如一些汇总计算查询,具体的如COUNT()等。总的来说,对于复杂的SELECT语句都可以使用查询缓存,例如多表JOIN后还需要做排序和分页,这类查询每次执行消耗都很大,但是返回的结果集却很小,非常适合查询缓存。不过需要注意的是,涉及的表上UPDATE、DELETE和INSERT操作相比SELECT来说要非常少才行。一个判断查询缓存是否有效地直接数据是命中率,就是使用查询缓存返回结果占总查询的比率。当MySQL接收到一个SELECT查询的时候,要么增加Qcache_hits的指,要么增加Com_select的值。所以查询缓存命中率可以由如下公式计算:Qcache_hits/(Qcache_hits+Com_select)不过,查询缓存命中率是一个很难判断的数值。命中率多大才是好的命中率?具体情况要具体分析。只要查询缓存带来的效率提升大于查询缓存带来的额外消耗,即使30%命中率对系统性能提升也有很大好处。另外,缓存了哪些查询也很重要,例如,被缓存的查询本身消耗非常巨大,那么即使缓存命中率非常低,也仍然会对系统性能提升有好处。所以,没有一个简单的规则可以判断查询缓存是否对系统有好处。任何SELECT语句没有从查询缓存中返回都成为\"缓存未命中\"。缓存未命中可能有如下几种原因:1.查询语句无法被缓存,可能是因为查询种包含一个不确定的函数(如CURRENT_DATE),或者查询结果太大而无法缓存。这都会导致状态值Qcache_not_cached增加2.MySQL从未处理这个查询,所以结果也从不曾被缓存过3.还有一种情况是虽然之前缓存了查询结果,但是由于查询缓存的内存用完了,MySQL需要将某些缓存\"逐出\
通用查询缓存优化。库表结构的涉及、查询语句、应用程序涉及都可能会影响到查询缓存的效率。除了前面的介绍之外,还有一些要点需要注意:1.用多个淆表代替一个大表对查询缓存有好处。这个涉及将会使得失效策略能够在一个更合适的粒度上进行。当然,不要让这个原则过分影响你的涉及,毕竟其他的一些有时可能很容易就弥补了这个问题2.批量写入时只需要做一次缓存失效,所以相比单条写入效率更好(另外需要注意,不要同时做延迟写和批量写,否则可能会因为失效导致服务器僵死较长时间)3.因为缓存空间太大,在过期操作的时候可能会导致服务器僵死。一个简单的解决办法就是控制缓存空间的大小(query_cache_size)或者直接禁用查询缓存4.无法在数据库或者表级别控制查询缓存,但是可以通过SQL_CACHE和SQL_NO_CACHE来控制某个SELECT语句是否需要进行缓存。你还可以通过修改会话级别的变量query_cache_type来控制查询缓存5.对于写密集型的应用来说,直接禁用查询缓存可能会提高系统的性能。关闭查询缓存可以移除所有相关的消耗。例如将query_cache_size设置成0,那么至少这部分就不再消耗任何内存了6.因为对互斥信号量的竞争,有时直接关闭查询缓存对读密集型的应用也会有好处。如果你希望提高系统的并发,那么最好做一个相关的测试,对比打开和关闭查询缓存时候的性能差异。如果不像所有的查询都进入查询缓存,但是有希望某些查询走查询缓存,那么可以将query_cache_type设置成DEMAND,然后在希望缓存的查询中加上SQL_CACHE,这虽然需要在查询中加入一些额外的语法,但是可以让你非常自由地控制哪些查询需要被缓存。相反,如果希望缓存多数查询,而少数查询又不希望缓存,那么你可以使用关键字SQL_NO_CACHE
查询缓存的替代方案。MySQL查询缓存工作的原则是:执行查询最快的方式就是不执行,但是查询仍然需要发送到服务器,服务器也还需要做一点点工作。如果对于某些查询完全不需要与服务器通信效果会如何呢?这是客户端的缓存可以很大程度上帮你分担MySQL服务器的压力。
查询缓存。很多数据库产品都能够缓存查询的执行计划,对于相同类型的SQL就可以跳过SQL解析和执行计划生成阶段。MySQL在某些场景下也可以实现,但是MySQL还有另一种不同的缓存类型:缓存完整的SELECT查询结果,也就是\"查询缓存\"。MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存,MySQL会立刻返回结果,跳过了解析、优化和执行解读那。查询缓存系统会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有的缓存数据都将失效。这种机制效率看起来比较低,因为数据表变化时很有可能对应的查询结果并没有变更,但是这种简单实现代价很小,而这点对于一个非常繁忙的系统来说非常重要。查询缓存对应用程序是完全透明的。应用程序无须关心MySQL是通过查询缓存返回的结果还是实际执行返回的结果。事实上,这两种方式执行的结果是完全相同的。换句话说,查询缓存无须使用任何语法。无论是MySQL开启或关闭查询缓存,对应用程序都是透明的。(有一种方式查询缓存可能和原生的SQL工作方式有所不同:默认的,当要查询的表被LOCK TABLES锁住时,查询仍然可以通过查询缓存返回数据。你可以通过参数query_cache_wlock_invaidate打开或者关闭这种行为)。随者现在的通用服务器越来越强大,查询缓存被发现是一个影响服务器扩展性的因素。他可能成为整个服务器的资源竞争单点,在多核服务器上还可能导致服务器僵死。后面再详细介绍如何配合查询缓存,但是很多时候我们还是认为应该默认关闭查询缓存,如果查询缓存作用很大的话,那就配置一个很小的查询缓存空间(如几十兆)。后面再解释如何判断再系统压力下打开查询缓存是否有好处。
MySQL高级特性
了解清楚\"把日志缓冲写到日中文件\"和\"把日志刷新到持久化存储\"之间的不同是很重要的。在大部分操作系统中,把缓冲写到日志只是简单地把数据从InnoDB的内存缓冲转移到了操作系统的缓存,也是在内存里,并没有真的把数据写到持久化存储。因此,如果MySQL崩溃了或者电源断电了,设置0和2通常会导致最多一秒的丢失,因为数据可能只存在于操作系统的缓存。我们说\"通常\
InnoDB事务日志(包含:Redo log 重做日志和Undo log回滚日志)。InnoDB使用日志来减少提交事务时的开销,因为日志中已经记录了事务,就无须在每个事务提交时把缓冲池的脏块刷新(flush)到磁盘中。事务修改的数据和索引通常会映射到表空间的随机位置,所以刷新这些变更到磁盘需要很多随机IO.InnoDB假设使用的时常规磁盘(机械磁盘),随机IO比顺序IO要昂贵很多,因为一个IO请求需要时间把磁头移到正确的位置,然后等待磁盘上读出需要的部分,再转到开始位置。InnoDB用日志把随机IO变成顺序IO.一旦日志安全写到磁盘,事务就持久化了,即使变更还没写到数据文件,如果一些糟糕的事情发生了(例如断电了),InnoDB可以重放日志并且恢复已经提交的事务。当然,InnoDB最后还是必须把变更写到数据文件,因为日志有固定的大小。InnoDB的日志是环形方式写的:当写到日志的尾部,会重新跳转到开头继续写,但不胡覆盖还没应用到数据文件的日志记录,因为这样做会清掉已提交事务的唯一持久化记录。InnoDB使用一个后台线程智能地刷新这些变更到数据文件。这个线程可以批量组合写入,使得数据写入更顺序,以提高效率。实际上,事务日志把数据文件的随机IO转换为几乎顺序的日志文件和数据文件IO.把刷新操作转移到后台使查询可以更快完成,并且缓和查询高峰时IO系统的压力。整体的日志文件受控于innodb_log_file_size和innodb_log_files_in_group两个参数,这对写性能非常重要。日志文件的总大小是每个文件的大小之和。默认情况下,只有两个5MB的文件,总共10MB。对高性能工作来说这太小了。至少需要几百MB,或者甚至上GB的日志文件。InnoDB使用多个文件作为一组循环日志,通常不需要修改默认的日志数量,只修改每个日志文件的大小即可。要修改日志文件大小,需要完全关闭MySQL,将旧的日志文件移到其他地方保存,重新配置参数,然后重启。一定要确保MySQL干净地关闭了,或者还有日志文件可以保证需要应用到数据文件的事务记录,否则数据库就无法恢复了。当重启服务器的时候,查看MySQL的错误日志。在重启成功之后,才可以删除旧的日志文件。日志文件大小和日志缓存。要确定理想的日志文件大小,必须权衡正常数据变更的开销和崩溃恢复需要鞥多时间。如果日志太小,InnoDB将必须做更多的检查点,导致更多的日志写。在极个别情况下,写语句可能被拖累,在日志没有空间继续写入前,必须等待变更被应用到数据文件。另一方面,如果日志太大了,在崩溃恢复时InnoDB可能不得不做大量的工作。这可能极大地增加恢复时间,尽管这个处理在新的MySQL版本中已经改善很多。数据大小和访问模式也将影响恢复时间。假设有一个1TB的数据和16GB的缓冲池,并且全部日志大小是128MB。如果缓冲池里面有很多脏页(例如,页被修改了还没被刷写回数据文件),并且它们均匀地分布在1TB数据中,崩溃后恢复将需要相当长一段时间。InnoDB必须从头到尾扫描日志,仔细检查数据文件,如果需要还要应用变更到数据文件。这是很庞大的读写操作!另一方面,如果变更是局部性的——就是说,如果只有几百MB数据被频繁地变更——恢复可能就很快,即使数据和日志文件很大。恢复时间也依赖普通修改操作的大小,这跟数据行地平均长度有关系。较短的行使得更多的修改而已放在同样的日志中,所以InnoDB可能必须在恢复时重放更多修改操作。当InnoDB变更任何数据时,会写一条变更记录到内存日志缓冲区。在font color=\"#a23735\
3.0_DSYNC这个选项使用日志文件调用open()函数时设置0_SYNC标记。它使得所有的写同步——换个说法,只有数据写道磁盘后写操作才返回。这个选项不影响数据文件。0_SYNC标记和0_DIRECT标记的不同之处在于0_SYNC没有禁用操作系统层的缓存。因此,它没有避免双重缓冲,并且它没有使写操作直接操作到磁盘。用了0_SYNC标记,在缓存中写数据,然后发送到磁盘。使用0_SYNC标记做同步写操作,听起来可能跟fsync()作得事情非常相似,但是它们两个的实现无论在操作系统层还是在硬件层都非常不同。用了0_SYNC标记后,操作系统可能把\"使用同步IO\"标记下传给硬件层,告诉设备不要使用缓存,另一方面,fsync()告诉操作系统把修改过的缓冲数据刷写到设备上,如果设备支持,紧接着会传递一个指令给设备刷新它自己的缓存,所以,毫无疑问,数据肯定记录在了物理媒介上。另一个不同是,用了0_SYNC的话,每个write()或pwrite()操作都会在函数完成之前把数据同步到磁盘,完成前函数调用是阻塞的。相对来看,不用0_SYNC标记的写入调用fsync()允许写操作积累在缓存(使得每个写更快),然后一次性刷新所有的数据。再一次吐槽下这个名称,这个选项设置0_SYNC标记,不是0_DSYNC标记,因为InnoDB开发者发现了0_DSYNC的Bug。0_SYNC和0_DSYNC类似于fsync()和fdatasync(),0_SYNC同时同步数据和元数据,但是0_DSYNC只同步数据。4.async_unbuffered这是Windows下的默认值。这个选项让InnoDB对大部分写使用没有缓冲的IO;例外是当innodb_flush_log_at_trx_commit设置为2的时候,对日志文件使用缓冲IO。这个选项使得InnoDB在Windows 2000 、XP,以及更新版本中对数据读写都是用操作系统的原生(重叠的)IO.在更老的Windows版本哪种,InnoDB使用自己用多线程模拟的异步IO5.unbuffered只对Windows有效。这个选项于async_unbuffered类似,但是不适用原生异步IO6.normal只对Windows有效。这个选项让InnoDB不要使用原生异步IO或者无缓冲IO7.Nosync和littersync只为开发使用。这个两个选项在文档中没有并且对生产环境来说不安全,不应该使用这个。如果这些看起来像是一堆不带建议的说明,那么下面是一些建议:如果使用类UNIX操作系统并且RAID控制器带有电池保护的写缓存,建议使用0_DIRECT。如果不是这样,默认值或者0_DIRECT都可能是最好的选择
innod_file_per_table默认是打开的
什么是最终的建议?我们建议使用innodb_file_per_table并且给共享表空间设置大小范围,这样可以过得舒服点(不用处理那些空间回收的事)。如果遇到人恶化头痛的场景,就像上面说的,考虑用下Percona的那个修复。提醒一下,事实上没有必要把InnoDB文件放在传统的文件系统上,像许多的传统数据库服务器一样,InnoDB提供使用裸设备的选项——例如,一个没有格式化的分区——作为它的存储。然而,今天的文件系统已经可以存放足够大的文件,所以经没有必要使用这个选项。使用裸设备可能提升几个百分点的性能,但是我们不认为这点小提升足以抵消这样做带来的坏处,我们不能直接使用文件管理数据。当把数据存在一个裸设备分区时,不能使用mv、cp或其他任何工具来操作它。最终,这点小的性能收益显然不值得。
行的旧版本和表空间。在一个写压力大的环境下,InnoDB的表空间可能增长得非常大。如果事务保持打开状态很久(即使它们没有做任何事),并且使用默认得REPETABLE READ事务隔离级别,InnoDB将不能删除旧得行版本,因为没提交得事务依然需要看到它们。InnoDB把旧版本存在共享表空间,所以如果有更多的数据在更新,共享表空间会持续增长。有时这个问题并非是没有提交的事务的原因,也可能是工作负载的问题:清理过程只有一个线程处理,直到最近的MySQL版本才改进,这可能导致清理线程处理速度跟不上旧版本行数增加的速度。无论发生何种情况,SHOW INNODB STATUS的数据都可以帮助定位问题。查看历史链表的长度会显示了回滚日志的大小,以页为单位。分析TRANSACTIONS部分的第一行和第二行可以证实这个观点,这部分展示了当前事务号以及清理线程完成到了哪个点。如果这个表差距很大,可能有大量的没有清理的事务。这有个例子:```sql---------TRANSACTIONS---------Trx id counter 0 80157601Purge done for trx's n:o < 0 80154573 undo n:o < 0 0```事务标识是一个64比特的数字,由两个32比特的数字(在更新版本的InnoDB中这是一个十六进制的数组)组成,所以需要这一点数学计算来计算差距。在这个例子中旧很简单了,因为最高为是0:那么由80 157 601 - 80 154 573 = 3028个\"潜在的\"没有被清理的事务。我们说\"潜在的\",是因为这跟很多没有清理的行是由很大区别的。只有改变了数据的事务才会创建旧版本的行,但是有很多事务并没有修改过数据(相反的,一个事务也可能修改很多行)如果有个很大的回滚日志并且表空间因此增长得很快,可以强制MySQL减速来使InnoDB的清理线程可以跟得上。这听起来不怎么样,但是没办法。否则,InnoDB将保持数据写入,填充磁盘直到最后磁盘空间饱满,或者表空间大于定义的上线。为了控制写入速度,可以设置innodb_max_purge_lag变量为一个大于0的只。这个只标识InnoDB开始延迟后面的语句更新数据之前,可以等待被清除的最大的事务数量。你必须直到工作负载以决定一个合理的只。例如事务平均影响1KB的行,并且可以容许表空间里有100MB的未清理行,那么可以设置这个值为100 000.牢记,没有清理的行版本会对所有的查询产生影响,因为它们事实上使得表和索引更大了。如果清理线程确实跟不上,性能可能显著地下降。设置innodb_max_purge_lag变量也会降低性能,但是它的伤害较少。在更新版本的MySQL中,甚至在更早版本的Percona Server和MariaDB,清理过程已经显著地提升了性能,并且从其他内部工作任务中分离出来。甚至可以创建多个专用地清理线程来更快地做这个后台工作。如果可以利用这些特性,会比限制服务器地服务能力要好得多
InnoDB表空间。font color=\"#a23735\
双写缓冲(Doublewrite Buffer)InnoDB用双写缓冲来避免页没写完整所导致的数据损坏。当一个磁盘写操作不能完整地完成时,不完整的页写入就可能发生,16KB的页可能只有一部分被写到磁盘上。有多种多样的原因(崩溃、Bug,等等)可能导致页没有写完整。双写缓冲区在这种秦广发生时可以保证数据完整性。font color=\"#a23735\
其他的IO配置项sync_binlog选项控制MySQL怎么刷新二进制日志到磁盘。默认值是0(更新版本中是1),意味着MySQL并不刷新,由操作系统自己决定什么时候刷新缓存到持久化设备。如果这个值比0大,它制定了两次刷新到磁盘的动作之间间隔多少次二进制日志写操作(如果autocommit被设置了,每个独立的语句都是一次血,否则就是一个事务一次写)。把它设置为0和1以外的值是很罕见的。如果没有设置sync_binlog为1,那么崩溃以后可能导致二进制日志没有同步事务数据。这可以轻易地导致复制中断,并且使得及时恢复变得不可能。无论如何,可以把这个值设置为1来获得安全地保障。这样就会要求MySQL同步把二进制日志和事务日志两个文件刷新到两个不同地位置。这可能需要要磁盘寻道,相对来说是个很慢的操作。像InnoDB日志文件一样,把二进制日志放到一个带有电池保护的写缓存的RAID卷,可以极大地提升性能。事实上,写和刷新二进制日志缓存其实比InnoDB事务日志要昂贵多了,因为不像InnoDB事务日志,每次写二进制日志都会增加它们的大小。这需要每次写入文件系统都更新元信息。所以,设置sync_binlog=1可能比innodb_flush_log_at_trx_commit=1对性能你的损害要大得多,尤其是网络文件系统,例如NFS.一个跟性能无关的提示,关于二进制日志:如果希望使用expire_logs_days选项来自动清理旧的二进制日志,就不要用rm命令去删。服务器会感到困惑并且拒绝自动删除它们,并且PURGE MASTER LOGS也将停止工作。解决的办法是,如果发现了这种情况,就手动重新同步\"主机名-bin.index\"文件,可以用磁盘上现有日志文件的列表来更新。把带有电池保护写缓存的高质量RAID控制器设置为使用写回(Writeback)策略,可以支持每秒数千的写入,并且依然会保证写到持久化存储。数据写到了带有电池的高速缓存,所以即使系统断电它也能存在。但电源恢复时,RAID控制器会在磁盘被设置可用前,把数据从缓存中写到磁盘。因此,一个带有电池保护写缓存的RAID控制器可以显著地提升性能,,这是非常值得的投资。当然,SSD存储是另一个选择。
InnoDB的IO配置.InnoDB不仅允许控制怎么恢复,还允许控制怎么打开和刷新数据(文件),这会对恢复和整体性能产生巨大的影响。尽管可以影响它的行为,InnoDB的恢复流程实际上是自动的,并且经常在InnoDB启动时运行。撇开恢复并假设InnoDB没有崩溃或者出错,InnoDB依然有很多需要配置的地方。它有一系列复杂的缓存和文件涉及可以提升性能,以及保证ACID特性,并且每一部分都是可配置的,如图所示。对于常见的应用,最重要的一小部分内容是InnoDB日志文件大小、InnoDB怎样刷新它的日志缓冲,以及InnoDB怎样执行IO.
可以使用多个设置,用逗号分割。例如\
配置MySQL的IO行为。有一些配置影响着MySQL怎样同步数据到磁盘以及如何做恢复操作。这些操作对性能的影响非常大,因为都涉及到昂贵的IO操作。它们也表现了性能和数据安全之间的权衡。通常,保证数据立刻并且一致地写到磁盘是很昂贵的。如果能够冒一点磁盘写可能没有真正持久化到磁盘的风险,就可以增加并发性和减少IO等待,但是必须决定可以容忍多大的风险
innodb_old_blocks_time。InnoDB有两段缓冲池LRU(最近最少使用)链表,设计目的是防止换出长期很多次的页面。像mysqldump产生的这种一次性的(大)查询,通常会读取页面到缓冲池的LRU列表,从中读取需要的行,然后移动到下一页。理论上,两段LRU链表将阻止此页取代很长一段时间内都需要用到的页面被放入\"年轻(Young)\"子链表,并且只在它被已被浏览过多次后将其移动到\"年老(Old)\"子链表。但是InnoDB默认没有配置为防止这种情况,因为页内有很多行,所以从页面读取的行的多次访问,会导致它立即被转移到\"年老(Old)\"子链表,对哪些需要长时间缓存的页面带来换出的压力。这个变量指定一个页面从LRU链表的\"年轻\"部分转移到\"年老\"部分之前必须经过的毫秒数。默认情况下将它设置为0,将它设为诸如1000毫秒(一秒)这样的小一点的值,在基准测试中已被证明非常有效。
高级InnoDB设置
内存交换区。当操作系统因为没有足够的内存而将一些虚拟内存写到磁盘就会发生内存交换(内存交换有时称为页面交换,从技术上来说,它们是不同的东西,但是人么你通常把它们作为同义词)内存交换对操作系统中运行的进程是透明的。只有操作系统知道特定的虚拟内存地址是在物理内存还是硬盘。内存交换对MySQL性能影响是很糟糕的,它破坏了缓存在内存的目的,并且相对于使用很小的内存做缓存,使用交换区的性能更差。MySQL和存储引擎有很多算法来区别对待内存中的数据和硬盘上的数据,因为一般都假设内存数据访问代价更低。因为内存交换对用户进程不可见,MySQL(或存储引擎)并不知道数据实际上已经移动到磁盘,还会以为在内存中。结果会导致很差的性能。例如,若存储引擎认为数据依然在内存,可能觉得为\"短暂\
使用如下命令查看系统手册:```cman vmstat```vmstat(8)使用手册如图
如何阅读iostat的输出。现在让我们转移到iostat。默认情况下,它显示了与vmstat相同的CPU使用信息。我们通常只是对IO统计感兴趣,所以使用下面的命令之战是扩展的设备统计:```ciostat -dx 5```与vmstat一样,第一行报告显示的是自系统启动以来的平均值(通常删掉它节省空间),然后接下来的报告显示了增量的平均值,每个设备一行。有多种选项显示和隐藏列。官方文档有点难以理解,因此我们必须从源码中挖掘真正显示的内容是什么。说明的列信息如下:1.rrqm/s和wrqm/s每秒合并的读和写请求。\"合并的\"意味着操作系统从队列中拿出多个逻辑请求合并为一个请求到实际磁盘2.r/s和w/s每秒发送到设备的读和写请求3.rsec/s和wsec/s每秒读和写的扇区数。有些系统也输出为rkB/s和wkB/s,意味着每秒读写的千字节数。为了简洁,省略了那些指标说明。4.avgrq-sz请求的扇区数5.avgqu-sz在设备队列中等待的请求数6.await磁盘队列上花费的毫秒数。很不幸,iostat没有独立统计读和写的请求,它们实际上不应该被一起平均7.svctm服务请求花费的毫秒数,不包括排队时间8.%util至少有一个活跃请求所占时间的百分比。如果熟悉队列理论张利用率的标准定义,那么这个命名很莫名其妙。它其实不是设备的利用率。超过一块硬盘的设备(例如RAID控制器)比一块硬盘的设备可以支持更高的并发,但是%util从来不会超过100%,除非在计算机时有四舍五入的错误。因此,这个指标无法真实反映设备的利用率,实际上跟文档说的相反,除非只有一块物理磁盘的特殊例子。可以用iostat的输出推断某些关于机器IO自系统的实际情况。一个重要的度量标准时请求服务的并发数。因为读写的单位时每秒而服务时间的单位是千分之一秒,所以可以利用利特尔法则(Littele's Law)得到下面的公式,计算出设备服务的并发请求数(另一种计算并发的方式是通过平均队列大小、服务时间,以及平均等大i时间:(avuqu_sz * svctm) / await):```cconcurrency = (r/s + w/s) * (svctm/1000)```把数字带入并发公式,可以得到(0.38 + 0.02) * (0.43 / 1000) = 0.000172.由于我这台服务器配置是乞丐版的,所以得到的并发数比较低。这意味着在一个采样周期内,这个设备平均要服务0.000172次的请求。
CPU密集型的机器。CPU密集型服务器的vmstat输出通常在us列会有一个很高的值,报告了花费在非内核代码上的CPU时钟;也可能在sy列有很高的值,表示系统CPU利用率,超过20%就足以令人不安了。在大部分情况下,也会有进程队列排队时间(在r列报告的)。如图所示。注意,这里也可能有合理数量的上下文切换(cs列),除非每秒超过100 000次或更多,一般都不用担心上下文切换。当操作系统停止一个进程转而运行另一个进程时,就会产生上下文切换。例如,一查询语句在MyISAM上执行了一个非覆盖索引扫描,就会先从索引中读取元素,然后根据索引再从磁盘上读取页面。如果页面不在操作系统缓存中,就需要从磁盘进行物理读取,着就会导致上下文切换中断进程处理,直到IO完成。这样一个查询可以导致大量的上下文切换。
发生内存交换的机器。一台正在发生内存交换的机器可能在swapd列有一个很高的值,也可能不高。但是可以看到si和so列有很高的值,这是我们不希望看到的
空闲的机器。为完整期间,下面也给出一台空闲机器上的vmstat输出。注意,没有在运行或或被阻塞的进程,idle列显示CPU是100%空闲,st列展示了从\"虚拟机\"偷来的时间
操作系统状态。操作系统会提供一些帮助发现操作系统和硬件正在做什么的工具。其中包括最常用的工具iostat和vmstat。如果系统不能提供它们中的任何一个,有可能提供了相似的替代品。当然目的不是让大家熟练使用iostat和vmstat,而是告诉大家用类似的工具诊断问题时应该看什么指标。除了这些,操作系统也许还提供了其他的工具,如mpstat或者sar.如果对系统的其他部分感兴趣,例如网络,你可能希望使用ifconfig(除了其他信息,它能显示了多少次网络错误)或者netstat.默认情况下,vmstat和iostat只是生成一个报告,展示自系统启动依赖很多计数器的平均值,这其实没什么用。然而,两个工具都可以给出一个间隔参数,让它们生成增量值得报告,展示服务器正在做什么,这更有用
优化服务器设置
复制解决的问题。下面是复制常见的用途。1.数据分布MySQL复制通常不会对贷款造成很大的压力,但在5.1版本引入的基于行的复制会比传统的基于语句的复制模式的带宽压力大,你可以随意地停止或开始复制,并在不同的地理位置来分布数据备份,例如不同的数据中心。即使在不稳定的网络环境下,远程复制也可以工作。但如果为了保持很低的复制延迟,最好有一个稳定的低延迟连接2.负载均衡通过MySQL复制可以将读操作分布到多个服务器上,实现对读密集型应用的优化,并且实现很方便,通过简单的代码修改就能实现基本的负载均衡。对于小规模的应用,可以简单地对机器名做硬编码或使用DNS轮询(将一个机器指向多个IP地址)。当然也可以使用更复杂的方法,例如网络负载均衡这一类的标准负载均衡解决方案,能够很好地将负载分配到不同的MySQL服务器上。Linux虚拟服务器(Linux Virtual Server,LVS)也能很好地工作,3.备份对于备份来说,复制是一项很有意义的技术补充,但复制既不是备份也不能够取代备份4.高可用性和故障切换复制能够帮助应用程序避免MySQL单点失败,一个包含复制的设计良好的故障切换系统能够显著地缩短宕机时间5.MySQL升级测试这种做法比较普遍,使用一个更高版本的MySQL作为备库,保证在升级全部实例前,查询能够在备库按照预期执行。
复制如何工作。在详细介绍如何设置复制之前,让我们先看看MySQL实际上是如何复制数据的。总的来说,复制有三个步骤:1.在主库上把数据更改记录到二进制日志(Binary Log)中(这些记录被称为二进制日志事件)2.备库将主库上的日志复制到自己的中继日志(Relay Log)中3.备库读取中继日志中的事件,将其重放到备库数据之上以上只是概述,实际上每一步都很复杂,如图所示第一步是在主库上记录二进制日志.在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。下一步,备库将主库的二进制日志复制到其本地的中继日志中。首先,备库会启动一个共组哦县城,称为IO线程,IO线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储(binlog dump)线程(该线程没有对应的SQL命令),这个二进制转储线程会读取主库上二进制日中的事件。它不会对事件进行轮询。如果该线程追赶上了主库,它将进入睡眠状态,直到主库发送信号量通知其有新的事件产生时才会被幻行,贝克IO线程会将接收到的事件记录到中继日志中。MySQL4.0之前的复制与之后的版本相比改变很大,例如MySLQ最初的复制功能没有使用中继日志,所以复制只用到了两个线程,而不是现在的三个线程。目前大部分人都是使用的最新版本。备库的SQL线程执行最后异步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当SQL线程追赶上IO线程时,终极日志通常已经在系统缓存中,所以中继日志的开销很低。SQL线程执行的事件也可以通过配置选项来决定是否写入其自己的二进制日志中。上图显示了在备库有两个运行的线程,在主库上也有一个运行的线程:和其他普通连接一样,由备库发起的连接,在主库上同样拥有一个线程。这种复制架构实现了获取事件和重放事件的解耦,允许这两个过程异步进行。也就是说IO线程能够独立于SQL线程之外工作。但这种架构也限制了复制的过程,其中最重要的一点时在主库上并发运行的查询在备库只能串行化执行,因为只有一个SQL线程来重放中继日志中的事件。后面我们将会看到,这是很多工作负载的性能瓶颈所在,虽然有一些针对该问题的解决方案,但大多数用户仍然会受制于单线程
概述。MySQL内建的复制功能是构建基于MySQL的大规模、高性能应用的基础,这类应用使用所谓的\"水平扩展\
运行下面的命令开始复制:```sqlmysql> START SLAVE;```执行该命令没有显示错误,现在我们再用SHOW SLAVE STATUS命令检查:```sqlmysql>SHOW SLAVE STATUS\\G*************************** 1. row ***************************Slave_IO_State:Master_Host:server1Master_User:replMaster_Port:3306Connect_Retry:60Master_Log_File:mysql-bin.000001Read_Master_Log_Pos:164Relay_Log_File:mysql-relay-bin.000001Relay_Log_Pos:164Relay_Master_Log_File:mysql-bin.000001Slave_IO_Running:YesSlave_SQL_Running:Yes.......Seconds_Behind_Master:0```从输出可以看到IO线程和SQL线程都已经开始运行,Seconds_Behind_Master的值也不再为NULL。IO线程正在等待从主库传递过来的事件,这意味着IO线程已经读取了主库所有的事件。日志位置发生了变化,表明已经从主库获取和执行了一些事件.如果在主库上做一些数据更新,就会看到备库的文件或者日志位置都可能会增加。备库的数据同样会随之更新。我们还可以从线程列表中看到复制线程。在主库上可以看到由备库IO线程向主库发起的连接:```sqlmysql>SHOW PROCESSLIST\\G*************************** 1. row ***************************Id:55User:replHost:replica1.webcluster_1:54813db:NULLCommand:Binlog DumpTime:610237State:Has sent all binlog to slave;waiting for binlog to be updatedInfo:NULL```
同样,在备库也可以看到两个线程,一个是IO线程,一个是SQL线程:```sqlmysql>SHOW PROCESSLIST\\G*************************** 1. row ***************************Id:1User:system userHost:db:NULLCommand:ConnectTime:611116State:Waiting for master to send eventInfo:NULL*************************** 2. row ***************************Id:2User:system userHost:db:NULLCommand:ConnectTime:33State:Has read all relay log; waiting for the slave I/O thread to update itInfo:NULL```这些简单的输出来自一台已经运行了一段时间的服务器,所以IO线程在主库和备库上的Time列的值较大,SQL线程已经空闲了33秒。这意味着33秒内没有重放任何事件。这些线程总是运行在\"system user\"账号下,其他列的值则不相同。例如,在SQL线程回放事件时,Info列可能显示正在执行的查询。
推荐的复制配置。有许多参数来控制复制,其中一些会对数据安全和性能产生影响。推荐一种\"安全\
配置复制。为MySQL服务器配置复制非常简单。但由于场景不同,基本的步骤还是有所差异的。最基本的场景是新安装的主库和备库,总的来说分为以下几步:1.在每台服务器上创建复制账号2.配置主库和备库3.通知备库连接到主库并从主库复制数据这里我们假定大部分配置采用默认值即可,在主库和备库都是全新安装并且拥有同样的数据(默认MySQL数据库)时这样的假设时合理的。假设有服务器server1(IP地址192.168.0.1)和服务器server2(IP地址192.168.0.2).
基于语句的复制。在MySQL5.0及之前的版本中只支持基于语句的复制(也称为逻辑复制),这在数据库领域是很少见的,基于语句的复制模式下,主库会记录那些造成数据更改的查询,当备库读取并重放这些事件时,实际上只是把主库上执行过的SQL再执行一遍。这种方式既有好处、也有缺点。最明显的好处是实现相当简单。理论上讲,简单地记录和执行这些语句,能够让主备保持同步。另一个好处是二进制日志里的事件更加紧凑,所以相对而言,基于语句的模式不会使用太多带宽。一条更新好几兆数据的语句在二进制日志里可能只占几十个字节。另外mysqlbinlog工具是使用基于语句的日志的最佳工具。但事实上基于语句的方式可能并不如看起来那么便利。因为主库上的数据更新除了执行的语句外,可能还依赖于其他因素。例如,同一条SQL在主库和备库上执行的事件可能稍微或很不相同,因此在传输的二进制日志中,除了查询语句,还包括了一些元数据信息,如当前的时间戳。即便如此,还存在着一些无法被正确复制的SQL.例如使用CURRENT_USER()函数的语句。存储过程和触发器在使用基于语句的复制模式时也可能存在问题。另外一个问题是更新必须是串行的。这需要更多的锁——有时候要特别关注这一点,另外不是所有的存储引擎都支持这种复制模式。尽管这些存储引擎是包括在MySQL5.5及之前版本中发行的。
复制文件。接下来看看复制会使用到的一些文件,除了前面的二进制日志文件和中继日志文件,起始还有其他的文件会被用到。不同版本的MySQL默认情况下可能将这些文件放到不同的目录里,大多取决具体的配置选项。可能在data目录或者包含服务器.pid文件的目录下(对于类UNIX系统可能是/var/run/mysqld).1.mysql-bin.index当在服务器上开启二进制日志时,同时会生成一个和二进制日志同名的但以.index作为后缀的文件,该文件用于记录磁盘上的二进制日志文件。这里的\"index\"并不是指表的索引,而是说这个文件的每一行包含了二进制文件的文件名,你可能认为这个文件时多余的。可以被删除(毕竟MySQL可以在磁盘上找到它需要的文件)。事实上并非如此,MySQL依赖于这个文件,除非在这个文件里有记录,否则MySQL识别不了二进制日志文件2.mysql-relay-bin-index这个文件是中继日志的索引文件,和mysql-bin.index的作用类似3.master.info这个文件用于保存备库连接到主库所需要的信息,格式为纯文本(每行一个值),不同的MySQL版本,其记录的信息也可能不同。此文件不能删除,否则备库在重启后无法连接到主库。这个文件以文本的方式记录了复制用户的密码,所以要注意此文件的权限控制4.relay-log.info这个文件包含了当前备库复制的二进制日志和中继日志坐标(例如,备库复制在主库上的位置),同样也不要删除这个文件,否则在备库重启后将无法获知从哪个位置开始复制,可能会导致重放已经执行过的语句使用这些文件来记录MySQL复制和日志状态是一种非常粗糙的方式。更不幸得是,它们不是同步写的。如果服务器断电并且文件数据没有被刷新到磁盘,在重启服务器后,文件中记录的数据可能是错误的。正如之前提到的,这些问题在MySQL5.5里做了改进。以.index作为后缀的文件也与设置expire_logs_days存在交互,该参数定义了MySQL清理过期日志的方式,如果文件mysql-bin.index在磁盘上不存在,在某些MySQL版本自动清理就会不起作用,甚至执行PURGE MASTER LOGS语句也没有用。这个问题的解决方法通常是使用MySQL服务器管理二进制日志,这样就不会产生误解(这意味着不应该使用rm来自己清理日志)。最好能显式地执行一些日志清理策略,比如设置expire_logs_days参数或者其他方式,否则MySQL的二进制日志i可能会将磁盘撑满。当做这些事情时,还需要考虑到备份策略。
复制的原理
一主库多备库。除了前面已经提过的两台服务器的主备结构外,这是最简单的拓扑结构。事实上一主多备的结构和基本配置差不多简单,因为备库之间根本没有交互(从技术上讲这并非正确的。但如果有重复的服务器ID,它们将现如竞争,并反复将对方从主库上踢出),它们仅仅是连接到同一个主库上,如图显示了这种结构。尽管这是非常简单的拓扑结构,但它非常灵活,能满足多种需求。下面是它的一些用途:1.为不同的角色使用不同的备库(例如添加不同的索引或使用不同的存储引擎)2.把一台备库当作待用的主库,除了复制没有其他数据传输3.延迟一个或多个备库,以备灾难恢复4.使用其中一个备库,作为备份、培训、开发或者测试使用服务器这种结构流行的原因是它避免了很多其他拓扑结构的复杂性。例如:可以方便地比较不同备库重放的事件在主库二进制日志中的位置。换句话说,如果在同一个逻辑点停止所有备库的复制,它们正在读取的是主库上同一个日志文件的相同物理位置。这是个很好的特性,可以减轻管理员许多工作,例如把备库提升为主库。这种特性只存在于兄弟备库之间。在没有直接的主备或兄弟关系的服务器上去比较日志文件的位置要复杂很多。
MySQL不支持多主库复制。多主库复制(multisource replication)特指一个备库有多个主库。不管之前你知道什么,但MySQL(和其他数据库产品不一样)现在不支持如图所示的结构
主动-被动模式下的主-主复制。这是前面描述的主-主结构的变体,它能够避免上面讨论的问题。这也是构建容错性和高可用性系统的非常强大的方式,主要区别在于其中的一台服务器是只读的被动服务器,如图所示。这种方式使得反复切换主动和被动服务器非常方便,因为服务器的配置是对称的。这使得故障转移和故障恢复很容易。它也可以让你在不关闭服务器的情况下执行维护、优化表、升级操作系统(或者应用程序、硬件等)或其他任务。例如,执行ALTER TABLE操作可能会锁住整个表,阻塞对表的读和写,这可能会花费很长事件并导致服务中断。然而在主-主配置下,可以先停止主动服务器上的备库复制线程(这样就不会在被动服务器上执行任何更新),然后在被动服务器上执行ALTER操作,交换角色,最后在先前的主动服务器上启动复制线程。这个服务器将会读取中继日志并执行相同的ALTER语句。这可能花费很长时间,但不要紧,因为该服务器没有为任何活跃查询提供服务。主动-被动模式的主-主结构能够帮助会比许多MySQL的问题和限制,此外还有一些工具可以完成这种类型的操作。让我们看看如何配置主-主服务器对,在两台服务器上执行如下设置后,会使其拥有对称的设置:1.确保两台服务器上有相同的数据2.启用二进制日志,选择唯一的服务器ID,并创建复制账号。3.启用备库更新的日志记录,后面将会看到,这是故障转移和故障恢复的关键4.把被动服务器配置成只读,防止可能与主动服务器上的更新产生冲突,这一点是可选的。5.启动每个服务器的MySQL实例6.将每个主库设置为对方的备库,使用新创建的二进制日志开始工作让我们看看主动服务器上更新时会发生什么事情。更新被记录到二进制日志中,通过复制传递给被动服务器的中继日志中。被动服务器执行查询并将其记录到自己的二进制日志中(因为开启了log_slave_updates选项)。由于时间的服务器ID与主动服务器的相同,因此主动服务器将忽略这些时间。设置主动-被动的主-主拓扑结构在某种意义上类似于创建了一个热备份,但是可以使用这个\"备份\"来提高性能,例如,用它来执行读操作、备份、\"离线\"维护以及升级等。真正的热备份做不了这些事情,然而,你不会获得比单台服务器更好的写性能。它是一种非常常见的并且重要的拓扑结构
拥有备库的主-主结构。另外一种相关的配置是为每个主库增加一个备库,如图所示。这种配置的优点是增加了冗余,对于不同地理位置的复制拓扑,能够消除站点单点失效的问题。你也可以像平常一样,将读查询分配到备库上。如果在本地为了故障转移使用主-主结构,这种配置同样有用。当主库失效时,用备库来代替主库还是可行的,虽然这有点复杂。同样也可以把备库指向一个不同的主库,但需要考虑增加的复杂度
环形复制。如图所示,双主结构实际上是环形结构的一种特例(也许应该说,是更明智的特例)。环形结构可以有三个或更多的主库。每个服务器都是在它之前的服务器的备库,是它在之后的服务器的主库。这种结构也称为环形复制(circular replication).环形结构没有双主结构的一些优点,例如对称配置和简单的故障转移,并且完全依赖于环上的每一个可用节点,这大大增加了整个系统失效的几率。如果从环中移除一个节点,这个节点发起的时间就会陷入无限循环:它们将永远绕着服务器链循环。总地来说,环形结构非常脆弱,应该尽量避免。可以通过为每个节点增加备库的方式来减少环形复制的风险,如图所示,但这仅仅防范了服务器失效的危险,断电或者其他一些影响到网络连接的问题都可能破坏整个环
主库、分发主库以及备库。我们之前提到当备库足够多时,会对主库造成很大的负载。每个备库会在主库上创建一个线程,并执行binlog dump命令。该命令会读取二进制日志文件中的数据并将其发送给备库。每个备库都会重复这样的工作,它们不会共享binlog dump的资源。如果有很多备库,并且有大的事件时,例如一次很大的LOAD DATA INFILE操作,主库上的负载会显著上升,甚至可能由于备库同时请求同样的事件而耗尽内存并崩溃。另一方面,如果备库请求的数据不在文件系统的缓存中,可能会导致大量的磁盘检索,这同样会影响主库的性能并增加锁的竞争。因此,如果需要多个备库,一个好办法时从主库移除负载并使用分发主库。分发主库事实上也是一个备库,它的唯一目的就是提取和提供主库的二进制日志。多个备库连接到分发主库,这使原来的主库摆脱了负担。为了避免在分发主库上做实际的查询,可以将它的表修改为blackhole存储疫情,如图所示。很难说当备库数据达到多少时需要一个分发主库。按照通用准则,如果主库接近满负载,不应该为其建立10个以上的备库。如果有少量的写操作,或者只复制其中一部分表,主库就可以提供更多的复制。另外,也不一定只适用一个分发主库。如果需要的话,可以使用多个分发主向大量的备库进行复制,或者使用金字塔状的分发主库。在某些情况下,可以通过设置slave_compressed_protocol来节约一些主库带宽。这对跨数据中心复制很有好处。还可以通过分发主库实现其他目的,例如,对二进制日志事件执行过滤和重写规则。这比在每个备库上重复进行日志记录、重写和过滤要高效得多。如果在分发主库上使用blackhole表,可以支持更多得备库。虽然会在分发主库执行查询,但其代价非常小,因为blackhole表中没有任何数据。blackhole表得缺点是其存在Bug,例如在某些情况下会忘记将自增ID写入到二进制日志中。所以要小心使用blackhole表。一个比较常见得问题是如何确保分发服务器上得每个表都是blackhole存储引擎。如果有人在主库创建了一个表并指定了不同的存储引擎呢?确实,不管什么时候,在备库上使用不同的存储引擎总会导致同样的问题。常见的解决方案是设置服务器的storage_engine选项:```cstorage_engine=blackhole```这只会影响哪些没有指定存储引擎的CREATE TABLE的语句。如果有一个无法控制的应用,这种拓扑结构可能会非常脆弱。可以通过skip_innodb选项禁止InnoDB,将表退化为MyISAM。但你无法禁止MyISAM或者Memory引擎。使用分发主库另外一个主要的缺点是无法使用一个备库来代替主库,因为由于分发主库的存在,导致各个备库与原始主库的二进制日志坐标已经不相同。
树或金字塔形。如果正在将主库复制到大量的备库中,不管是把数据分发到不同的地方,还是提供更高的读性能,使用金字塔结构都能够更好地管理,如图所示。这种设计地好处是减轻了主库地负担,就像前面提到地分发主库一样。它的缺点是中间层出现的任何错误都会影响到多个服务器。如果每个备库和主库直接相连就不会存在这样的问题。同样,中间层次越多,处理故障会更困难、更复杂。
分离功能。许多应用都混合了在线事务处理(OLTP)和在线数据分析(OLAP)的查询。OLTP查询比较短并且是事务型的。OLAP查询则通常很大,也很慢,并且不要求绝对最新的数据。这两种查询给服务器带来的负担完全不同,因此它们需要不同的配置,甚至可能使用不同的存储引擎或者硬件。一个常见的办法是将OLTP服务器的数据复制到专门为OLAP工作负载准备的备库上。这些备库可以有不同的硬件、配置、索引或者不同的存储引擎。如果决定在备库上执行OLAP查询,就可能需要忍受更大的复制延迟或者降低备库的服务质量。这意味着在一个非专用的备库上执行一些任务时,可能会导致不可接受的性能,例如执行一条长时间运行的查询。无须做一些特殊的配置,除了需要选择忽略主库上的一些数据,前提是能获得明显的提升。即使通过复制过滤器过滤掉一小部分的数据也会减少IO和缓存活动。
数据归档。可以在备库上实现数据归档,也就是说可以在备库上保留主库上删除过的数据,在主库上通过delete语句删除数据是确保delete语句不传递到备库就可以实现。有两种通常的办法:一种是在主库上选择性地禁止二进制日志,另一种是在备库上使用replicate_ignore_db规则(是的,两种方法都很危险)第一种方法需要先将SQL_LOG_BIN设置为0,然后再进行数据清理。这种方法的好处是不需要在备库进行任何配置,由于SQL语句根本没有记录到二进制日志中,效率会稍微有所提升。最大缺点也正因为是没有将在主库的修改记录下来,因此无法使用二进制日志来进行审计或者做按时间点的数据恢复。另外还需要SUPER权限。第二种办法是在清理数据之前对主库上特定的数据库使用USE语句。例如,可以创建一个名为purge的数据库,然后在备库的my.cnf文件里设置replicate_ignore_db=purge并重启服务器。备库将会忽略使用了use语句指定的数据库。这种方法没有第一种方法的缺点,但有另一个小小的缺点:备库需要去读取它不需要的事件。另外,也可能有人在purge数据库上执行非清理查询,从而导致备库无法重放该事件.Percona Toolkit中的pt-arciver支持以上两种方式
将备库用作全文检索。许多应用要求合并事务和全文检索。然而仅有MyISAM支持全文检索,但是MyISAM不支持事务(在MySQL5.6有一个实验室预览版本实现了InnoDB的全文检索,但尚未GA).一个普遍的做法是配置一台备库,将某些表设置为MyISAM存储引擎,然后创建全文索引并执行全文检索查询。这避免了在主库上同时使用事务型和非事务型存储引擎所带来的复制问题,减轻了主库维护全文索引的负担。
只读备库。x许多机构选择将备库设置为只读,以防止在备库进行的无意识修改导致复制中断。可以通过设置read_only选项来实现。它会禁止大部分写操作,除了复制线程和拥有超级权限的用户以及临时表操作。只要不给也不应该普通用户超级权限,这应该是很完美的方法。
定制的复制方案。MySQL的复制非常灵活,可以根据需要定制解决方案。典型的定制方案包括组合过滤、分发和向不同的存储引擎复制。也可以使用\"黑客手段\",例如,从一个使用blackhole存储引擎的服务器上复制或复制到这样的服务器上。可以根据需要任意设计。这其中最大的限制是合理地监控和管理,以及所拥有资源额约束(网络带宽、CPU能力等)
复制拓扑.可以在任意个主库和备库之间建立复制,只有一个限制:每一个备库只能有一个主库。有很多复杂的拓扑结构,但即使是最简单的也可能会非常灵活。一种头普可以有多种用途。关于使用复制的不同方式可以很轻易地写一本书。前面已经讨论了如何为主库设置一个备库,接下来讨论其他比较普遍地拓扑结构以及它们的优缺点.记住下面的基本原则:1.一个MySQL备库实例只能有一个主库2.每个备库必须有一个唯一的服务器ID3.一个主库可以有多个备库(或者相应的,一个备库可以有多个兄弟备库)4.如果打开了log_slave_updates选项,一个备库可以把其主库上的数据变化传播到其他备库
为什么复制无法扩展写操作。糟糕的服务容量比例的根本原因是不能像分发读操作那样把写操作等同地分发到更多服务器上。换句话说,复制只能扩展读操作,无法扩展写操作。你可能想知道到底有没有办法使用复制来增加写入能力。答案是否定的,根本不行。对数据进行分区是唯一可以扩展写入的方法。一些人可能会想到使用主-主拓扑结构(主动-主动模式下的主-主复制)并为两个服务器执行写操作。这种配置比主备结构能支持稍微多一点的写入,因为可以再两台服务器之间共享串行化带来的开销。如果每台服务器上执行50%的写入,那复制的执行量也只有50%需要串行化。理论上讲,这比在一台机器上(主库)对100%的写入并发执行,而在另外一台机器上(备库)上对100%的写入做串行化更优。这可能看起来很吸引人,然而这种配置还比不上单台服务器能支持的写入。一个有50%的写入被串行化的服务器性能比一台全部写入都并行化的服务器性能要低。这是这种策略不能扩展写入的原因。它只能在两台服务器间共享串行化写入的缺点。所以\"链中最弱的一环\"并不是那么弱,它只提供了比主动-被动复制稍微好点的性能。但是增加了很大的风险。通常不能带来任何好处
监控复制。复制增加了MySQL监控的复杂性。尽管复制发生在主库和备库上,但大多数工作是在备库上完成的,这也正式最常出问题的地方。是否所有的备库都在工作?最慢的备库延迟是多大?MySQL本身提供了大量可以回答上述问题的信息,弹药实现自动化监控过程以及使复制更健壮还是需要用户做更多的工作。在主库上,可以使用SHOW MASTER STATUS命令来查看当前主库的二进制日志位置和配置。还可以查看主库当前有哪些二进制日志在磁盘上的:```sqlmysql>SHOW MASTER LOGS;```该命令用于给PURGE MASTER LOGS命令决定使用哪个参数。另外还可以通过SHOW BINLOG EVENTS来查看复制事件。例如,在运行前一个命令后,我们在另一个不曾使用过的服务器上创建一个表,因为知道这是唯一改变数据的语句,而且也知道语句在二进制日志中的偏移量是319,所以我们能看到如下内容:
测量备库延迟。一个比较普遍的问题是如何监控备库落后主库的延迟有多大。虽然SHOW SLAVE STATUS输出的Seconds_behind_master列理论上显示了备库的延时,但由于各种各样的原因,并不总是准确的:1.备库Seconds_behind_master值是通过讲服务器当前的时间戳与二进制日志中的事件的时间戳相对比得到的,所以只有在执行事件时才能报告延迟。2.如果备库复制线程没有运行,就会报延迟为NULL3.一些错误(例如主备的max_allowed_packet不匹配,或者网络不稳定)可能中断复制并且/或者停止复制线程,但Seconds_behind_master将显示为0而不是显示错误4.即使备库线程正在运行,备库有时候可能无法计算延时,如果发生这种情况,备库会报0或者NULL5.一个大事务可能会导致延迟波动,例如,有一个事务更新数据长达一个小时,最后提交。这条更新将比它实际发生时间要晚一个小时才记录到二进制日志中。当备库执行这条语句时,会临时地报告备库延迟为一个小时,然后又很快变成06.如果分发主库落后了,并且其本身也有已经追赶上它的备库,备库的延迟将显示为0,而事实上和源主库之间是有延迟的。解决这些问题的办法是忽略Seconds_behind_master的值,并使用一些可以直接观察和衡量的方式来监控备库延迟。最好的解决办法是使用heartbeat record,这是一个在主库上会每秒更新一次的时间戳。为了计算延时,可以直接用备库当前的时间戳减去心跳记录的值。这个方法能够解决刚刚我们提到的所有问题,另外一个额外的好处是我们还可以通过时间戳知道备库当前的复制状况。包含在Percona Toolkit里的pt-heartbeat脚本是\"复制心跳\"最流行的一种实现。心跳还有其他好处,记录在二进制日志中的心跳记录拥有多种用途,例如在一些很难解决的场景下可以用于灾难恢复。刚刚所描述的几种延迟指标都不能表明备库需要多长时间才能赶上主库,这依赖于许多因素,例如备库的写入能力以及主库持续写入的次数。
确定主备是否一致。在理想情况下,备库和主库的数据应该是完全一样的。但事实上备库可能发生错误并导致数据不一致。即使没有明显的错误,备库同样可能因为MySQL自身的特性导致数据不一致,例如MySQL的Bug、网络中断、服务器崩溃,非正常关闭或者其他一些错误。(如果你正在使用非事务型存储引擎,不首先调用STOP SLAVE就关闭服务器是很不妥当的)。按照经验来看,主备一致应该是一种规范,而不是例外,也就是说,检查你的主备一致性应该是一个日常工作,特别是当使用备库来做备份时尤为重要,因为你肯定不希望从一个已经损坏的备库里获得备份数据。MySQL并没有内建的方法来比较一台服务器与别的服务器的数据是否相同。它提供了一些组建来为表和数据生成校验值,例如CHECKSUM TABLE。但当复制正在进行时,这种方法是不可行的。Percona Toolkit里的pt-table-checksum能够解决上述几个问题。其主要特性是用于确认备库与主库的数据是否一致。工作方式是通过在主库执行INSERT ...SELECT查询。这些查询对数据进行校验并将结果插入到一个表中。这些语句通过复制传递到备库,并在备库执行一遍,然后可以比较主备上的结果是否一样。由于该方法是通过复制工作的。它能够给出一致的结果而无须同时把主备上的表都锁上。通常情况下可以在主库上运行该工具,参数如下:```c$ pt-table-checksum --replicate=test.checksum <master_host>```该命令将检查所有的表,并将结果插入到test.checksum表中。当查询在备库执行完后,就可以简单地比较主备之间的不同了。pt-table-checksum能够发现服务器所有的备库,在每台备库上运行查询,并自动地输出结果。
从主库重新同步备库。在职业生涯中,也许会不止一次需要去处理未被同步的备库。可能是使用校验工具发现了数据不一致,或是因为已经知道是备库忽略了某条查询或者有人在备库上修改了数据。传统的修复不一致的办法是关闭备库,然后重新从主库复制一份数据。当备库数据不一致的问题可能导致严重后果时,一旦发现就应该将备库停止并从生产环境移除,然后再从一个备份中克隆或恢复备库。这种方法的缺点是不太方便,特别是数据量很大时。如果能够找出并修复不一致的数据,要比从其他服务器上重新克隆数据要有效得多。如果发现的不一致并不严重,就可以保持备库在线,并重新同步受影响的数据。最简单的办法是使用mysqldump转储受影响的数据并重新导入。在整个过程中,如果数据没有发生变化,这种方法会很好。你可以在主库上简单地锁住表然后进行转储,再等备库赶上主库,然后将数据导入到备库中。(需要等待备库赶上主库,这样就不至于为其他表引入新的不一致,例如那些可能通过和失去同步的表做join后进行数据更新的表)。虽然这种方法再许多场景下是可行的。但在一个繁忙的服务器上有可能行不通。另外一个缺点是在备库上通过非复制的方式改变数据。通过复制改变备库数据(通过在主库上执行更新)通常是一种安全的技术,因为它避免了竞争条件和其他意料外的事情。如果表很大或者网络带宽首先,转储和重载数据的代价依然很高。当在一个有一百万行的表上只有一千行不同的数据呢?转储和重载表的数据是非常浪费资源的。pt-table-sync是Percona Toolkit中的另外一个工具,可以解决该问题。该工具能够高效地查找并解决表之间的不同。它同样通过复制工作,在主库上执行查询,在备库上重新同步,这样就没有竞争条件。它是结合pt-table-checksum生成的checksum表来工作的。所以只能操作那些已知不同步的表的数据块。但该工具不是在所有场景下都有效。为了正确地同步主库和备库,该工具要求复制是正常地,否则就无法工作。pt-table-sync设计得很搞笑,但当数据量非常大时效率还是会很低。比较主库和备库上1TB的数据不可避免地会带来额外的工作。尽管如此,在那些合适的场景中,该工具依然能节约大量的时间和工作
计划内的提升。把备库提升为主库理论上是很简单的。简单来说,有以下步骤:1.停止向老的主库ieru2.让备库追赶上主库(可选的,会简化下面的步骤)3.将一台备库配置为新的主库4.将备库和写操作指向新的主库,然后开启主库的写入但这其中还隐藏着很多细节。一些场景可能依赖于复制的拓扑结构。例如,主-主结构和主-备结构的配置就有所不同。更深入一点,下面是大多数配置需要的步骤:1.停止当前主库上的所有写操作。如果可以,最好能将所有的客户端程序关闭(除了复制连接)。为客户端程序建立一个\"do not run\"这样的类似标记可能会有所帮助。如果正在使用虚拟IP地址,也可以简单地关闭虚拟IP,然后断开所有地客户端连接以关闭其打开地事务2.通过FLUSH TABLES WITH READ LOCK在主库上停止所有活跃的写入,这一步是可选的。也可以在主库上设置read_only选项。从这一刻开始,应该禁止向即将备替换的主库做任何写入。因为一旦它不是主库,写入就意味着数据丢失。注意,即使设置read_only也不会阻止当前已存在的事务继续提交。为了更好地保证这一点,可以\"kill\"所有打开的事务,这将会真正地结束所有写入3.选择一个备库作为新的主库,并确保它已经完全跟上主库(例如,让他执行完所有从主库获得的中继日志)4.确保新主库和旧主库的数据是已知的。可选5.在新主库上执行STOP SLAVE6.在新主库上执行CHANGE MASTER TO MASTER_HOST='',然后再执行RESET SLAVE,使其断开与老主库的连接,并丢弃master.info里记录的信息(如果连接信息记录在my.cnf里,会无法正确工作,这也是建议不要把复制连接信息写到配置文件里的原因之一)7.执行SHOW MASTER STATUS记录新主库的二进制日志坐标8.确保其他备库已经追赶上9.关闭旧主库10.在MySQL5.1及以上版本中,如果需要,激活新主库上事件11.将客户端连接到新主库12.在每台备库上执行CHANGE MASTER TO语句,使用之前通过SHOW MASTER STATUS获得的二进制日志坐标,来指向新的主库。当将备库提升为主库时,要确保备库上任何特有的数据库、表和权限已经备移除。可能还需要修改备库特有的配置选项,例如innodb_flush_log_at_trx_commit选项,同样的,如果是把主库降级为备库,也要保证需要的配置。如果主备的配置相同,就不需要做任何改变。
计划外的提升。当主库崩溃时,需要提升一台备库来代替它,这个过程可能就不太容易。如果只有一台备库,可以直接使用这台备库。但如果有超过一台的备库,就需要做一些额外的工作。另外,还有潜在的丢失复制事件的问题。可能有主库上已经发生了修改还没有更新到它的任何一台备库上的情况。甚至还可能一条语句在主库上执行了回滚,但在备库上没有回滚,这样备库可能超过主库的逻辑复制位置(这是有可能的,即使MySQL在事务提交前并不记录任何事件。另外一种场景是主库崩溃后恢复,但没有设置innnodb_flush_log_at_trx_commit的值为1,所以可能会丢失一些更新)。如果能在某一点恢复主库的数据,也许就可以取得丢失的语句并手动执行它们。在以下的步骤中,需要确保在计算中使用Master_Log_File和Read_Master_Log_Pos的值。以下是对主备拓扑结构中的备库进行提升的过程:1.确定哪台备库的数据最新。检查每台备库上的SHOW SLAVE STATUS命令的输出,选择其中Master_Log_File/read_Master_Log_Pos的值最新的那个。2.让所有哦备库执行完所有其从崩溃前的旧主库那获得的中继日志。如果在未完成前修改备库的主库,它会抛弃剩下的日志事件,从而无法获知该备库在什么地方停止3.执行前面的5~7步4.比较每台备库和新主库上的Master_Log_File/Read_Master_Log_Pos的值5.执行前面的10~12步正如开始推荐的,假设已经在所有的备库上开启了log_bin和log_slave_updates,这样可以帮助你将所有的备库恢复到一个一致的时间点,如果没有开启这两个选项,则不能可靠地做到这一点。
改变主库。迟早会有把备库指向一个新的主库的需求。也许是为了更迭升级服务器,或者是主库出现问题时需要把一台备库转换成主库,或者只是希望重新分配容量。不管处于什么原因,都需要告诉其他的备库新主库的信息。如果这是计划内的操作,会比较容易(至少比紧急情况下要容易)。只需在备库简单地使用CHNAGE MASTER TO命令,并指定合适的值。大多数值都是可选的。只需要指定需要改变的项即可。备库将抛弃之前的配置和中继日志并从新的主库开始复制。同样新的参数会被更新到master.info文件中,这样就算重启,备库配置信息也不会丢失。整个过程中最难的是获取新主库上合适的二进制日志文职,这样备库才可以从和老主库相同的逻辑位置开始复制。把备库提升为主库要更困难一点。有两种场景需要将备库替换为主库,一种是计划内的提升,一种是计划外的提升。
当一个二进制日志损坏时,能恢复多少数据取决于损坏的类型,有几种比较常见的类型:1.数据改变,但事件仍是有效的SQL不幸的是,MySQL甚至无法察觉这种损坏。因此最好还是经常检查备库的数据是否正确。在MySQL未来的版本中可能会被修复2.数据改变并且事件是无效的SQL这种情况可以通过mysqlbinlog提取出事件并看到一些错乱的数据,例如:```sqlUPDATE tbl SET col ???????```可以通过增加偏移量的方式来尝试找到下一个事件,这样就可以只忽略这个损坏的事件。3.数据一楼并且/或者事件的长度是错误的这种情况下,mysqlbinlog可能会发生错误退出或者直接崩溃,因为它无法读取事件,并且找不到下一个事件的开始位置4.某些事件已经损坏或被覆盖,或者偏移量已经改变并且下一个事件的起始偏移量也是错误的。同样的,这种情况下mysqlbinlog也起不了多少作用当损坏非常严重,通过mysqlbinlog已经无法获取日志事件时,就不得步进行一些十六进制的编辑或者通过一些繁琐的技术来找到日志事件的边界。这通常并不困难,因为有一些可辨识的标记会分割事件。如下例所示,首先使用mysqlbinlog找到样例日志的日志事件偏移量```sql$ mysqlbinlog mysql-bin.000113 | grep '^# at'```一个找到日志偏移量的比较简单的方法是比较以下string命令输出的偏移量:```sql$ strings -n 2 -t d mysql-bin.000113```mysqlbinlog默认在/var/lib/mysql目录下有一些可辩别的模式可以帮助定位事件的开头,注意以'G结尾的字符串在日志事件开头的一个字节后的位置。它们是固定长度的事件头的一部分。这些值因服务器而异,因此结果也可能取决于解析的日志所在的服务器。简单地分析后应该能够从二进制日志中找到这些模式并找到下一个完整的日志事件偏移量。然后通过mysqlbinlog的--start-position选项来跳过损坏的事件,或者使用CHANGE MASTER TO命令的MASTER_LOG_POS参数。
使用非事务型表。如果一切正常,基于语句的复制通常能够很好地处理非事务型表。但是当对非事务型表的更新发生错误时,例如查询在完成前被kill,就可能导致主库和备库的数据不一致。例如,假设更新一个MyISAM表的100行数据,若查询更新到了其中50条时有人kill该查询,会发生什么呢?一半的数据改变了,而另一半则没有,结果是复制必然不同步,因为该查询会在备库重放并更新完100行数据(MySQL随后会在主库上发现查询引起的错误,而备库上则没有报错,此后复制将会发生错误并中断)。如果使用的是MyISAM表,在关闭MySQL之前需要确保已经运行了STOP SLAVE,否则服务器在关闭时会kill所有正在运行的查询(包括没有完成的更新)。事务型存储引擎则没有这个问题。如果使用的是事务型表,失败的更新会在主库上回滚并且不会记录到二进制日志中
混合事务型和非事务型表。如果使用的是事务型存储引擎,只有在事务提交后才会将查询记录到二进制日志中。因此如果事务回滚,MySQL就不会记录这条查询,也就不会在备库上重放。但是如果混合使用事务型和非事务型表,并且发生了一次回滚,MySQL能够回滚事务型表的更新,但非事务型表则被永久地更新了。只要不发生类似查询中途被kill这样的错误,这就不是问题:MySQL此时会记录该查询并记录一条ROLLBACK语句到日志中,结果是同样的语句也在备库中执行,所有的都很正常。这样效率会第一点,因为备库需要做一些工作并且最后再把它们丢弃掉。但理论上能够保证主备的数据一致。目前看来一切很正常。但是如果备库发生死锁而主库没有也可能会导致问题。事务型表的更新会被回滚,而非事务型表则无法回滚,此时备库和主库的数据是不一致的。防止该问题的唯一办法是避免混合使用事务型和非事务型表.如果遇到这个问题,唯一的解决办法是忽略错误,并重新同步相关的表。基于行的复制不会受到这个问题的影响。因为它记录的是数据的更改。而不是SQL语句。如果一条语句改变了一个MyISAM表和一个InnoDB表的某些行,然后主库发生了一次死锁,InnoDB表的更新会被回滚,而MyISAM表的更新仍会被记录到日志中并在备库重放。
主库和备库使用不同的存储引擎。在悲苦上使用不同的存储引擎,有时候可以带来好处。但是在一些场景下,当使用基于语句的复制方式时,如果备库使用了不同的存储引擎,则可能造成一条查询在主库和备库上的执行结果不同,例如不确定语句在主库使用不同的存储引擎更容易导致问题。如果发现主库和备库的某些表已经不同步,除了检查更新这些表的查询外,还需要检查两台服务器上使用的存储引擎是否相同。
备库发生数据改变。基于语句的复制方式前提时确保备库和主库相同的数据,因此不应该允许对备库数据的任何更改(比较好的办法是设置read only选项)。假设有如下语句:```sqlmysql>INSERT INTO table1 SELECT * FROM table2;```如果备库上table2的数据和主库上不同,该语句会导致table1的数据也会不一致。换句话说,数据不一致可能会在表之间传播。不仅仅是INSERT....SELECT查询,所有类型的查询都可能发生。有两种可能的结果:备库上发生重复索引键冲突错误或者根本不提示任何错误。如果能报告错误还好,起码能够提示你主备数据已经不一致。无法察觉的不一致可能会悄无声息地导致各种严重的问题。唯一解决的办法就是重新从主库同步数据
不唯一的服务器ID。这种问题更加难以捉摸。如果不小心为两台备库设置了相同的服务器ID,看起来似乎没有什么问题,但如果查看错误日志,或者使用innotop查看主库,可能会看到一些古怪的信息。在主库上,会发现两台备库中只有一台连接到主库(通常情况下所有的备库都会建立连接以等待随时进行复制)。在备库的错误日志中,则会发现反复的重连和连接断开信息,但不会提及被错误配置的服务器ID。MySQL可能会缓慢地进行正确的复制,也可能无法进行正确复制,这取决于MySQL的版本,给定的备库可能会丢失二进制日志事件,或者重复执行事件,导致重复键错误(或者不可见的数据损坏)。也可能因为备库的互相竞争造成主库的负载升高。如果备库竞争非常激烈,回导致错误日志在很短的时间内急剧增大。唯一的解决办法是小心设置备库的服务器ID。一个比较号的办法是创建一个主库到备库的服务器ID映射表,这样就可以跟踪到备库的ID信息(也许你想把它保存在服务器中,这 不完全是玩笑,可以给ID添加一个唯一索引)。如果备库全在一个自网络内,可以将每台机器IP的后八位作为唯一ID
对未复制数据的依赖性。如果在主库上有备库不存在的数据库或表,复制会很容易意外中断,反之亦然。假设主库上有一个备库不存在的数据库,命令为scratch。如果在主库上发生对该数据库中表的更新,备库会在尝试重放这些更新时中断。同样的,如果在主库上创建一个备库上已存在的表,复制也可能中断。没有什么好的解决办法,唯一的办法就是避免在主库上创建备库上没有的表。这样的表是如何创建的呢?有很多可能的方式,其中一些可能更难防范。例如,假设先在备库上创建一个数据库scratch,该数据库在主库上不存在,然后因为某些原因切换了主备。当完成这些后,可能忘记了移除scratch数据库以及它的权限。这时候一些人就可以连接到该数据库并执行一些查询,或者一些定期的任务会发现这些表,并在每个表上执行OPTIMIZE TABLE命令。当提升备库未主库时,或者决定如何配置备库时,需要注意这一点。任何导致主备不同的行为都会产生潜在的问题。
不复制所有的更新。如果错误地使用SET SQL_LOG_BIN=0或者没有理解过滤规则,备库可能会丢失主库上已经发生的更新。有时候希望利用此特性来做归档,但常常会导致意外并出现不好的结果。例如假设设置了replicate_do_db规则,把sakila数据库的数据复制到某一台备库上,如果在主库上执行如下语句,会导致主备数据不一致:```sqlmysql>USE test;mysql>UPDATE sakila.actor ....```其他类型的语句甚至会因为没有复制依赖导致备库复制抛出错误而失败。
子主题
在复制之外并行写入。另一种避免备库严重延迟的办法是绕过复制。任何在主库上的写入操作必须在备库串行化。因此有理由认为\"串行化写入\"不能充分利用资源。所有写操作都应该从主库传递到备库码?如果把备库优先的串行写入容量留给哪些真正需要通过复制进行的写入?这种考虑有助于对写入进行区分。特别是,如果能确定一些写入可以轻易地在复制之外执行,就可以并行化这些操作以利用备库的写入容量。一个很好的例子是之前讨论过的数据归档。OLTP归档需求通常是简单的单行操作。如果只是把不需要的记录从一个表转移到另一个表,就没有必要将这些写入复制到备库。可以禁止归档查询记录到二进制日志中,然后分别在主库和备库上单独执行这些归档查询。自己复制数据到另外一台服务器,而不是通过复制,这听起来有些疯狂,但却对一些应用有意义,特别是如果应用是某些表的唯一更新源。复制的瓶颈通常集中在小部分表上,如果能在复制之外单独处理这些表,就能够显著地加快复制。
为复制线程预取缓存。如果有正确的工作负载,就能通过预先将数据读入内存中,以受益于在备库上的并行IO所带来的好处。这种方式并不广为人知。大多数人不会使用,因为除非有正确的工作负载特性和硬件配置,否则可能没有任何用处。上面讨论的其他几种变通方式通常是更好的选择,并且有更多的方法来应用它们。但是我们直到也有小部分应用会受益于数据预取。有两种可行的实现方法。一种是通过程序实现,略微比备库SQL线程提前读取中继日志,并将其转换为SELECT语句执行。这会使得服务器将数据从磁盘加载到内存中,这样当SQL线程执行到相应的语句时,就无须从磁盘读取数据。事实上,SELECT语句可以并行地执行,所以可以加速SQL线程的串行IO.当一条语句正在执行时,下一条语句需要的数据也正在从磁盘加载到内存中。如果满足下面这些条件,预取可能会有效:1.复制SQL线程是IO密集型的,但备库服务器并不是IO密集型的.一个完全的IO密集型服务器不会受益于预取,因为它没有多余的磁盘性能来提供预取2.备库有多个硬盘驱动器,也许8个或者更多3.使用的是InnoDB以前宁,并且工作集远不能完全加载到内存中一个受益于预读取的例子是随机单行UPDTE语句,这些语句通常在主库上高并发执行。DELETE语句也可能受益于这种方法,但INSERT语句则不太可能会——尤其是当顺序插入时——因为前一次插入已经使索引\"预热\"了。如果表上有很多索引,同样无法预取所有将要被修改的数据。UPDATE语句可能需要更新所有索引,但SELECT语句通常只会读取主键和一个二级索引。UPDATE语句依然需要去读取其他索引的数据以进行更新。在多索引表上这种方法的效率会降低。这种技术并不是\"银弹\",有很多原因会导致其不能工作,甚至适得其反。只有在清楚硬件和操作系统的状况时才能尝试这种方法。我们直到有些人利用这种办法将复制速度提升了300%到400%,但也尝试过很多次,并发现这种办法常常无法工作。正确地设置参数非常重要,但并没有绝对正确的参数组合。mk-slave-perfetch是Maatkit种的一款工具,该工具实现了提到的预取策略。mk-slave-prefetch本身有很多复杂的策略以保证其尽可能多的场景下工作,但缺点是它实在太复杂并且需要许多专业知识来使用,另一款工具是Anders Karlsson的slavereadahead工具。另一种方法是在InnoDB内部实现的。它可以允许设置事务为特殊的模式,以允许InnoDB执行\"假\"更新。因此可以使用一个程序来执行这些加更新,这样复制线程就可以更快地执行真正的更新。Percona Server为一个非常流行的互联网网络应用单独开发了该功能。可以去检查一下此特性现在的状况。如果正在考虑这项技术,可以从一个熟悉其工作原理及可用选项的专家那里获得很好的建议,这应该作为其他方法都不可行时最后的解决办法
过大的复制延迟。复制延迟是一个很普遍的问题。不管怎么样,最好在设计应用程序时能够让其容忍备库出现延迟,如果系统在备库出现延迟时就无法很好地工作,那么应用程序也许就不应该用到复制。但是也有一些办法可以让备库跟上主库。MySQL单线程复制的设计导致备库的效率相当低下。即使备库有很多磁盘、CPU或者内存,也会很容易落后于主库。因为备库的单线程通常只会有效地使用一个CPU和磁盘。而事实上,备库通常哦都会和主库使用相同配置的机器。备库上的锁同样也是问题。其他在备库运行的查询可能会阻塞主复制线程,因为复制时单线程的,复制线程在等待时将无法做别的事情。复制一般有两种产生延迟的方式:突然产生延迟然后再跟上,或者稳定的延迟增大。前一种通常是由于一条运行很长的查询导致的,而后者即使在没有长时间运行的查询时也会出现。不幸的是,目前我们还没那么容易确定备库是否接近其容量上限。正如之前提到的。如果负载总是保持均匀的,备库在负载达到99%时和其负载10%的时候表现的性能相同,但一旦达到100%时就会突然产生延迟。但实际上负载不太可能很稳定,所以当备库接近写容量时,就可能在尖峰负载时看到复制延迟的增加。当备库无法跟上时,可以记录备库上的查询并使用一个日志分析工具找出哪里满了。不要依赖于自己的直觉,也不要基于查询在主库上的查询性能进行判断,因为主库和备库性能特征很不相同。最好的分析办法是暂时在备库上打开慢查询日志记录,然后使用pt-query-digest工具来分析。如果打开了log_slow_slave_statements选项,在标准的MySQL慢查询日志能够记录MySQL5.1及更新的版本中复制线程执行的语句,这样就可以找到在复制时哪些语句执行慢了。Percona Server和MariaDB允许开启或进制该选项而无须重启服务器。除了购买更快的磁盘和CPU(固态硬盘能够提供极大的帮助),备库没有太多的调优空间。大部分选项都是禁止某些额外的工作以减少备库的负载。一个简单的办法是配置InnoDB,使其不要那么频繁地刷新磁盘,这样事务会提交得更快些。可以通过设置innodb_flush_log_at_trx_commit得值为2来上限。还可以在备库上禁止二进制日志记录,把innodb_locks_unsafe_for_binlog设置为1,并把MyISAM得delay_key_write设置为ALL.但是这些设置以牺牲安全换取速度。如果需要将备库提升为主库,记得把这些选项设置回安全的值
来自主库的过大的包。另一个难以追踪的问题是主库的max_allowed_packet值和备库的不匹配。在这种情况下,主库可能会记录一个备库认为过大的包。当备库获取到该二进制日志事件时,可能会碰到各种各样的问题,包括无限报错和充实,或者中继日志损坏
磁盘空间不足。复制有可能因为二进制日志、中继日志或临时文件将磁盘撑满,特别是在主库上执行了LOAD DATA INFILE查询并在备库开启了log_slave_updates选项。延迟越严重,接收到但尚未执行的中继日志会占用越多的磁盘空间。可以通过监控磁盘并设置relay_log_space选项来避免这个问题
复制的问题和解决方案。中断MySQL的复制并不是件难事,因为实现简单,配置相当容易,但也意味着有很多的方式导致复制停止,现如混乱并中断。
MySQL复制的高级特性。Oracle对MySQL5.5的复制有着明显的改进。更多的特性还在开发种,MySQL5.6将包含这些特性。一些改进使得复制更加强健,例如,增加了多线程(并行)复制以减少当前单线程复制的瓶颈。另外,还有一些改进增加了一些高级特性,使得复制更加灵活并可控制。第一个是半同步复制,基于Google多年前所做的工作。这是自MySQL5.1引入行复制后最大的改进。它可以帮助你确保备库拥有主库数据的拷贝,减少了潜在的数据丢失危险。半同步复制在提交过程种增加了一个延迟:当提交事务时,在客户端接收到查询结束反馈前必须保证二进制日志已经传输到至少一台备库上。主库将事务提交到磁盘上之后会增加一些延迟。同样的,这也增加了客户端的延迟,因此其执行大量事务的速度不会比将这些事务传递给备库的速度更快。关于半同步,有一些普遍的误解,下面时它不会去做的:1.在备库提示其已经受到事件前,会阻塞主库上的事务提交。事实上在主库上已经完成事务提交,只有通知客户端被延迟了2.直到备库执行事务后,才不会阻塞客户端。备库在接收到事务后发送反馈而非完成事务后发送3.半同步不总是能够工作。如果备库一直没有回应已收到事件,会超时并转化为正常的异步复制模式尽管如此,这仍然是一个很好用的工具,有助于确保备库提供更好的冗余度和持久性。在性能方面,从客户端的角度来看,增加了事务提交的延时,延时的多少取决于网络传输,数据写入和刷新到备库磁盘的时间(如果开启了配置)以及备库反馈的网络时间。听起来似乎这是累加的,但测试表明这些几乎是不中国要的,也许延迟是由其他原因引起的。Giuseppe Maxia发现每次提交大约延时200微妙.对于小事务开销可能会比较明显,这也是预期中的。事实上半同步复制在某些场景下确实能够提供足够的灵活性以改善性能,在主库关闭sync_binlog的情况下保证更加安全。写入远程的内存(一台备库反馈)比写入本地的磁盘(写入并刷新)要更快。Henrik Ingo运行了一些性能测试表明,使用半同步复制相比在主库上进行强持久化的性能有两倍的改善。在任何系统上都没有绝对的持久化)只有更加高的持久化层次——并且看起来半同步复制应该是一种比其他替代方案开销更小的系统数据持久化方法。除了半同步复制,MySQL5.5还提供了复制心跳,保证备库一直与主库相联系,避免悄无声息地断开连接。如果出现断开的网络连接,备库会注意到丢失的心跳数据。当使用基于行的复制时,还提供了一种改进的能力来处理主库和备库上不同的数据类型。有几个选项可以用于配置复制元数据文件是如何刷新到磁盘以及在一次崩溃后如何处理中继日志,减少了备库崩溃恢复后出现问题的概率
复制
概述。有些应用仅仅适用于一台或少数几台服务器,那么哪些可扩展性建议是和这些应用相关的呢?大多数人从不会维护超大规模的系统,并且通常也无法效仿在主流大公司所使用的策略。选择一个合适的策略能够大大地节约时间和金钱。MySQL经常被批评很难进行扩展,有些情况下这种看法是正确的,但如果选择正确的架构并很好地实现,就能够非常好地扩展MySQL.但是扩展性并不是一个很好理解的主题。
一个线性扩展的系统能由两台服务器获得两倍容量
一个非线性扩展的系统
线性扩展、Amadhl扩展以及USL扩展定律
正式的可扩展性定义。有必要探讨以下可扩展性在数学上的定义了,这有助于在更高层次的概念上清晰地理解可扩展性。如果没有这样的基础,就可能无法理解或精确地表达可扩展性。不过不用担心,这里不会涉及高等数学,即使不是数学天才,也能够很直观地理解它。关键是前面使用的短语:\"划算的等同提升(equal bang for the buck)\".另一种说法是,可扩展性是当增加资源以处理负载和增加容量时系统能够获得的投资产出率(ROI).假设有一个只有一台服务器的系统,并且能够测量它的最大容量,如图所示。下面用s表示服务器,c表示容量,假设我们现在增加一台服务器,系统的能力加倍,如图所示。这就是线性扩展。我们增加了一倍的服务器,结果增加了一倍的容量。大部分系统并不是线性扩展的,而是如图所示的扩展方式。大部分系统都只能以比线性扩展略低的扩展系数进行扩展,越高的扩展系数会导致越大的线性偏差。事实上,多数系统最终会达到一个最大吞吐量临界点,超过这个点后增加头伏反而会带来负回报——继续增加更多工作负载,实际上会降低系统的吞吐量(事实上,\"投资产出率\
扩展模型不是最终定论。虽然有许多理论,但在现实中能做到何种程度呢?正如牛顿定律被证明只有远低于光速时才合理,哪些\"扩展性定律\"也只是在某些场景下才能很好地工作的简化模型。有一种说法认为所有的模型都是错误的,但有一些模型还是有用的,特别是USL能够帮助理解一些导致扩展性差的因素。当工作负载和其所运行的系统存在微妙的关系时,USL理论可能失效。例如,一个USL无法很好建模的常见情况是:当集群的总内存由于数据集大小而发生改变时,也会导致系统的行为发生变化。USL不允许比线性更好的可扩展性,但现实中可能会发生这样的事情:增加系统的资源后,原来一部分IO密集型的工作变成了纯内存工作,因此获得了超过线性的性能扩展。还有一些情况,USL无法很好地描述系统行为。当系统或数据集大小改变时算法的复杂度可能改变,类似这样的情况可能就无法建立模型(USL由O(1)复杂度和O(N^2)复杂度两部分构成,那么对于诸如O(logN)或者O(NlogN)这样复杂度的部分呢?)根据一些思考和实际经验,我们可以将USL扩展以覆盖这些比较普遍的场景中的一部分。但这会将一个简单并且有用的模型变得复杂并难以使用。事实上,它在很多情况下都是很好地,足以为你所能想象到的系统行为建立模型,这也是为什么我们发现它是在正确性和有效性之间的一个很好的妥协。简单地说:有保留地使用模型,并且在使用中验证你的发现
什么是可扩展性。人们常常把诸如\"可扩展性\"、\"高可用性\"以及\"性能\"等术语在一些非正式的场合用作同一侧,但事实上它们是完全不同的。在前面我们将性能定义为响应时间。我们也可以很精确地定义可扩展性。简要地说,可扩展性表明了当需要增加资源以执行更多工作时系统能够获得划算地等同提升(equal bang for the buck)的能力。缺乏扩展能力的系统在达到收益递减的转折点后,将无法进一步增长。容量是一个和可扩展性相关的概念。系统容量表示在一定时间内能够完成的工作量(从物理学来看,单位时间内做的功称为功率(power),而在计算机领域,\"power\"是一个被反复使用的术语,含义模糊,因此应避免使用它,但是关于容量的精确定义是系统的最大功率输出。)但容量必须是可以有效利用的。系统的最大吞吐量并不等同于容量。大多数基准测试能够衡量一个系统的最大吞吐量,但真实的系统一般不会使用到极限。如果达到最大吞吐量,则性能会下降,并且响应时间会变得不可接受地大且非常不稳定。我们将系统地真实容量定义为在保证可接受地性能地情况下能够达到地吞吐量。这就是为什么基准测试地结果通常不应该简化为一个单独地数字。容量和可扩展性并不依赖于性能。以高速公路上的汽车来类比的话:1.性能是汽车的时速2.容量是车道数乘以最大安全时速3.可扩展性就是在不减慢交通的情况下,能增加更多车和车道的程度在这个类比中,可扩展性依赖于多个条件:换道设计得是否合理、路上有多少车抛锚或者发生事故,汽车行驶速度是否不同或者是否频繁变换车道——但一般来说和汽车得引擎是否强大无关。这并不是说性能不重要,性能确实重要,只是需要指出,即使系统性能不是很高也可以具备可扩展性。从较高层次看,可扩展性就是能够通过增加资源来提升容量的能力。即使MySQL架构是可扩展的,但应用本身也可能无法扩展,如果很难增加容量,不管原因是什么,应用都是不可扩展的。之前我们从吞吐量方面来定义容量,但同样也需要从较高的层次来看代容量问题。从有利的角度来看,容量可以简单地认为是处理负载的能力,从不同的角度来考虑负载很有帮助。1.数据量应用所能累积的数据量是可扩展性最普遍的挑战,特别时对于现在的许多互联网应用而言,这些应用从不删除任何数据。例如社交网站,通常从不会删除老的消息或评论2.用户量即使每个用户只有少量的数据,但在累计到一定数量的用户后,数据量也会开始不成比例地增长且速度快过用户增长。更多的用户意味着要处理更多的事务,并且事务数可能和用户数不成比例。最后,大量用户(以及更多的数据)也意味着更多复杂的查询,特别是查询跟用户关系相关时(用户间的关联数可以用N x (N -1)来计算,这里的N表示用户数)3.用户活跃度不是所有的用户活跃度都相同,并且用户活跃度也不总是不变的。如果用户突然变得活跃,例如由于增加了一个吸引人的新特性,那么负载可能会明显提升。用户活跃度不仅仅指页面浏览数,即使同样的页面浏览数,如果网站的某个需要执行大量工作的部分变得流行,也可能导致更多的工作。另外,某些yoghurt也会比其他用户更活跃:它们可能比一般人拥有更多的朋友、消息、照片4.相关数据集的大小如果用户间存在关系,应用可能需要在整个相关联用户群体上执行查询和计算,这比处理一个一个的用户和用户数据要复杂得多。社交网站经常会遇到由那些人气很旺的用户组或朋友很多的用户所带来的挑战
规划可扩展性。人们通常只有在无法满足增加的负载时才会考虑到可扩展性,具体表现为工作负载从CPU密集型变成IO密集型,并发查询的竞争,以及不断增大的延迟。主要原因是查询的复杂度增加或者内存中驻留着一部分不再使用的数据或者索引。你可能看到一部分类型的查询发生改变,例如大的查询或者复杂查询常常比哪些小的查询更影响系统。如果是可扩展的应用,则可以简单地增加更多的服务器来分担负载,这样就没有性能问题了。但如果不是可扩展的,你会发现自己将遭遇到无穷无尽的问题。可以通过规划可扩展性来避免这个问题。规划可扩展性最困难的部分是估算需要承担的负载到底有多少。这个值不一定非常精确,但必须在一定的数量级范围内。如果估计过高,会浪费开发资源。但如果低估了,则难以应付可能的负载。另外还需要大致正确地估计日程表——也就是说,需要知道底线在哪里。对于一些应用,一个简单的原型可以很好地工作几个月,从而有事件去筹资建立一个更加扩展的架构。对于其他的一些应用,你可能需要当前的架构能够为未来两年提供足够的容量。以下的问题可以帮助规划可扩展性:1.应用的功能完成了多少?许多建议的可扩展性解决方案可能会导致实现某些功能变得更加困难。如果应用的某些核心功能还没有开始实现,就很难看出如何在一个可扩展的应用中实现它们。同样地,在知道这些特性如何真正地工作之前也很难决定,使用哪一种可扩展性解决方案2.预期的最大负载是多少?应用应当在最大负载下也可以正常工作。如果你的网络和Yahoo!News或者Slashdot的首页一样,会发生什么呢?即使不是很热门的网站,也同样有最高负载。例如,对于一个在线零售商,假日期间——尤其是在圣诞前的几个星期——通常是负载达到巅峰的时候。在美国,情人节和母亲节前的周末对于在线花店来说也是负载高峰期3.如果依赖系统的每个部分来分担负载,在某个部分失效时会发生什么呢?例如,如果依赖备库来分担读负载,当其中一个失效时,是否还能正常处理请求?是否需要禁用一些功能?可以预先准备一些空闲容量来防范这种问题
为扩展赢得时间。在理想情况下,应该时计划先行,拥有足够的开发者、有花不完的预算,等等,但现实中这些情况会很复杂,在扩展应用时常常需要做一些妥协,特别是需要把对系统的改动推迟一段时间再执行。在深入MySQL扩展的细节前,以下是一些可以做的准备工作:1.优化性能很多时候可以通过一个简单的改动来获得明显的性能提升,例如为表建立正确的索引或从MyISAM切换到InnoDB存储引擎。如果遇到了性能限制,可以打开查询日志进行分析。在修复了大多数主要的问题后,会到达一个收益递减点,这时候提升性能会变得越来越困难。每个新的优化都可能耗费更多的经历但只有很小的提升,并会使应用更加复杂2.购买性能更强的硬件升级或增加服务器在某些场景下行之有效,特别使对于处于软件生命周期早期的应用,购买更多的服务器或者增加内存通常是个好办法。另一个选择是尽量在一台服务器上运行应用程序。比起修改应用的涉及,购买更多的硬件可能是更实际办法,特别是时间紧急并且缺乏开发者的时候。如果应用很小或者被涉及为便于利用更多的硬件,那么购买更多的硬件应该是行之有效的办法。对于新应用这是很普遍的,因为它们通常很小或者涉及合理。但对于大型的旧应用,购买更多硬件可能没什么效果,或者代价太高。服务器从1台增加到3台或许算不了什么,但从100台增加到300台就是另外一回事儿了——代价非常昂贵。如果是这样,花一些时间和经历来尽可能地提升现有系统的性能就很划算
1.按功能拆分。按功能拆分,或者说按职责拆分,意味着不同的节点执行不同的任务。我们之前已经提到了一些类似的实现,。按功能拆分采取的策略比这些更进一步,将独立的服务器或节点分配给不同的应用,这样每个节点只包含它的特定应用所需要的数据。这里我们显式地使用了\"应用\"一词。所指的并不是一个单独的计算机程序,而是相关的一系列程序,这些程序可以很容易地批彼此分离,没有关联。例如如果有一个网站,各个部分无须共享数据,那么可以按照网站的功能进行划分。门户网站常常把不同的栏目放在一起;在门户网站,可以浏览网站新闻、论坛,寻求支持和访问知识库等,等等。这些不同功能区域的数据可以放到专用的MySQL服务器中,如图所示。如果应用很庞大,每个功能区域还可以拥有其专用的Web服务器,但没有专用的数据库服务器这么常见。另一个可能的按功能划分方法是对单个服务器的数据进行划分,并确保划分的表集合之间不会执行关联操作。当必须执行关联操作时,如果对性能要求不高,可以在应用中做关联。虽然有一些变通的方法,但它们有一个共同点,就是每种类型的数据只能在单个节点上找到。这并不是一种通用的分布数据方法,因为很难做到高效,并且相比其他方案没有任何优势。归根结底,还是不能通过功能划分来无限地进行扩展,因为如果一个功能区域被捆绑到单个MySQL节点,就只能进行垂直扩展。其中的一个应用或者功能区域最终增长到非常庞大时,都会迫使你去寻求一个不同的策略。如果进行了太多的功能划分,以后就很难采用更具扩展性的设计了
分片?还是不分片?这是一个问题,对吧?答案很简单:如非必要,尽量不分片。首先看是否能通过性能调优或者更好的应用或数据库设计来推迟分片。如果能足够长时间地推迟分片,也许可以直接购买更大地服务器,升级MySQL到性能更优地版本,然后继续使用单台服务器,也可以增加或减少复制。简单地说,对单台服务器而言,数据大小或写负载变得太大时,分片将是不可避免的。如果不分片,而是尽可能地优化应用,系统能扩展到什么程度呢?答案可能会让你惊讶。有些非常受欢迎的应用,你可能以为从一开始就分片了,但实际上直到已经值数十亿美元并且流量及其巨大也还没有采用分片的设计。分片不是城里唯一的游戏,在没有必要的情况下采用分片的架构来构建应用会步履维艰
2.数据分片。在目前用于扩展大型MySQL应用的方案中,数据分片(分片也被称为\"分裂\"、\"分区\",但是我们使用\"分片\"以避免混淆。谷歌将它称为\"分片\
3.选择分区键(partitioning key)数据分片最大的挑战是查找和获取数据:如何查找数据取决于如何进行分片。有很多方法,其中有一些方法会比另外一些更好。我们的目标是对那些最重要并且频繁查询的数据减少分片(记住,可扩展性法则的其中一条就是要避免不同节点间的交互)。这其中最重要的是如何为数据选择一个或多个分区键。分区键决定了每一行分配到哪一个分片中。如果直到一个对象的分区键,就可以回答如下两个问题:1.应该在哪里存储数据?2.应该从哪里取到希望得到的数据?后面讲展示多个选择和使用分区键的方法。先看一个例子。假设像MySQL NDB Cluster那样来操作,并对每个表的主键使用哈希来将数据分割到各个分片中。这是一种非常简单的实现,但可扩展性不好,因为可能需要频繁检查所有的分片来获得需要的数据。例如,如果想查看user3的博客文章,可以从哪里找到呢?由于使用主键值而非用户名进行分割,博客文章可能均匀分散在所有的数据分片中。使用主键值哈希简化了判断数据存储在何处的操作,但却可能增加获取数据的难度,具体取决于需要什么数据以及是否知道主键。跨多个分片的查询比单个分片上的查询性能要差,但只要不涉及太多的分片,也不会太糟糕。最糟糕的情况是不知道需要的数据存储在哪里,这时候就需要扫描所有分片。一个好的分区键常常是数据库中一个非常重要的实体的主键。这些键值决定了分片单元。例如,如果通过用户ID或客户端ID来分割数据,分片单元就是用户或者客户端。确定分区键一个比较好的办法是用实体——关系图,或一个等效的能显示所有实体及其关系的工具来展示数据模型。尽量把相关联的实体靠的更近。这样可以很直观地找出候选分区键。当然不要仅仅看图,同样地也要考虑应用的查询。即使两个实体在某些方面是相关联的,但如果很少或几乎不对其做关联操作,也可以打断这种联系来实现分片。某些数据模型比其他的更容易进行分片,具体取决于实体——关系图中的关联性程度。如图的左边展示了一个易于分片的数据模型,右边的那个则很难分片。左边的数据模型比较容易分片,因为与之相连的子图中大多数节点只有一个连接,很容易切断子图之间的联系。右边的数据模型则很难分片,因为它没有类似的子图,幸好大多数数据模型更像左边的图。选择分区键的时候,尽可能选择那些能够避免跨分片查询的,但同时也要让分片足够小,以免过大的数据片导致问题。如果可能,应该期望分片尽可能同样小,这样在为不同数量的分片进行分组时能够很容易平衡。例如,如果应用只在美国使用,并且希望将数据分割为20个分片,则可能不应该按照州来划分,因为加利福尼亚的人口非常多。但可以按照县或者电话区号来划分,因为尽管并不是均匀的,但足以选择20个集合以粗略地表示等同的密集程度,并且基本上避免跨分片查询。
4.多个分区键。复杂的数据模型会使苏剧分片更加困难。许多应用拥有多个分区键,特别是存在两个或更多个\"维度\"的时候。换句话说,应用需要从不同的角度看到有效且连贯的数据视图。这意味着某些数据在系统内至少需要存储两份。例如,需要将博客应用的数据按照用户ID和文章ID进行分片,因为这两者都是应用查询数据时使用比较普遍的方式。试想一下这种情形:频繁地读取某个用户的所有文章,以及某个文章的所有评论。如果按用户分片就无法找到某篇文章的所有评论,而按文章分片则无法找到某个用户的所有文章。如果希望这两个查询都落到同一个分片上,就需要从两个维度进行分片。需要多个分区键并不意味着需要去设计两个完全冗余的数据存储。我们来看看另一个例子:一个社交网站下的读书俱乐部站点,该站点的所有用户都可以对书进行评论。该网站可以显示所有书籍的所有评论,也能显示某个用户已经读过或评论过的所有书籍。假设为用户数据和书籍数据都设计了分片数据存储。而评论同时拥有用户ID和评论ID。这样就跨越了两个分片的边界。实际上却无须冗余存储两份评论数据,替代方案时,将评论和用户数据一起存储,然后把每个评论的标题和ID与书籍数据存储在一起。这样在渲染大多数关于某本书的评论的视图时无须同时访问用户和书籍数据存储,如果需要显示完整的评论内容,可以从用户数据存储中获得。
6.分配数据、分片和节点。分片和节点不一定是一对一的关系,应该尽可能地让分片地大小比节点容量小很多,这样就可以在单个节点上存储多个分片。保持分片足够小更容易管理。这将使数据地备份和恢复更加容易,如果表很小,那么像更改表结构这样的操作会更加容易。例如,假设有一个100GB的表,你可以直接存储,也可以将其划分为100个1GB的分片,并存储在单个节点上。现在假如要向表上增加一个索引,在单个100GB的表上的执行时间会比100个1GB分片上执行的总时间更长,因为1GB的分片更容易全部加载到内存中,并且在执行ALTER TABLE时还会导致数据不可用,阻塞1GB的数据比阻塞100GB的数据要好得多。小一点的分片也便于转移。这有助于重新分配容量,平衡各个节点的分片。转移分片的效率一般都不高。通常需要先将受影响的分片设置为只读模式(也是需要在应用中构建的特性),提取数据,然后转移到另外一个节点。这包括使用mysqldump获取数据然后使用mysql命令将其重新导入。如果使用的是Percona Server,可以通过XtraBackup在服务器间转移文件,这比转储和重新载入要高效得多。除了在节点间移动分片,你可能还需要考虑在分片间移动数据,并尽量不中断整个应用提供服务。如果分片太大,就很难通过移动整个分片来平衡容量,这时候可能需要将一部分数据(例如一个用户)转移到其他分片。分片间转移数据比转移分片要更复杂,应该尽量避免这么做。这也是我们建议设置分片大小尽量易于管理的原因之一。分片的相对大小取决于应用的需求。简单地说,我们说的\"易于管理的大小\"是指保持表足够小,以便能在5或10分钟内提供日常的维护工作,例如ALTER TABLE、CHECK TABLE或者OPTIMIZE TABLE.如果将分片设置得太小,会产生太多得表,这可能引发文件系统或MySQL内部结构得问题。另外太小的分片还会导致跨分片查询增多。
7.在节点上部署分片。需要确定如何在节点上部署数据分片。以下是一些常用的办法:1.每个分片使用单一数据库,并且数据库名要相同。典型的应用场景是需要为每个分片都能镜像到原应用的结构。这在部署多个应用实例,并且每个实例对应一个分片时很有用2.将多个分片的表放到一个数据库中,在每个表名上包含分片号(例如bookclub.comments_23),这种配置下,单个数据库可以支持多个数据分片。3.为每个分片使用一个数据库,并在数据库中包含所有应用需要的表。在数据库中包含分片号(例如表名可能是bookclub_23.comments或者bookclub_23.users等),但表名不包括分片号。当应用连接到单个数据库并且不在查询中指定数据库名时,这种做法很常见。其优点是无须为每个分片专门编写查询,也便于对只使用单个数据库的应用进行分片4.每个分片使用一个数据库,并在数据库名和表名中包含分片号(例如表名可以是bookclub_23.comments_23)5.在每个节点上运行多个MySQL实例,每个实例上有一个或多个分片,可以使用上面提到的方式的任意组合来安排分片。如果在标命中包含了分片号,就需要在查询模板里插入分片号。常用的方法是在查询中使用特殊的\"神奇的\
8.固定分配。将数据分配到分片中有两种主要的方法:固定分片和动态分配。两种方法都需要一个分区函数,使用行的分区键值作为输入,返回存储该行的分片(这里的\"函数\"使用了其数学含义,表示从输入(域)到输出(区间)的映射。如你所见,可以用很多方式来创建类似的函数,包括在数据中使用查找表)。固定分配使用的分区函数仅仅依赖于分区键的值。哈希函数和取模运算就是很好的例子。这些函数按照每个分区键的值将数据分散到一定数量的\"桶\"中。假设有100个桶,你希望弄清楚用户111该放到哪个桶里。如果使用的是对数字取模的方式,答案很简单:111对100取模的值为11,所以应该将其放到第11个分片中。而入股哦使用CRC32()函数来做哈希,答案是81.```sqlmysql> SELECT CRC32(111)%100;```固定分配的主要优点是简单,开销低,甚至可以在应用中直接硬编码。但固定分配也有如下缺点:1.如果分片很大并且数量不多,就很难平衡不同分片间的负载2.固定分片的方式无法自定义数据放到哪个分片上,这一点对于那些在分片间负载不均衡的应用来说尤其重要。一些数据可能比其他的更加活跃,入股哦这些热点数据都分配到同一个分片中,固定分配的方式就无法通过热点数据转移的方式来平衡负载。(如果每个分片的数据量切分得比较小,这个问题就没那么严重,根据大数定律,这样做会更容易将热点数据平均分配到不同分片)3.修改分片策略通常比较困难,因为需要重新分配已有的数据。例如,如果通过模10的哈希函数来进行分片,就会有10个分片。如果应用增长使得分片变大,如果要拆成20个分片,就需要对所有数据重新哈希,这回导致更新大量数据,并在分片间转移数据正是由于这些限制,我们倾向于为新应用选择动态分配的方式。但如果是为已有的应用做分片,使用固定分配策略可能会更容易些,因为它更简单。也就是说,大多数使用固定分配的应用最后迟早要使用动态分配策略
10.混合动态分配和固定分配。可以混合使用固定分配和动态分配。这种方法通常很有用。有时候甚至必需要混合使用。目录映射不太大时,动态分配可以很好胜任。但如果分片单元太多,效果就会变差。以一个存储网站链接的系统为例。这样一个站点需要存储数百亿的行,所使用的分区键时源地址和目的地址URL的组合.(这两个URL的任意一个都可能有好几亿的链接,因此,单独一个URL并不适合做分区键)。但是在映射表中存储所有的源地址和目的地址URL组合并不合理,因为数据量太大了,每个URL都需要很多存储空间。一个解决方案是将URL相连并将其哈希到固定数目的桶中,然后把桶动态地映射到分片上。如果桶地数目足够大——例如100万个——你就能把大多数数据分配到每个分片上,获得动态分配的大部分好处,而无须使用庞大的映射表
向外扩展。可以把向外扩展(有时也称为横向扩展或水平扩展)策略划分为三个部分:复制、拆分以及数据分片(sharding).最简单也最常见的向外扩展的方法是通过复制将数据分发到多个服务器上,然后将备库用于读查询。这种技术对于以读为主的应用很有效。它也有一些缺点,例如重复缓存,但如果数据规模有限这就不是问题。另外一个比较常见的向外扩展是工作负载分布到多个\"节点\"。具体如何分布工作负载是一个复杂的话题。许多大型的MySQL应用不能自动分布负载,就算有也没有做到完全的自动化。后面会讨论一些可能的分布负载的方案,并探讨它们的优点和缺点。在MySQL架构中,一个节点(node)就是一个功能部件。如果没有规划冗余和高可用性。那么一个节点可能就是一台服务器。如果设计的是能够故障转移的冗余系统,那么一个节点通常可能是下面的某一种:1.一个主——主复制双机结构,拥有一个主动服务器和被动服务器2.一个主库和多个备库3.一个主动服务器,并使用分布式复制块设备(DRBD)作为备用服务器4.一个基于存储区域网络(SAN)的\"集群\"大多数情况下,一个节点内的所有服务器应该拥有相同的数据。我们倾向把主——主复制架构作为两台服务器的主动——被动节点
通过多实例扩展。一个分片较多的架构可能会更有效地利用硬件。研究和经验表名MySQL并不能完全发挥现代硬件的性能。当扩展到超过24个CPU核心时,MySQL的性能开始趋于平缓,不再上升。当内存超过128GB时也同样如此,MySQL甚至不能完全发挥诸如Virident或Fusion-io卡这样的高端PCIe flash设备的IO性能。不要在一台性能强悍的机器上只运行一个服务器实例,我们还有别的选择。你可以让数据分片足够小,以使每台机器上都能放置多个分片(这也是一直提倡的),每台服务器上运行多个实例,然后划分服务器的硬件资源,将其分配给每个实例。这样做尽管比较繁琐,但确实有效。这是一种向上扩展和向外扩展的组合方案。也可以用其他方法来实现——不一定需要分派你——但分片对于在大型服务器上的联合扩展具有天然的适应性。一些人倾向于通过虚拟化技术来实现合并扩展,这有它的好处。但虚拟化技术本身有很大的性能损耗。具体损耗多少取决于具体的技术,但通常都比较明显,尤其是IO非常快的时候损坏会非常惊人。另一种选择是运行多个MySQL实例,每个实例监听不同的网络端口,或绑定到不同的IP地址。我们已经在一台性能强悍的硬件上获得了10倍或15倍的合并系数。你需要平衡管理复杂度代价和更优性能的收益,以决定哪种方法是最优的。这时候网络可能会成为瓶颈——这个问题大多数MySQL用户都不会遇到。可以通过使用多块网卡并进行绑定来解决这个问题。但Linux内核可能会不理想,这取决于内核版本,因为老的内核对每个绑定设备的网络中断只能使用一个CPU。因此不要把太多的连接绑定到很少的虚拟设备上,否则会遇到内核层的网络瓶颈。新的内核在这一方面会有所改善,所以需要检查你的系统版本,以确定该怎么做。另一个方法是将每个MySQL实例绑定到待定的CPU核心上。这有两点好处:第一,由于MySQL内部的可扩展性限制,当核心数较少时,能够在每个核心上获得更好的性能;第二,当实例在多个核心上运行线程时,由于需要在多核心上同步共享数据,因为会有一些额外的开销。这可以避免硬件本身的可扩展性限制。限制MySQL到少数几个核心能够帮助减少CPU核心之间的交互。注意到反复出现的问题了没?将进程绑定到具有相同物理套接字的和欣赏可以获得最优的效果.
2.ClustrixClustrix是一个分布式数据库,支持MySQL协议,所以它可以直接替代MySQL。除了协议外,它是一个全新的技术,并非建立在MySQL的基础之上,它是一个完全支持ACID,支持MVCC的事务型SQL数据库,主要用于OLTP负载场景,Clustrix在节点间进行数据分片以满足容错性,并对查询进行分发,在节点上并发执行,而不是将所有节点上取得的数据集中起来执行。集群可以在线扩展节点来处理更多的数据或负载。在某些方面Clustrix和MySQL Cluster很像;关键的不同点是,Clustrix是完全分布式执行并且缺少顶层的\"代理\"或者集群前端的查询协调器(query coordinator)。Clustrix本身能够理解MySQL协议,所以无须MySQL来进行协议转换。相比较而言,MySQL cluster是由三个部分组成的:MySQL、NDB集群存储引擎,以及NDB实验评估和性能测试表明,Clustrix能够提供高性能和可扩展性。Clustrix看起来是一项比较有前景的技术
3.ScaleBase.ScaleBase是一个软件代理,处于应用和多个后端MySQL服务器之间。它会把发起的查询进行分裂,并将其分发到后端服务器并发执行,然后汇集结果返回给应用。
4.GenieDBGenieDB最开始用于地理上分布部署的NoSQL文档存储。现在它也有一个SQL层,可以通过MySQL存储引擎进行控制。它包含了很多技术,包括本地内存缓存、消息层,以及持久化磁盘数据存储。将这些技术汇集在一起,就可以使用松散的最终一致性,让应用在本地快速执行查询,或是通过分布式集群(会增加网络延迟)来保证最新的数据视图。通过存储引擎实现的MySQL兼容层不能提供100%的MySQL特性,但对于支持类似Joomla!WordPress,以及Drupal这样的应用已经够用了。MySQL存储引擎的用处主要是使GenieDB能够结合存储引擎获得对ACID的支持,例如InnoDB。GenieDB本身并不是ACID数据库
5.Akiban对Akiban最好的描述应该使查询加速器。它通过存储物理数据来匹配查询模式,使得低开销的跨表关联操作成为可能。尽管类似反范式话(denormalization),但数据层并不是冗余的,所以这和预先计算关联并存储结果的方式是不同的,关联表中元组是互相交错的,所以能够按照关联顺序进行顺序扫描。这就要求管理员确定查询模式能够从所谓的\"表组(table grouping)\"技术中受益,并需要为查询优化涉及表组。目前建议的系统架构是将Akiban配置为MySQL主库的备库,并用它来为可能较满的查询提供服务。加速系数是一到两个数量级。但是还没有看到生产环境部署或者相关的实验评估
通过集群扩展。理想的扩展方案时单一逻辑数据库能够存储尽可能多的数据,处理尽可能多的查询,并如期望的那样增长。许多人的第一想法就是建立一个\"集群\"或者\"网格\"来无缝处理这些事情,这样应用就无须去做太多工作,也不需要知道数据到底存在哪台服务器上。随者云计算的流行,自动扩展——根据负载或数据大小变化动态地在集群中增加/移除服务器——变得越来越有趣。网上出现了许多被称为NoSQL的技术。许多NoSQL的支持者发表了一些奇怪且未经证实的观点,例如\"关系型模型无法进行扩展\",或者\"SQL无法扩展\
保持活跃数据独立。即使并不真的把老数据转移到别的服务器,许多应用也能受益于活跃书数据和非活跃数据的隔离。这有助于高效利用缓存,并为活跃和不活跃的数据使用不同的硬件或应用架构.下面列举了几种做法:1.将表划分为几个部分分表是一个比较明智的办法,特别是整张表无法完全加载到内存时。例如,可以把users表划分为active_users和inactive_users表。你可能认为这并不需要,因为数据库本身只缓存\"热\"数据,但事实上这取决于存储引擎。如果用的是InnoDB,每次缓存一页,而一页能存储100个用户,但只有10%是活跃的,那么这时候InnoDB可能认为所有的页都是\"热\"的——因此每个\"热\"页的90%将被浪费掉。将其拆分成两个表可以明显改善内存利用率2.MySQL分区MySQL5.1本身提供了对表进行分区的功能,能够帮助把最近的数据留在内存中3.基于时间的数据分区。如果应用不断有心数据进来,一般心数据总是比旧数据更加活跃。例如,我们知道博客服务的流量大多是最近七天发表的文章和评论。更新的大部分是相同的数据集。因此这些数据被完整地保留在内存中,使用复制来保证在主库失效时有一份可用的备份。其他数据则完全可以放到别的地方去。我们也看到过这样一种设计,在两个节点的分片上存储用户数据,心数据总是进入\"活跃节点\
向内扩展.处理不断增长的数据和负载最简单的办法是对不再需要的数据进行归档和清理,这种操作可能会带来显著的成效,具体取决于工作负载和数据特性。这种做法并不用来代替其他策略,但可以作为争取时间的短期策略,也可以作为处理大数据量的长期计划之一。在设计归档和清理策略时需要考虑到如下几点:1.对应用的影响一个设计良好的归档系统能够在不影响事务处理的情况下,从一个高负载的OLTP服务器上移除数据。这里的关键是能高效地找到要删除的行,然后一小块一小块地移除。通常需要平衡一次归档的行数和事务的大小,以找到一个锁竞争和事务负载量的平衡。还需要设计归档任务在必要的时候让步于事务处理2.要归档的行当知道某些数据不再使用后,就可以立刻清理或归档它们。也可以设计应用去归档那些几乎不怎么使用的数据。可以把归档的数据置于核心表父进附近通过视图来访问,或完全转移到别的服务器上3.维护数据一致性当数据间存在联系时,会导致归档和清理工作更加复杂。一个设计良好的归档任务能够保证数据的逻辑一致性.或至少在应用需要时能够保证一致,而无须在大量事务中包含多个表。当表之间存在关系时,哪个表首先归档是个问题。在归档时需要考虑孤立行的影响。可以选择违背外键约束(可以通过执行SET FOREIGN_KEY_CHECKS=0禁止InnoDB的外键约束)或暂时把\"悬空指针\"(dangling pointer)记录放到一边。如果应用层认为这些相关联的表具有层次关系,那么归档的顺序也应该和它一样。例如,如果应用总是先检查订单再检查发货单,就先归档订单。应用应该看不到孤立的发货单,因此接下来就可以将返货但归档。4.避免数据丢失如果是再服务器间归档,归档期间可能就无法做分布式事务,也有可能将数据归档到MyISAM或其他非事务型的存储引擎中。因此,为了避免数据丢失,再从源表中删除时,要保证已经在目标机器上保存。将归档数据单独写到一个文件里也是个好主意。可以将归档任务设计为能够随时关闭或重启,并且不会引起不一致或索引冲突之类的错误。5.解除归档(unarchiving)可以通过一些解除归档策略来减少归档的数据量。它可以帮助你归档那些不确定是否需要的数据,并在以后可以通过选项进行回退。如果可以设置一些检查点让系统来检查是否有需要归档的数据,那么这应该时一个很容易实现的策略。例如,要对不活跃的用户进行归档,检查点就可以设置在登录验证时。如果因为用户不存在导致登录失败,可以去检查归档数据中是否存在该用户,如果有,则从中取出来并完成登录。Percona Toolkit包含的工具pt-archiver能够帮助你有效地归档和清理MySQL表,但不提供解除归档功能
扩展性MySQL。如果将应用所有的数据简单地放到单个MySQL服务器实例上,则无法很好地扩展,迟早会碰到性能瓶颈。对于许多类型的应用,传统的解决办法是购买更多强悍的机器,也就是常说的\"垂直扩展\"或者\"向上扩展\"。另外一个与之相反的方法是将任务分配到多台计算机上,这通常被称为\"水平扩展\"或者\"向外扩展\"。接下来将讨论如何联合使用向上扩展和向外扩展的解决方案,以及如何使用集群方案来进行扩展。最后,大部分应用还会有一些很少或者从不需要的数据,这些数据可以被清理或归档。
1.复制上的读/写分离。MySQL复制产生了多个数据副本,你可以选择在悲苦还是主库上执行查询。由于备库复制是异步的,因此主要的难点是如何处理备库上的脏数据。应该将备库用作只读的,而主库可以同时处理读和写查询。通常需要修改应用以适应这种分离需求。然后应用就可以使用主库来进行写操作,并将读操作分配到主库和备库上;如果不太关心数据是否是脏的,可以使用备库,而对需要即时数据的请求使用主库。我们将这种称为读/写分离。如果使用的是主动——被动模式的主——主复制对,同样也要考虑这个问题。使用这种配置时,只有主动服务器接受写操作。如果能够接受读到脏数据,可以将读分配给被动服务器。最大的问题时如何避免由于读了脏数据引起的奇怪问题。一个典型的例子时当一个用户做了某些修改,例如增加了一条博客文章的评论,然后重新加载页面,但并没有看到更新,因为应用从备库读取到了脏的数据。比较常见的读/写分离方法如下:1.基于查询分离最简单的分离方法时将所有不能容忍脏数据的读和写查询分配到主动或者主库服务器上。其他的读查询分配到备库或者被动服务器上。该策略很容易实现,但事实上无法有效地使用备库,因为只有很少的查询能容忍脏数据2.基于脏数据分离这是对基于查询分离方法的小改进。徐奥做一些额外的工作,让应用检查复制延迟,以确定备库数据是否太旧。许多报表类应用都使用这个策略:只需要晚上加载到备库即可,它们并不关心是不是100%跟上了主库3.基于会话分离另一个决定能否从备库读数据的稍微复杂一点的方法时判断用户自己是否修改了数据,用户不需要看到其他用户的最新数据,但需要看到自己的更新。可以在会话层设置一个标记位,表明做了更新,就将该用户的查询在一段时间内总是指向主库。这是我们通常推荐的策略,因为它是在简单和有效性之间的一种很好的妥协。如果有足够的想象力,可以把基于会话的分离方法和复制延迟监控结合起来。如果用户在10秒前更新了数据,而所有备库延迟在5秒内,就可以安全地从备库中读取数据,但为整个会话选择同一个备库是一个很好的主意,否则用户可能会奇怪有些备库的更新速度比其他服务器要慢。4.基本版本分离这和基于会话的分离方法相似:你可以跟踪对象的版本好以及/或者时间戳,通过从备库读取对象的版本或时间戳来判断数据是否足够新。如果备库的数据太久,可以从主库获取最新的数据。即使对象本身没有变化,但如果是顶层对象,只要下面的任何对象有比那花,也可以增加版本好,这简化了脏数据检查(只需要检查顶层对象一处就能判断是否有更新)。例如,在用户发表了一篇新文章后,可以更新用户的版本。这样就会从主库去读取数据了5.基于全局版本/会话分离这个办法是基于版本分离和基于会话分离的变种。当应用执行写操作时,在提交事务后,执行一次SHOW MASTER STATUS操作。然后在缓存中存储主库日志坐标,作为被修改对象以及/或者会话的版本号。当应用连接到备库时,执行SHOW SLAVE STATUS并将备库上的坐标和缓存中的版本号相对比。如果备库相比记录点更新,就可以安全地读取备库数据。大多数读/写分离解决方案都需要监控复制延迟来决策读查询的分配,不管时通过复制或负载均衡器,或是一个中间系统。如果这么做,需要注意通过SHOW SLAVE STATUS得到的Seconds_behind_master列的值并不能准确地用于监控延迟。Percona Toolkit中的pt-heartbeat工具能够帮助监控延迟,并维护元数据,例如二进制日志未知,这可以减轻之前我们讨论的一些策略存在的问题。如果不在乎用昂贵的硬件来承载压力,也就可以不适用复制来扩展读操作,这样当然更简单。这可以避免在主备上分离读的复杂性。有些人认为这很有意义;也有人认为浪费硬件。这种分歧时由于不同的目的引起的:你是只需要可扩展性,还是要同时具有可扩展性和高利用率?如果需要高利用率,那么备库除了保存数据副本还需要承担其他任务,就不得不处理这些额外的复杂度
2.修改应用的配置。还有一个分发负载的办法是重新配置应用。例如,你可以配置多个机器来分担生成大报表操作的负载。每台机器可以配置成连接到不同的MySQL备库,并为第N个用户或网站生成报表。这样的系统很容易实现,但如果需要修改一些代码——包括配置文件修改——会变得脆弱且难以处理。硬编码有着固有的限制,需要在每台机器上修改硬编码,或者在一个中心服务器上修改,然后通过文件副本或代码控制更新命令\"发布\"到其他服务器上,如果将配置存储在服务器或缓存中,就可以避免这些麻烦。
3.修改DNS名。这是一个比较粗糙的负载均衡技术,但对于一些简单的应用,为不同的目的创建DNS还是很实用的。你可以为不同的服务器指定一个合适的名字。最简单的方法是只读服务器有一个NDS名,而给负责写操作的服务器起另外一个DNS名。如果备库能够跟上主库,那就把只读DNS名指定给备库,当出现延迟时,再将该DNS名指定给主库。这种DNS技术非常容易实现,但也有很多缺点。最大的问题是无法完全控制DNS.1.修改DNS并不是立刻生效的,也不是原子的。将DNS的变化传递到整个网络或在网络间传播都需要比较长的时间2.DNS数据会在各个地方缓存下来,它的过期时间是建议性质的,而非强制的3.可能需要应用或服务器重启才能使修改后的DNS完全生效。4.多个IP共用一个DNS名依赖于轮询行为来均衡请求,这并不是一个好主意,因为轮询行为并不宗总是可预知的5.DBA可能没有权限直接访问DNS除非应用非常简单,否则依赖不受控制的系统会非常危险。你可以通过修改/etc/hosts/文件而非DNS来改善对系统的控制。当发布一个对该文件的更新时,会知道该变更已经生效。这比等待缓存的DNS失效要好得多。但这仍然不是理想的办法。通常建议人们构建一个完全不依赖DNS的应用。即使应用很简单也适用,因为你无法预知应用会增长到多大规模。
4.转移IP地址。一些负载均衡解决方案依赖于在服务器间转移虚拟地址(虚拟IP地址不是直接连接到任何特定的计算机或网络端口,而是\"漂浮\
直接连接。有些人认为负载均衡就是配置在应用和MySQL服务器之间的东西。但这并不是唯一的负载均衡方法。你可以在保持应用和MySQL连接的情况下使用负载均衡。事实上,集中化的负载均衡系统只有在存在一个对等置换的服务器池时才能很好工作。如果应用需要做一些决策,例如在悲苦上执行读操作是否安全,就需要直接连接到服务器。除了可能出现的一些特定逻辑,应用为负载均衡做决策是非常高效的。例如,如果有两个完全相同的备库,你可以使用其中的一个来处理特定分片的数据查询,另一个处理其他的查询。这样能够有效利用备库的内存,因为每个备库只会缓存一部分数据。如果其中一个备库失效,另外一个备库拥有所有的数据,仍然能提供服务。
3.在服务器池中增加/移除服务器。增加一个服务器到池中并不是简单地插入禁区,然后通知负载均衡器就可以了,你可能以为只要不是一下子涌进大量连接请求就可以了,但并不一定如此。有时候你会缓慢增加一台服务器的负载,但一些缓存还是\"冷\"的服务器可能会慢到一段时间内都无法处理任何的用户请求。如果用户浏览一个页面需要30秒才能返回数据,即使流量很小,这个服务器也是不可用的。有一个方法可以避免这个问题,在通知负载均衡器有新服务器加入前,可以暂时把SELECT查询映射到一台活跃服务器上,然后再新开启的服务器上读取和重放活跃服务器上的日志文件,或者捕捉生产服务器上的网络通信,并重放它的一部分查询。Percona Toolkit中的pt-query-digest工具能够有所帮助。另一个有效的办法是适用Percona Server或MySQL5.6的快速预热特性。在配置连接池中的服务器时,要保证有足够多未使用的容量,以备在撤下服务器做维护时适用,或者当服务求失效时可以派上用场。每台服务器上都应该保留高于\"足够'的容量。要确保配置的限制值足够高,即使从池中撤出一些服务器也能够工作。举个例子,如果你发现每个MySQL服务器一般有100个连接,应该设置池中每个服务器的max_connections值为200.这样就算一半的服务器失效,服务器池也能处理同样数量的请求
引入中间件。迄今为止,我们所讨论的方案都嘉定应用跟MySQL服务器使直接相连的。但是许多负载均衡解决方案都会引入一个中间件,作为网络通信的代理。它以便接受所有的通信请求,另一边将这些请求派发到指定的服务器上,然后把执行结果发送回请求的机器上。中间件可以实硬件设备或时软件(你可以把诸如LVS这样的解决方案配置成只有一个用需要创建一个新连接时才参与进来,此后不再作为中间价)。如图描述了这种架构,这种解决方案通常能工作得很好,当然除非为负载均衡器本身增加冗余,这样才能避免单点故障引起的整个系统瘫痪。从开源软件,如HAProxy,到许多广为人知的商业系统,有许多负载均衡器得到了成功的应用
一主多备间的负载均衡。最常见的复制拓扑结构就是一个主库加多个备库。我们很难绕开这个架构,许多应用都假设只有一个目标机器用于所有的写操作,或者所有的数据都可以从单个服务器上获得。尽管这个架构不太具有很好的可扩展性,但可以通过一些办法结合负载均衡来获得很好的效果。1.功能分区对于特定的目的可以通过配置备库或一组备库来极大地阔扎脑容量。一些比较常见的功能包括报表、分析、数据仓库,以及全文检索2.过滤和数据分区可以适用适用复制过滤技术在相似的备库上对数据进行分区。只要数据在主库上已经被隔离到不同的数据库或表中,这种方法就可以奏效。不幸的是,没有内建的八幡在行级别上进行复制过滤。你需要适用一些独创性的技术来实现这一点,例如适用触发器和一组不同的表。即使不把数据分区到各个备库上,也可以通过对读进行分区而不是随机分配来提高缓存效率。例如,可以把对以字母A-M开头的用户的读操作分配给一个给定的备库,把N-Z开头的分配给另外一个。这能够更好地利用每台机器的缓存,因为分离读更可能在缓存中找到相关的数据。最好的情况下,当没有写操作时,这样适用的缓存相当于两台服务器缓存的综合。相比之下,如果随机地在备库上分配读操作,每个机器的缓存本质上还是重复的数据,而总的有效缓存效率和一个备库缓存一样,不管你有多少台备库。3.将部分写操作转移到备库主库并不总是需要处理写操作中的所有工作。你可以分解写擦汗寻,并在备库上执行其中的一部分,从而显著减少主库的工作量4.保证备库跟上主库如果要在备库执行某种操作,他需要即使知道数据处于哪个时间点——哪怕需要等待一会儿才能到达这个点——可以适用函数MASTER_POS_WAIT()阻塞直到备库赶上了设置的主库同步点。另一种替代方案是复制复制心跳来检查延迟情况5.同步写操作也可以使用MASTER_POS_WAIT()函数来确保写操作已经被同步到一个或多个备库上。如果应用需要模拟同步复制来确保数据安全性,就可以在多个备库上轮流执行MASTER_POS_WAIT()函数。这就类似创建了一个\"同步屏障\",但任意一个备库出现复制延迟时,都可能花费很长时间完成,所以最好在确实需要的时候才适用这种方法(如果你的目的只是确保某些备库拥有时间,可以只等待一台备库接收到时间,MySQL5.5增加了半同步复制,能够支持这项技术)
负载均衡。负载均衡的基本思路很简单:在一个服务器集群中尽可能地平均负载量。通常的做法是在服务器前端设置一个负载均衡器(一般是专门的硬件设备)。然后负载均衡器将请求的连接路由到最空闲的可用服务器。如图显示了一个典型的大型网站负载均衡设置,其中一个负载均衡器用于HTTP流量,另一个用于MySQL访问。负载均衡有五个常见目的。1.可扩展性负载均衡对某些扩展策略有所帮助,例如读写分离时从备库读数据2.高效性负载均衡有助于更有效地使用资源,因为它能够控制请求被路由到何处。如果服务器器处理能力各不相同,这就尤为重要:你可以把更多的工作分配给性能更好的机器3.可用性一个灵活的负载均衡解决方案能够使用时刻保持可用的服务器4.透明性客户端无须知道是否存在负载均衡设置,也不需要关心在负载均衡器的背后有多少机器,它们的名字是什么。负载均衡器给客户端看到的只是一个虚拟的服务器5.一致性如果应用是有状态的(数据库事务、网站会话等),那么负载均衡器就应该将相关的查询指向同一个服务器,以防止状态丢失。应用无须跟踪到底连接的是哪个服务器。在与MySQL相关的领域里,负载均衡架构通常和数据分片及复制紧密相关。你可以把负载均衡和高可用性结合在一起,部署到应用的任一层次上。例如,可以在MySQL Cluster集群的多个SQl节点上做负载均衡,也可以在多个数据中心间作负载均衡,其中每个数据中心又可以使用数据分片架构,每个节点实际上是拥有多个备库的主——主复制对结构,这里又可以作负载均衡。对于高可用性策略也同样如此:在一个架构里可以配置多层的故障转移机制。负载均衡有许多微妙之处,举个例子,其中一个挑战就是管理读/写策略。有些负载均衡技术本身能够实现这一点,但其他的则需要应用自己知道哪些节点是可读的或可写的。在决定如何实现负载均衡时,应该考虑到这些因素。有许多负载均衡解决方案可以使用,从诸如Wackamole这样基于端点的(peer-based)实现,到DNS、LVS(Linux Virtual Server)硬件负载均衡器、TCP代理、MySQL Proxy,以及在应用中管理负载均衡。最普遍的策略是使用硬件负载均衡器,大多是使用HAProxy,它看起来很流行并且工作得很好。还有一些人使用TCP代理,例如Pen.但MySQL Proxy用得并不多
可扩展的MySQL
概述。接下来分析提到的复制、可扩展性以及高可用性三个主题中的第三个。归根结底,高可用性实际上意味着\"更少的宕机时间\"。然而糟糕的是,高可用性经常和其他的概念混淆,例如冗余、保障数据不丢失,以及负载均衡。高可用性实际上优点像神秘的野兽。它通常以百分比表示,这本身也是一种按时:高可用性不是绝对的,只有相对更高的可用性。100%的可用性是不可能达到的。可用性的\"9\"规则是表示可用性目标最普遍的方法,你可能也知道\"5个9\"表示99.999%的正常可用时间。换句话说,每年只允许5分钟的宕机时间。对于大多数应用这已经是令人惊叹的数字,尽管还有一些人视图获得更多的\"9\".每个应用对可用性的需求各不相同。在设定一个可用时间的目标之前,先问问自己,是不是确实需要达到这个目标。可用性每提高一点,所花费的成本都会远超之前;可用性的效果和开销的比例并不是线性的。需要保证多少可用时间,取决于能够承担多少成本。高可用性实际上是在宕机造成的损失与降低宕机时间所花费的成本之间取一个平衡。换句话说,如果需要花大量金钱去获得更好的可用时间,但所带来的收益却很低,可能就不值得做。总地来说,应用在超过一定的点以后追求更高的可用性是非常困难的。成本也会很高,因此我们建议设定一个更现实的目标并且避免过度设计。幸运的是,建立2个9或3个9的可用时间的目标可能并不困难,具体情况取决于应用。有时候人们将可用性定义成服务正在运行的时间段。我们认为可用性的定义还应该包括应用是否能以足够好的性能处理请求。有许多方法可以让一个服务器保持运行,但服务并不是真正可用。对一个很大的服务器而言,重启MySQL之后,可能需要几个消失才能充分预热以保证查询请求的响应时间是可以接受的,即使服务器只接收了正常流量的一小部分也是如此。另一个需要考虑的问题是,即使应用并没有停止服务,但是否可能丢失了数据。如果服务器遭遇灾难性故障,可能多少都会丢失一些数据,例如最近已经写入(最新丢失的)二进制日志但尚未传递到备库的中继日志中的事务。你能够容忍吗?大多数应用能够容忍;因为替代方案大多非常昂贵且复杂,或者有一些性能开销。例如,可以使用同步复制,或是将二进制日志档到一个通过DRBD进行复制的设备上,这样就算服务器完全失效也不用担心丢失数据(但是整个数据中心也有可能会掉电)。一个良好的应用架构通常可以降低可用性方面的需求,至少对部分系统而言是这样的,良好的架构也更容易做到高可用。将应用中重要和不重要的部分进行分离可以节约不少工作量和金钱,因为对于一个更小的系统改进可用性会更容易。可以通过计算\"风险敞口(risk exposure)\",将失效概率与失效代价相乘来确认高优先级的风险,画一个简单的风险计算表,以概率、代价和风险敞口作为列,这样很容易找到需要优先处理的享目。在前面讨论如何避免导致糟糕的可扩展性的原因,来退出如何获得更好的可扩展性。这里也会使用相似的方法来讨论可用性,因为我们相信,理解可用性最好的方法就是研究它的反面——宕机时间。
导致宕机的原因。我们经常听到导致数据库宕机最主要的原因是编写的SQL查询性能很差,真的是这样吗?2009年我们决定分析我们客户的数据库所遇到的问题,以找出那些真正引起宕机的问题,以及如何避免这些问题。结果正是了一些已有的猜想,但也否定了一些(错误的)认识,并从中学到了很多。我们首先对宕机时间按表现方式而非导致的原因进行分类。一般来说,\"运行环境\"是排名第一的宕机类别,大于35%的时间属于这一类。运行环境可以看作是支持数据库服务器运行的系统和资源集合,包括操作系统、硬盘以及网络等。性能问题紧随其后,也是约占35%;然后是复制,占20%,最后剩下的10%包含各种类型的数据丢失或损坏以及其他问题。我们对时间按类型进行分类后,确定了导致这些时间的原因。以下是一些需要足以的地方:1.在运行环境的问题中,最普遍的是磁盘空间耗尽2.在性能问题中,最普遍的宕机原因确实是运行很糟糕的SQL,但也不一定都是这个原因,比如也有很多问题是由于服务器Bug或错误的行为导致的3.糟糕的Schema和索引设计是第二大影响性能的问题4.复制问题通常由于主备数据不一致导致5.数据丢失问题通常由于DROP TABLE的误操作导致,并总是伴随着缺少可用备份的问题。复制虽然常被人们用来改善可用时间,但却也可能导致宕机。这主要是由于不正确的使用导致的,即便如此,它也阐明了一个普遍的情况:许多高可用性策略可能会产生反作用
提升平均失效时间(MTBF).其实只要尽职尽责地做好一些应做的事情,就可以避免很多宕机。在分类整理宕机事件并追查导致宕机的根源时,还发现,很多宕机本来是有一些方法可以避免的。我们发现大部分宕机事件都可以通过全面的常识性系统管理办法来避免。以下是做一些指导性的建议:1.测试恢复工具和流程,包括从备份中恢复数据2.遵从最小权限原则3.保持系统干净、整洁4.使用好的命名和组织约定来避免产生混乱,例如服务器是用于开发还是用于生产环境5.谨慎安排升级数据库服务器6.在升级前,使用诸如Percona Toolkit中的pt-upgrade之类的工具仔细检查系统7.使用InnoDB并进行适当的配置,确保InnoDB是默认存储引擎。如果存储引擎被禁止,服务器就无法启动8.确认基本的服务器配置是正确的9.通过skip_name_resolve禁止DNS10.除非能证明有效,否则禁用查询缓存11.避免使用复杂的特性,例如复制过滤和触发器,除非确实需要12.监控重要的组建和功能,特别是像磁盘空间和RAID卷状态这样的关键享目,但也要避免误报,只有当确实发生问题时才发送告警13.定期检查复制完整性14.讲备库设置为只读,不要让复制自动启动15.定期进行查询语句审查16.归档并清理不需要的数据17.为文件系统保留一些空间。在GNU/Linux中,可以使用-m选项来为文件系统本身保留空间。还可以在LVM卷组中留下一些空闲空间。或者,更简单的方法,仅仅创建一个巨大的空文件,在文件系统快满时,直接将其删除(这是100%跨平台兼容的)18.养成习惯,评估和管理系统的改变、状态以及性能信息我们发现对系统变更管理的缺失时所有导致宕机的实践中最扑鼻那的原因。典型的错误包括粗心的升级导致升级失败并遭遇一些Bug,或是尚未测试就将Schema或查询语句的更改直接运行到线上,或者没有为一些失败的情况制定计划,例如达到了磁盘容量限制。另外一个导致问题的主要原因是缺少严格的评估,例如因为疏忽没有确认备份是否是可以恢复的。最后,可能没有正确地监控MySQL的相关信息。例如缓存命中率报警并不能说明问题,并且可能产生大量的误报,这会使监控系统被认为不太有用,于是一些人就会忽略报警。有时候监控系统失效了,甚至没人会注意到,直至你的老板质问你,\"为什么Nagios没有告诉我们磁盘已经满了?\"
降低平均恢复时间(MTTR).之前提到,可以通过减少恢复时间来获得高可用性。事实上,一些人走的更远,只专注于减少恢复时间的某个方面:通过在系统中建立冗余来避免系统完全失效,并避免单点失效问题。在降低恢复时间上进行投资非常虫咬,一个能够提供冗余和故障转移能力的系统架构则是降低恢复时间的关键环节。但实现高可用性不单单是技术问题,还有许多个人和组织的因素。组织和个人在避免宕机和从宕机事件中恢复的成熟度和能力层次各不相同。团队成员是最重要的高可用性资产,所以为恢复制定一个好的流程非常重要。拥有熟练技能、应变能力、训练有素的雇员,以及处理紧急事件的详细文档和经过仔细测试的流程,对从宕机中恢复有巨大的作用。但也不能完全依赖工具和系统,因为它们并不能理解实际情况的细微差别,有时候它们的行为在一般情况下是正确的,但在某些场景下却会是个灾难对宕机事件进行评估有助于提升组织学习能力,可以帮助避免未来发生相似的错误,但是不要对\"事后反思\"或\"事后的调查分析\"期待太高。后见之明被严重曲解,并且一味想找到导致问题的唯一根源,这可能会影响你的判断力。许多流行的方法,例如\"五个为什么\",可能会被过度使用,导致一些人将他们的经历集中在找到唯一的替罪羊。很难去回顾我们解决的问题当时所处的状况,也很难理解真正的原因,因为原因通常是多方面的。因此,尽管事后反思可能是有用的,但也应该对结论有所保留。即使是这里给出的建议,也是基于长期研究导致宕机事件的原因以及如何预防它们所得,并且只是这里的观点而已。这里要反复提醒:所有的宕机事件都是由多方面的失效联合在一起导致的。因此可以通过利用合适的方法确保单点的安全来避免。整个链条必须要打断,而不仅仅是单个环节。例如,那些问我们求助恢复数据的人不仅遭受数据丢失(存储失效,DBA误操作等)。同时还缺少一个可用的备份。这样说来,当开始调查并尝试阻止失效或加速恢复时,大多数人和组织不应太过于内疚,而是要专注于技术上的一些措施——特别是那些很酷的方法,例如集群系统和冗余架构。这些是有用的,但要记住这些系统依然会失效。事实上,前面提到的MMM复制管理,已经对它失去了型取,因为它被证明可能导致更多的宕机事件。你应该不会奇怪一组Perl脚本会陷入混乱,但即使是特别昂贵并精密设计的系统也会出现灾难性的失效——是的,即使是花费了大量金钱的SAN也是如此。已经见过太多的SAN失效
如何实现高可用性。可以通过同时进行以下两步来获得高可用性。首先,可以尝试避免导致宕机的原因来减少宕机时间。许多问题其实很容易避免,例如通过适当的配置、监控,以及规范或安全保障措施来避免认为错误。第二,金狼保证在发生宕机时能够快速恢复。最常见的策略时在系统中制造冗余,并且具备故障转移的能力。这两个维度的高可用性可以通过两个相关的度量来确定:平均失效时间(MTBF)和平均恢复时间(MTTR)。一些阻止会非常仔细地追踪这些度量值.第二步——通过冗余快速恢复——很不幸,这里时应该最注意的地方,但预防措施的投资回报率会很高。
共享存储或磁盘复制。共享存储能够为数据库服务器和存储解耦合,通常使用的是SAN.使用共享存储时,服务器能够正常挂载文件系统并进行操作。如果服务器挂了,备用服务器可以挂载相同的文件系统,执行需要的恢复操作,并在失效服务器的数据上启动MySQL.这个过程在逻辑上跟修复那台故障的服务器没什么两样,不过更快速,因为备用服务器已经启动,随时可以运行。当开始故障转移时,检查文件系统、恢复InnoDB以及预热(Percona Server提供了一个新特性,能够把buffer pool保存下来并在重启后还原,在使用共享存储时能够很好地工作。这可以减少几个小时甚至好几天的预热时间。MySQL5.6也有相似的特性。)是最有可能遇到延迟的地方,但检测失效本身在许多设置中也会花费很长时间。共享存储有两个优点:可以避免除存储外的其他任何组件失效所引起的数据丢失,并为非存储组件建立冗余提供可能。因此它有助于减少系统一些部分的可用性需求,这样就可以集中精力关注一小部分组件来获得高可用性。不过,共享存储本身仍然是可能失效的单点。如果共享存储失效了,那整个系统也失效了,尽管SAN通常设计良好,但也可能失效,有时候需要特别关注,就算SAN本身拥有冗余也会失效。共享存储本身也有风险,如果MySQL崩溃等故障导致数据文件损坏,可能会导致备用服务器无法恢复。我们强烈建议在使用共享存储策略时选择InnoDB存储引擎或其他稳定的ACID存储引擎。一次崩溃几乎肯定会损坏MyISAM表,需要花费很长时间来修复,并且会丢失数据。我们也强烈建议使用日志型文件系统。我们见过比较严重的情况是,使用非日志型文件系统和SAN(这是文件系统的问题,跟SAN无关)导致数据损坏无法恢复。磁盘复制技术是另外一个获得跟SAN类似效果的方法。MySQL中最普遍使用的font color=\"#a23735\
1.MySQL ClusterMySQL中的同步复制首先出现在MySQL Cluster(NDB Cluster)。它在所有节点上进行同步的主——主复制。这意味着可以在任何节点上写入;这些节点拥有等同的读写能力。每一行都是冗余存储的,这样即使丢失了一个节点,也不会丢失数据,并且集群仍然能提供服务。尽管MySQL Cluster还不是是哟个与所有应用的完美解决方案,但正如我们在前面提到的,在最近的版本中它做了非常快速的改进,现在已经拥有了大量的新特性和功能:非索引数据的磁盘存储、增加数据节点能够在线扩展、使用ndbinfo表来管理集群、配置和管理集群的脚本、多线程操作、下推(push-down)的关联操作(现在称为自适应查询本地化)、能够处理BLOB列和很多列的表、集中式的用户管理,以及通过像memcached协议一样的NDB API来实现NoSQL访问。在下一个版本中将包含最终一致运行模式,包括为跨数据中心的主动-主动复制提供事务冲突检测和跨WAN解决方案。简而言之,MySQL Cluster是一项引人注目的技术。现在至少有两个为简化集群部署和管理提供附加产品的供应商:Oracle针对MySQL Cluster的服务支持包含了MySQL Cluster Manager工具;Severalnines提供了Cluster Control工具,该工具还能够帮助部署和管理复制集群
MySQL同步复制。当使用同步复制时,主库上的事务只有在至少一个备库上提交后才能认为其执行完成。这实现了两个目标:当服务器崩溃时没有提交的事务会丢失,并且至少有一个备库拥有实时的数据副本。大多数同步复制架构运行在主动——主动模式。这意味着每个服务器在任何时候都是故障转移的候选者,这使得通过冗余关于获得高可用性更加容易,早期版本的MySQL不支持同步复制(MySQL5.5支持半同步复制),还有两个基于MySQL的集群解决方案支持同步复制。
基于复制的冗余。复制管理器是使用标准MySQL复制来创建冗余的工具(冗余并不等同于高可用性)。尽管可以通过复制来改善可用性,但也有一些\"玻璃天花板\"会阻止MySQL当前版本的异步复制和半同步复制获得和真正的同步复制相同的结果。复制无法保证实时的故障转移和数据零丢失,也无法将所有节点等同对待。复制管理器通常监控和管理三件事:应用和MySQL间的通信、MySQL服务器的健康度,以及MySQL服务器间的复制关系。它们既可以修改负载均衡的配置,也可以在必要的时候转移虚拟IP地址以使应用连接到合适的服务器上,还能够在一个伪集群中操作复制以选择一个服务器作为写入节点。大体上操作并不复杂:只需要确定写入不会发送到一个还没有准备好提供写服务的服务器上,并保证当需要提升一台备库为主库时记录下正确的复制坐标。这听起来在理论上是可行的,但经验表明实际上并不总是能有效工作。事实上这非常糟糕,有些时候最好有一些轻量级的工具集来帮助从常见的故障中恢复并以很少的开销获得较高的可用性。不幸的是,还没有听说过任何一个好的工具及可以可靠地完成这一点。后面会介绍两个复制管理器,其中一个很新,而另外一个则有很多问题。我们发现很多人试图去写自己的复制管理器。它们常常会陷入很多人已经遭遇过的陷阱,自己去写一个复制管理器并不是好主意。异步组件有大量的故障形式,很多你从未亲身经历过,其中一些甚至无法理解,并且程序也无法适当处理。因此从这些异步组件中得到正确的行为相当困难,并且可能遭遇数据丢失的危险。事实上,机器刚开始出现问题时,由一个经验丰富的人来解决时很快的。但如果其他人做了一些错误的修复操作则可能导致问题更严重我们要提到的第一个复制管理器时MMM.有些人认为它在一些人工——故障转移模式下的场景下比较有用,而有些人甚至从不使用这个工具。许多人在自动——故障转移模式下使用该工具时确实遇到了许多严重的问题。它会导致健康的服务器离线,也可能将写入发送到错误的地点,并将备库移动到错误的坐标。有时混乱就接踵而至.另外一个比较新一点的工具时Yoshinori Matsunobu 的MHA工具集,它和MMM一样时一组脚本,使用相同的通用技术来建立一个伪集群,但它不是一个完全的替换这;它不会去做太多的事情,并且依赖于Pecemaker来转移虚拟IP地址。一个主要的不同是,MHA有一个很好的测试集,可以防止一些MMM遇到过的问题。除此之外对该工具集还没有更多的认识。基于复制的冗余最终来说好坏参半。只有在可用性的重要性远比一致性或数据零丢失保证更重要时才推荐使用。例如,一些人并不会真的从它们的网站功能重获利,而是从它的可用性重赚钱。谁会在乎是否出现了故障导致一张照片丢失了几条评论或者其他什么东西呢?只要广告收益继续滚滚而来,可能并不值得花更多成本去实现真正的高可用性。但还是可以通过复制来建立\"尽可能的\"高可用性,当遇到一些很难处理的严重宕机时,可能会有所帮助。这是一个大赌注,并且可能对大多数人而言太过于冒险,除非是哪些老成(或者专业)的用户。问题时许多用户不知道如何去证明自己有资格并评估复制\"轮盘赌\"是否适合它们。这有两个方面的原因。第一,它们并没有看到\"玻璃天花板\",错误地认为一组虚拟IP地址、复制以及管理脚本能够实现真正的高可用性。第二,它们低估了技术的复杂度,因此也低估了严重故障发生后从中恢复的难度。一些人认为它们能够使用基于复制的冗余技术,但随后它们可能会更希望选择一个有更强保障的简单系统。其他一些类型的复制,例如DRBD或者SAN,也有他们的缺点——但也不要认为将这些技术说的无所不能而把MySQL自身的复制贬得一团糟,那不是本意。你可以为DRBD写出低质量的故障转移脚本,这很简单,就像为MySQL复制编写脚本一样。主要的区别时MySQL复制非常复杂,有很多非常细小的差别,并且不会阻止你干坏事
提升备库或切换角色。提升一台备库为主库,或者在一个主——主复制结构中调换主动和被动角色,这些都是许多MySQL故障转移策略很重要的一部分。我们不能认定自动化工具总能在所有的情况下做正确地事情。你不应该假定在发生故障时能够立刻切换到被动备库,这要看具体的工作负载。备库会重放主库的写入,但如果不用来提供读操作,就无法进行预热来为生产环境负载提供服务。如果希望有一个随时能承担读负载的备库,就要不断地\"训练\"它,既可以将其用于分担工作负载,也可以将生产环境地读查询镜像到备库上。我们有时候通过监听TCP流量,截取除其中地SELECT查询,然后在备库上重放来实现这个目的。Percona Toolkit中有一些工具可以做到这一点。
等待更新扩散。经常有这种情况,在某一层帝国一了一个冗余后,需要等待低层执行一些改变。我们指出通过DNS修改服务器是一个很脆弱的解决方案,因为DNS的更新扩散速度很慢,改变IP地址可给予你更多的哦内阁制,但在一个LAN中的IP地址同样依赖于更底层——ARP——来扩散更新
虚拟IP地址或IP接管。可以为需要提供特定服务的MySQL实例指定一个逻辑IP地址。当MySQL实例失效时,可以将IP地址转移到另一台MySQL服务器上。这和前面提到的思想本质是相同的,唯一的不同是现在是用于故障转移,而不是负载均衡。这种方法的好处是对应用透明。它会中断已有的连接,但不要求修改配置。有时候还可以原子地转移IP地址,保证所有的应用在同一时间看到这一变更。当服务器在可用和不可用状态间\"摇摆\"时,这一点尤其重要。以下是它的一些不足之处:1.需要把所有的IP地址定义在同一网段,或者使用网络桥接2.改变IP地址需要系统root权限3.有时候还需要更新ARP缓存。有些网络设备可能会把ARP信息保存太久,以致无法即时将一个IP地址切换到另一个MAC地址上。我们看到过很多网络设备或其他组件不配合切换的例子,结果系统的许多部分可能无法确定IP地址到底在哪里4.需要确定网络硬件支持快速IP接管。有些硬件需要克隆MAC地址后才能工作5.有些服务器即使完全丧失功能也会保持持有IP地址,所以可能需要从物理上关闭或断开网络连接。这就是为人所熟知的\"击中其他节点的头部\"(shoot the other node in the head 简称STONITH).他还有一个更加微妙并且比较官方的名字:击剑(fencing)浮动IP地址和IP接管能够很好地应付彼此临近(也就是同一子网内)的机器之间的故障转移。但是最后需要提醒的是,这种策略并不总是万无一失,还取决于网络硬件等因素
中间件解决方案。可以使用代理、端口转发、网络地址转换(NAT)或者硬件负载均衡来实现故障转移和故障恢复。这些都是很好的解决方法,不像其他方法可能会引入一些不确定性(所有系统组件认同哪一个是主库码?它能够即使并原子地更改吗?)它们是控制应用和服务器连接的中枢。但是,它们自身也引入了单点失效,需要准备冗余来避免这个问题。使用这样的解决方案,你可以将一个远程数据中心设置成看起来好像和应用在同一个网络里。这样就可以使用诸如浮动IP地址这样的技术让应用和一个完全不同的数据中心开始通信。你可以配置每个数据中心的每台应用服务器,通过它自己的中间件连接,将流量路由到活跃数据中心的机器上,如图所示。如果活跃数据中心安装的MySQL彻底崩溃了,中间件可以路由流量到另外一个数据中心的服务器池中,应用无须知道这个变化。这种配置方法的主要缺点是在一个数据中心的Apache服务器和另外一个数据中心的MySQL服务器之间的延迟比较大。为了缓和这个问题,可以把Web服务器设置重定向模式。这样通信都会被重定向到放置活跃MySQL服务器的数据中心。还可以使用HTTP代理来实现这一目标。如图显示了如何使用代理来连接MySQL服务器,也可以将这个方法和许多别的中间件架构结合在一起,例如LVS和硬件负载均衡器
在应用中处理故障转移。有时候让应用来处理故障转移会更加简单或者更加灵活。例如,如果应用遇到一个错误,这个错误外部观察者正常情况下是无法察觉的,例如关于数据库损坏的错误日志信息,那么应用可以自己来处理故障转移过程。虽然把故障转移处理过程整合到应用中看起来比较吸引人,但可能没有想象中那么有效。大多数应用有许多组件,例如cron任务、配置文件,以及用不同语言编写的脚本。将故障转移整合到应用中可能导致应用变得太过笨拙,尤其是当应用增大并变得更加复杂时。但是将监控构建到应用中是一个好主意,当需要时,能够立刻开始故障转移过程。应用应该也能够管理用户体验,例如提供降级功能,并显示给用户合适的信息.
故障转移和故障恢复。冗余是很好的技术,但实际上只有在遇到故障需要恢复时才会用到。(见鬼,这可以用备份来实现)。冗余一点儿也不会增加可用性或减少宕机。在故障转移的过程中,高可用性是建立在冗余的基础上。当有一个组件失效,但存在冗余时,可以停止使用发生故障的组件,而使用冗余备件。冗余和故障转移结合可以帮助更快地恢复,如你所知,MTTR(平均恢复时间)的减少将降低宕机时间并改善可用性。在继续这个话题之前,我们先来定义一些术语。我们统一使用\"故障转移(failover)\
高可用性
概述。如果在提高MySQL的性能上花费太多时间,容易使事业局限于MySQL本身,而忽略了用户体验。回过头来看,也许可以意识到,或许MySQL已经足够优化,对于用户看到的响应时间而言,其所占的比重已经非常之小,此时应该关注下其他部分了。这是个很不错的观点,尤其是对DBA而言,这是很值得去做的正确的事。但如果不是MySQL,那有时什么导致了问题?在前面的讨论中,可以通过测量可以快速而准确地给出答案。如果能顺着应用的逻辑过程从头到尾来剖析,那么找到问题的源头一般来说并不困难。有时,尽管问题在MySQL上,也很容易在系统的另一部分得到解决。无论问题出在哪里,都至少可以找到一个靠谱的工具来帮助进行分析,而且通常事免费的。例如,如果有JavaScript或者页面渲染的问题,可以使用包括FireFox浏览器的Firebug插件在内的调优工具,或者使用Yahoo!的YSlow工具。一些工具甚至可以剖析整个堆栈:New Relic是一个很好的例子,它可以剖析Web应用的前端、应用以及后端。
常见问题。我们在应用中反复看到一些相同的问题,经常是因为人们使用了缺乏设计的线程系统或者简单开发的流行框架。虽然有时候可以通过这些框架更快更简单地构建系统,但是如果不清楚这些框架背后做了什么操作,反而会增加系统的风险。下面是我们经常会碰到的问题清单,通过这些过程可以激发你的思维。1.什么东西在消耗系统中每台主机的CPU、磁盘、网络,以及内存资源?这些值是否合理?如果不合理,对应用程序做基本的检查,看什么占用了资源。配置文件通常是解决问题最简单的方式。例如,如果Apache因为创建1000各需要50MB内存的工作进程而导致内存溢出,就可以配置应用程序少使用一些Apache工作进程。也可以配置每个进程少使用一些内存。2.应用真的需要所有获取到的数据吗?获取1000行数据但只显示10行,而丢弃剩下的990行,这是常见的错误。(如果应用程序缓存了另外的990行备用,这也许是有意的优化)3.应用在处理本应由数据库处理的事情吗?或者反过来?这里有两个例子,从表中获取所有的行在应用中进行统计计数,或者在数据库中执行复杂的字符串操作。数据库擅长统计计数,而应用擅长正则表达式。要善于使用正确的工具来完成任务。4.应用执行了太多的查询?ORM宣称的把程序员从写SQL中解放出来的语句解耦通常是罪魁祸首。数据库服务器为从多个表匹配数据做了很多优化,因此应用程序完全可以删掉多余的嵌套循环,而使用数据库的关联来代替。5.应用执行的查询太少了?好吧,上面之说了执行太多SQL可能成为问题。到那时,有时候让应用来做\"手工关联\"以及类似的操作也可能是个好主意。因为它们允许更细的粒度控制和更有效的使用缓存,以及更少的锁争用,甚至有时应用代码里模拟的哈希关联会更快(MySQL的嵌套循环的关联方法并不总是高效的)6.应用创建了没必要的MySQL连接吗?如果可以从缓存中获得数据,就不要再连接数据库7.应用对一个MySQL实例创建连接的次数太多了吗?(也许因为应用的不同部分打开了它们自己的连接)通常来说更好的办法是重用相同的连接。8.应用做了太多的\"垃圾\"查询?一个常见的例子是发送查询前先发送一个ping命令看数据库是否存活,或者每次执行SQL前选择需要的数据库。总是连接到一个特定的数据库并使用完整的表名也许是更好的方法(这也使得从日志或者通过SHOW PROCESSLIST看SQL更容易了,因为执行日志中的SQL语句的时候不用再切换到特定的数据,数据库名已经包含在SQL语句中了。)\"预备\"(Preparing)连接时另一个常见问题。Java驱动在预备期间会做大量的操作,其中大部分可以禁用。另一个常见的垃圾查询是SET NAMES UFT8。这是一个错误的方法(它不会改变客户端库的字符集,只会影响服务器的设置)。如果应用在大部分情况使用特定的字符集工作,可以修改配置文件把特定字符集设为默认值,而不需要在每次执行时去做修改。9.应用使用了连接池吗?这既可能是好事,也可能是坏事。连接池可以帮助限制总的连接数,有大量SQL执行的时候效果不错(Ajax应用是一个典型的例子)。然而连接池也可能有一些副作用,比如说应用的事务、临时表、连接相关的配置项,以及用户自定义变量之间相互干扰等10.应用是否适用长连接?这可能导致太多连接。通常来说长连接不是个好主意,除非网络环境很慢导致创建连接的开销很大,或者连接只被一或两个很快的SQL使用,或者连接频率很高导致客户端本地端口不够用。如果MySQL的配置正确,也许就不需要长连接了。比如使用skip-name-resolve来避免DNS反向查询,确保thread_cache足够大,并且增加back_log。11.应用是否在不使用的时候还保持连接打开?如果是这样,尤其是连接到很多服务器时,可能会过多地消耗其他进程所需要地连接。例如,假设你连接到10个MySQL服务器。从一个Apache进程中获取10个连接不是问题,但是任意时刻其中只有1个在真正工作。其他9个大部分时间都处于Sleep状态。如果其中一台服务器变慢了,或者有一个很长地网络请求,其他地服务器就可能因为连接数过多而受到影响。解决方案时控制应用怎么使用连接。例如,可以将操作批量地依次发送到每个MySQL实例,并且在下一次执行SQL前关闭每个连接。如果执行的是比较消耗时间的操作,例如调用Web服务接口,甚至可以先关闭MySQL连接,执行耗时的工作,再重新打开MySQL连接继续在数据库上工作。长连接和连接池的区别可能使人困惑。长连接可能跟连接池有同样的副作用,因为重用的链接在这两种情况下都是有状态的。然而,连接池通常不会导致服务器连接过多,因为它们会在进程间排队和共享连接。另一方面,长连接是在每个进程基础上创建,不会在进程间共享。连接池也比共享连接的方式对连接策略有更强的控制力。连接池可以配置为自动扩展,但是通常的实践经验是,当遇到连接池完全占满时,应该将连接请求进行排队而不是扩展连接池。这样做可以在应用服务器上进行排队等,而不是将压力传递到MySQL数据库服务器上导致连接数太多而过载。有很多方法可以使得查询和连接更快,但是一般的规则是,如果能够直接避免进行查询和连接,肯定比努力提升查询和连接的性能能获得更好的优化结果
寻找最优并发度。每个Web服务器都有一个最佳并发度——就是说,让进程处理请求尽可能快,并且不超过系统负载的最优的并发连接数。这就是前面说的最大系统容量。进行一个简单的测量和建模,或者只是反复试验,就可以找到这个\"神奇的数\
Web服务器问题。Apache是最流行的Web应用服务器软件。它在许多情况下都运行良好, 但如果使用不当也会消耗大量的资源。最常见的问题是保持它的进程的存活(alive)时间过长,或者使用各种不同的用途下混合使用,而不是分别对不同类型的工作进行优化。Apache通常是通过prefork配置来使用mod_php、mod_perl和mod_python模块的。 prefork模式会为每个请求预分配进程。因为PHP、Perl和Python脚本是可以定制化的,每个进程使用50MB或100MB内存的情况并不少见。当一个请求完成后,会释放大部分内存给操作系统,但并不是全部。Apache会保持进程处于打开状态以备后来的请求重用。这意味着,如果下一个请求是请求静态文件,比如一个CSS文件或者一张图片,就会出现用一个占用内存很多的进程来为一个很小的请求服务的情况。这就是使用Apache作为通用Web服务器很危险的原因。它的确是为通用目的而设计的,但如果能够有针对性地使用其长处,会获得更好的性能。另一个主要的问题是,如果开启了Keep-Alive设置,进程可能很长时间处于繁忙状态。当然,即使没有开启Keep-Alive,某些进程也可能存活很久,\"填鸭式\
应用层以下的缓存。MySQL服务器有自己的内部缓存,但也可以构建你自己的缓存和汇总表。可以对缓存表量身定制,使他们最有效地过滤、排序、与其它表关联、计数,或者用于其他用途。缓存表也比许多应用层缓存更持久,因为在服务器重启后它们还存在。缓存并不总是有用。必需确认缓存真的可以提升性能,因为有时候缓存可能没有任何帮助。例如,在实践中发现从Nginx的内存中获取内容比从缓存中代理中获取要快,如果代理的缓存在磁盘上则尤其如此.原因很简单:缓存自身也有一些开销。比如检查缓存是否存在,如果命中则直接从缓存中返回数据。另外将缓存对象失效或者写入新的缓存对象都会有开销。缓存只在这些开销比没有缓存的情况下生成和提供数据的开销少时才有用。如果直到所有这些操作的开销,就可以计算出缓存能提供多少帮助。没有缓存时的开销就是每个请求生成数据的开销。有缓存的开销是检查缓存的开销加上缓存不命中的概率乘以生成数据的开销,再加上缓存命中的概率乘以缓存提供数据的开销。如果有缓存时的开销比没有时要低,则说明缓存可能有用,但依然不能保证。还要记住,就像从Nginx的内存中获取数据比从代理在磁盘中的缓存获取要好一样,有些缓存的开销比另外一些要低
2.本地共享内存缓存。这种缓存一般是中等大小(几个GB),快速,难以在多台机器间同步。它们对小型的半静态位数据比较合适。例如每个州的城市列表,分片数据存储的分区函数(映射表),或者使用存活时间(TTL)策略进行失效的数据等。共享内存最大的好处是访问非常快——通常比其他任何远程缓存访问都要快不少3.分布式内存缓存。最常见的分布式内存缓存的例子是memcached.分布式缓存比本地共享内存缓存要大得多,增长也容易。缓存中创建的数据每一个比特都只有一份副本,这样既不会浪费内存,也不会因为相同的数据存在不同的地方而引入一致性问题。分布式内存非常适合存储共享对象,例如用户资料,评论,以及HTML片段。分布式缓存比本地共享缓存的延时要高得多,所以最高效的使用方法是批量进行多个获取操作(例如,在一次循环中获取多个对象)。分布式缓存还需要考虑怎么增加更多的节点,以及某个节点崩溃了怎么处理。对于这两个场景,应用程序必需决定在节点间怎么分布或重分布缓存对象。当缓存集群增加或减少一台服务器时,一致性缓存对避免性能问题而言是非常重要的。4.磁盘上的缓存。磁盘是很慢的,所以缓存在磁盘上的最好是持久化对象,很难全部装进内存的对象,或者静态内容(例如预处理的自定义图片)。对于磁盘上的缓存和Web服务器,一个非常有用的技巧是使用404错误处理机制来捕捉缓存未命中的情况。假设Web应用要在头部展示一张基于用于名(\
应用层缓存。应用层缓存通常在同一台机器的内存中存储数据,或者通过网络存在另一台机器的内存中。因为应用可以缓存部分计算结果,所以应用缓存可能比更低层次的缓存更有效。因此应用层缓存可以节省两方面的工作:获取数据以及基于这些数据进行计算。一个很好的例子是HTML文本块。应用程序可以生成例如头条新闻的标题这样的HTML片段,并且做好缓存。后续的页面试图就可以简单地插入这个缓存过的文本。一般来说,在缓存数据前对数据做的处理越多,缓存命中节省的工作越多。但应用层缓存也有缺点,那就是缓存命中率可能更低,并且可能使用较多的内存。假设需要50个不同版本的头条新闻标题,以使不同地区生活的用户看到不同的内容,那就需要足够的内存去存储全部50个版本,任何给定版本的标题命中次数都会更少,并且失效策略也会更加复杂。应用缓存有许多种,下面是其中的一小部分:
缓存对象分层。分层缓存对象对检索、失效和内存利用都有帮助。相对于只缓存对象,也可以缓存对象的ID、对象的ID组等通常需要一起检索的数据。电子商务网站的搜索结果是这种技术很好的例子。一次搜索可能返回一个匹配产品的列表,包括名称、描述、缩略图,以及价格。缓存整个列表的效率很低:其他的搜索也可能会包含一些相同的产品,这就会导致数据重复,并且浪费内存。这种策略也使得当一个产品价格变动时,找出并失效搜索结果变得很困难,因为你必须查看每个列表,找到哪些列表包含了更新过的产品。可以缓存关于搜索的最小信息,而不必缓存整个列表,例如返回结果的数量以及列表中的产品ID。然后可以再单独缓存每个产品。这样做可以解决 两个问题:不会重复存放任何结果数据,也更容易在失效产品的粒度上去失效缓存。缺点则是,相对于一次性获得整个搜索结果,必须在缓存中检索多个对象。然而不管怎么说,为搜索结果缓存产品ID的列表都是更有效的做法。先在一个缓存命中返回ID的列表,再使用这些ID去请求缓存获得产品信息。如果缓存允许在一次调用里返回多个结果,第二次请求就可以返回多个产品(memcached通过mget()调用来支持)如果使用不当,这种方法可能会导致奇怪的结果。假设使用TTL策略来失效搜索结果,并且当产品变更时显式地区失效单个产品。现在想象以下,一个产品地描述发生了变化,不再包含搜索中匹配地关键字,但是搜索结果地缓存还没有过期失效,此时用户就会看到错误地搜索结果,因为缓存的搜索结果将会引用这个变化了的产品,即使它不再包含匹配搜索的关键字。对于大多数应用程序来说,这不是问题。如果应用程序不能容忍这种情况,可以使用基于版本的缓存,并在执行搜索时在结果中存储产品的版本好。当发现搜索结果在缓存中时,可以将当前搜索结果的版本号和搜索结果中的每个产品的版本号做比较。如果发现任何一个产品的版本数据不一致,可以重新搜索并且重新缓存结果。这对理解远程缓存访问的花销是多么昂贵非常重要。虽然缓存很快,也可以避免很多工作,但在LAN环境下网络往返缓存服务器通常也需要0.3ms左右。我们见过很多案例,复杂的网页需要一千次左右的缓存访问来组合页面结果,这将会耗费3s左右的网络延时,意味着你的页面可能慢得不可接受,即使它甚至不需要访问数据库!因此,在这种情况下对缓存使用批量获取调用是非常重要的。对缓存分层,采用小一些的本地缓存,也可能获得很大的收益
作为基础组件的缓存。缓存有可能成为基础设施的重要组成部分。也很容易陷入一个陷阱,认为缓存虽然很好用,但并不是重要到非有不可得东西。你也许会辩驳,如果缓存服务器宕机或者缓存被清空,请求也可以直接落在数据库上,系统依然可以正常运行。如果是刚刚将缓存加入应用系统,这也许是对的,但缓存的加入可以使得在应用压力显著增长时不需要对系统的某些部分同比增加资源投入——通常是数据部分。因此,系统可能慢慢地变得对缓存非常依赖,却没有被发觉。例如,如果高速缓存命中率是90%,当由于某种原因失去缓存,数据库上的负载将增加到原来的10倍。这很可能导致压力超过数据库服务器的性能极限。为了避免像这样的意外,应该设计一些高可用性缓存(包括数据和服务)的解决方案,或者至少是评估好禁用缓存或丢失缓存时的性能影响。比如说可以设计应用在遇到这样的情况时能够进行降级处理。
拓展MySQL。如果MySQL不能做你需要的事,一种可能是拓展其功能。在这里不会展示如何去做到这一点,但会提供一些可能的方向。如果你对进一步探索有兴趣,那么有很多很好的在线资源。当我们说\"MySQL不能做你需要的事\",我们指的是两件事情:MySQL根本做不到这一点或者MySQL可以做到,但是只能通过缓慢或笨拙的方法,总之做得不够好。无论哪个都是需要对MySQL拓展的原因。好消息事,MySQL已经越来越模块化和通用。存储引擎是拓展MySQL的一个很好的方式。Brian Aker已经写了一个存储引擎的框架,还有一系列介绍有关如何开始编写自己的存储引擎的文章。这是目前几个主要的第三方存储引擎的基础。许多公司都编写了它们自己的内部存储引擎。例如,一些社交网站公司使用了特殊的为社交图形操作设计的存储引擎,我们还知道有个公司定制了一个用于模糊搜索的引擎。写一个简单的自定义存储引擎并不难。还可以使用存储引擎作为另一个软件的接口。Sphinx引擎就是一个很好的例子,该引擎是Spinx全文检索软件的接口
缓存。缓存对高负载应用来说至关重要。一个典型的Web应用程序会提高大量的内容,直接生成这些内容的成本比采用缓存要高得多(包括检查和缓存超时的开销),所以采用缓存通常可以获得数量级的性能提升。诀窍是找到正确的粒度和缓存过期策略组合。另外也需要决定哪些内容适合缓存,缓存在哪里。典型的高负载应用会有很多层缓存。缓存并不仅仅发生在服务器上,而是在每一个环节,甚至包括用户的Web浏览器(这就是内容过期头的用处)。通常,缓存越接近客户端,就越节省资源并且效率更高。从浏览器缓存提供一张图片比从Web服务器的内容获取快得多,而从服务器的内存读取又比从服务器的磁盘上读取好得多。每种类型的缓存有其不一样的特点,例如容量和延时。可以把缓存分成两大类:被动缓存和主动缓存。被动缓存除了存储和返回数据外不做任何事情。当从被动缓存请求一些内容时,要么可以得到结果,要么得到\"结果不存在\"。被动缓存的一个典型例子时memcached.相比之下,主动缓存会在访问未命中时做一些额外的工作。通常会将请求转发给应用的其他部分来生成请求结果,然后存储该结果并返回给应用。Squid缓存代理服务器就是一个主动缓存。设计应用程序时,通常希望缓存是主动的(也可以叫做透明的),因为它们对应用隐藏了检查——生成——存储这个逻辑过程。也可以在被动缓存的前面构建一个主动缓存。
应用层优化
为什么要备份?下面是备份非常重要的几个理由:1.灾难恢复灾难恢复是下列场景下需要做的事情:硬件故障、一个不经意的Bug导致数据损坏,或者服务器及其数据由于欧协原因不可获取或无法使用等。你需要准备好应付很多问题:某人偶尔连错数据库服务器执行了一个ALTER TABLE的操作,计放大楼被炸毁,恶意的黑客攻击或MySQL的Bug等。尽管遭受任何一个特殊的灾难的几率都非常低,但所有的风险叠加在一起就很有可能会碰到2.人们改变象发不必惊讶,很多人经常会在删除某些数据后又想要恢复这些数据3.审计有时候需要知道数据或Schema在过去的某个时间点是什么样的。例如,你也许被卷入一场法律官司,或发现了应用的一个Bug,想知道这段代码之前干了什么(有时候仅仅依靠代码的版本控制还不够)4.测试一个最简单的基于实际数据来测试的方法是,定期用最新的生产环境数据更i性能测试服务器。如果使用备份的方案就非常简单:只要把备份文件还原到测试服务器上即可。检查你的假设。例如, 你认为共享虚拟主机供应商会提供MySQL服务器的备份?许多主机供应商根本不备份MySQL服务器,另外一些也仅仅在服务器运行时复制文件,这可能会创建一个损坏的没有用处的备份
备份误区1:\"复制就是备份\"这是我们经常碰到地一个误区。复制不是备份,当然使用RAID阵列也不是备份。为什么这么说?可以考虑以下,如果意外地在生产库上执行了DROP DATABASE,它们是否可以帮你恢复所有的数据?RAID和复制连这个简单的测试都没法通过。它们不是备份,也不是备份的替代品。只有备份才能满足备份的要求
定义恢复需求。如果一切正常,那么永远也不需要考虑恢复。但是,一旦需要恢复,只有世界上最好的备份系统是没用的,还需要一个强大的恢复系统。不幸的是,让备份系统平滑工作比构造良好的恢复过程和工具更容易。原因如下:1.备份在先。只有已经做了备份才可能恢复,因此在构建系统时,注意力自然会集中在备份上2.备份由脚本和任务自动完成。经常不经意地,我们会花些时间调优备份过程。花5分钟来对备份过程做小地调整看起来并不重要,但是你是否天天同样地重视恢复呢?3.备份时日常任务,但恢复常常发生在危急情形下4.因为安全的需要,如果正在做异地备份,可能需要对备份数据进行加密,或采取其他措施来进行保护,安全性往往只关注数据被盗用的后果,但是有没有人想过,如果没有人能对用来恢复数据的加密卷解锁,或需要从一个整块的加密文件中抽取单个文件时,损害又是多大?5.只有一个人来规划、设计和实施备份。当灾难袭来时,那个人可能不在。因此需要培养几个人并有计划地互为备份,这样就不会要求一个不合格的人来恢复数据这里有一个看到的真实例子:一个客户报告说当mysqldump加上-d选项后,备份变得像闪电一般快,他想知道为什么没有一个人提出该选项可以如此快地加速备份过程。如果这个客户已经尝试还原这些备份,就不难发现其原因:使用-d选项将不会备份数据!这个客户关注备份,却没有关注恢复,因此完全没有意识到这个问题。规划备份和恢复策略时,有两个重要的需求可以帮助思考:恢复点目标(PRO)和恢复时间目标(RTO)。它们定义了可以容忍丢失多少数据,以及需要等待多久将数据恢复。在定义RPO和RTO时,先尝试回答下面几类问题:1.在不导致严重后果的情况下,可以容忍丢失多少数据?需要故障恢复,还是可以接受自上次日常备份后所有的工作全部丢失?是否有法律法规的要求?2.恢复需要在多长时间内完成?哪种类型的宕机是可以接受的?哪种影响(例如,部分服务不可用)是应用和用户可以接受的?当哪些场景发生时,又该如何持续服务?3.需要恢复什么?常见的需求是护肤整个服务器,单个数据库,单个表,或仅仅是特定的事务或语句建议将上面问题的答案明确地用文档记录下来,同时还应该明确备份策略,以及备份过程
在线备份还是离线备份。如果可能,关闭MySQL做备份是最简单最安全的,也是所有获取一致性副本的方法中最好的。而且损坏或不一致的风险最小。如果关闭了MySQL,就根本不用关心InnoDB缓冲池中的脏页或其他缓存。也不需要担心数据在尝试备份的过程被修改,并且因为服务器不对应用提供访问,所以可以更快地完成备份。尽管如此,让服务器停机的代价可能比看起来要更昂贵。即使能最小化停机时间,在高负载和高数据量下关闭和重启MySQL也可能要花很长一段时间,尽管有一些能使这个影响最小化的技术,但并不能将其减少为零。因此,必需要设计不需要生产服务器停机的备份。即便如此,由于一致性的需要,对服务器进行在线备份仍然会有明显的服务中断。在众多的备份方法中,一个最大问题就是它们会使用FLUSH TABLES WITH READ LOCK操作,这会导致MySQL关闭并锁住所有的表,将MyISAM的数据文件刷新到磁盘上(但InnoDB不是这样的!),并且刷新查询缓存。该操作需要非常常的时间来完成。具体需要多长时间是不可预估的;如果全局读锁要等待一个长时间运行的语句完成,或有许多表,那么时间会更长。除非锁被释放,否则就不能在服务器上更改任何数据,一切都会被阻塞和积压(是的,即使SELECT查询也会被阻塞,因为如果有一个查询需要修改某些数据,只要它开始等待表上的写锁,所有尝试获取读锁的查询也必需等待)。FLUSH TABLES WITH READ LOCK不像关闭服务器的代价那么高,因为大部分缓存仍然在内存中,并且服务器一直是\"预热\"的,但是它也有非常大的破坏性。如果有人说这样做很快,可能是准备向你推销某种从来没有在真正的线上服务器上运行过的东西。避免使用FLUSH TABLES WITH READ LOCK的最好的方法是只使用InnoDB表。在权限和其他系统信息表中使用MyISAM表是不可避免地,但是如果数据改变量很少(正常情况下),你可以只刷新和锁住这些表,这不会有什么问题。在规划备份时,有一些与性能相关地因素需要考虑.1.锁时间需要持有锁多长时间,例如在备份期间持有地全局FLUSH TABLES WITH READ LOCK?2.备份时间复制备份到目的地需要多久?3.备份负载在复制备份到目的地时对服务器性能的影响有多少?4.恢复时间把备份镜像从存储位置复制到MySQL服务器,重放二进制日志等,需要多久?最大的权衡是备份时间与备份负载。可以牺牲其一以增强另外一个。例如可以提高备份的优先级,代价是降低服务器性能。同样,也可以利用负载的特性来设计备份。例如,如果服务器在完上的8小时内仅仅有50%的负载,那么可以尝试规划备份,使得服务器的负载低于50%且仍能在8小时内完成。可以采用许多方法来完成这个目标,例如,可以用ioice和nice来提高复制或压缩操作的优先级,使用不同的压缩等级,或在备份服务器上压缩而不是在MySQL服务器上。甚至可以使用lzo或pigz以获取更快的压缩。也可以使用0_DIRECT或fadvise()在复制操作时绕开操作系统的缓存,以避免污染服务器的缓存。像Percona XtraBackup和MySQL Enterprise Backup这样的工具都有下六选项,可以使用pv时加上--rate-limit选项来限制备份脚本的吞吐量
逻辑备份逻辑备份有如下优点:1.逻辑备份是可以用编辑器或像grep 和sed之类的命令查看和操作的普通文件。当需要恢复数据或只想查看数据但不恢复时,这都非常有帮助2.恢复非常简单。可以通过管道把它们输入到mysql,或者使用mysqlimport。3.可以通过网络来备份和恢复——就是说,可以在与MySQL主机不同的另外一台及其上操作4.可以在类似Amazon RDS这样不能访问底层文件系统的系统中使用5.非常灵活,因为mysqldump——大部分人喜欢的工具——可以接受许多选项,例如可以用WHERE子句来限制需要备份哪些行。6.与存储引擎无关。因为是从MySQL服务器中提取数据而生成,所以消除了底层数据存储和不同。因此,可以从InnoDB表中备份,然后只需极小的工作量就可以还原到MyISAM表中。而对于原始数据却不能这么做。7.有助于避免数据损坏。如果磁盘驱动器有故障而要复制原始文件时,你将会得到一个错误并且/或生成一个部分或损坏的备份。如果MySQL在内存中的数据还没有损坏,当不能得到一个正常的原始文件复制时,有时可以得到一个可以信赖的逻辑备份尽管如此,逻辑备份也有它的缺点:1.必需由数据库服务器完成生成逻辑备份的工作,因此要使用更多的CPU周期2.逻辑备份在某些场景下比数据库文件本身更大(以经验来看,逻辑备份往往比物理备份要小许多,但也并不总是如此)。ASCII形式的数据不总是和存储引擎存储数据一样高效。例如,一个整型需要4字节来存储,但是用ASCII写入时,可能需要12个字符。当然也可以压缩文件以得到一个更小的备份文件,但这样会使用更多的CPU资源。(如果索引比较多,逻辑备份一般要比物理备份小)3.无法保证导出后再还原出来的一定是同样的数据。浮点表示的问题、软件Bug等都会导致问题,尽管非常少见4.从逻辑备份中还原需要MySQL加载和解释语句,转化为存储格式,ing重建索引,所有这一切会很慢。最大的缺点时从MySQL中导出数据和通过SQL语句将其加载回去的开销。如果使用逻辑备份,测试恢复需要的时间将非常重要。Percona Server中包含的mysqldump,在使用InnoDB表时能起到帮助作用,因为它会对输出格式化,以便在重新加载时利用InnoDB的快速建索引的优点。测试显示这样做可以减少2/3甚至更多的还原事件。索引越多,好处越明显(如图所示,(上图是cat /var/lib/mysql-bin.000001操作所示))
逻辑备份还是物理备份。有两种主要的方法来备份MySQL数据:逻辑备份(也叫\"导出\")和直接复制原始文件的物理备份。逻辑备份将数据包含在一种MySQL能够解析的格式中,要么时SQL,要么时以某个符号分割的文本。(由mysqldump生成的逻辑备份并不一定是文本文件。SQL导出会包含许多不同的字符集,同样也会包含二进制数据,这些数据并不是有效的字符。对于许多编辑器来说,文件行也可能会太长。但是,大多数这样的文件还是可以被编辑器打开和读取,特别是mysqldump使用了--hex-blob选项时)。原始文件是指存在硬盘上的文件。任何一种备份都有其优点和缺点.
备份什么?恢复的需求决定需要备份什么。最简单的策略是只备份数据和表定义,但这是一个最低的要求。在生产环境中恢复数据库一般需要更多的工作。下面是MySQL备份需要考虑的几点:1.非显著数据不要忘记哪些容易被忽略的数据:例如m二进制日志和InnoDB事务日志。2.代码现在的MySQL服务器可以存储许多代码,例如触发器和存储过程。如果备份了mysql数据库,那么大部分这类代码也备份了,但入股哦需要还原单个业务数据库会比较麻烦,因为这个数据库中的部分\"数据\",例如存储过程,实际是存放在mysql数据库中的3.复制配置如果恢复一个涉及复制关系的服务器,应该备份所有与复制相关的文件,例如二进制日志、中继日志、日志索引文件和.info文件。至少应该包含SHOW MASTER STATUS和/或SHOW SLAVE STATUS的输出。执行FLUSH LOGS也非常有好处,可以让MySQL从一个新的二进制日志开始。从日志文件的开头做基于故障时间点的恢复要比从中间更容易4.服务器配置假设要从一个实际的灾难中恢复,比如说,地震过后在一个新数据中心构建服务器,如果备份中包含服务器配置,你一定会喜出望外5.选定的操作系统文件对于服务器配置来说,备份中对生产服务器至关重要的任何外部配置,都十分重要。在UNIX服务器上,这可能包括cron任务、用户和组的配置、管理脚本,以及sudo规则这些建议在许多场景下会被当作\"备份一切\"。然而,如果有大量的数据,这样做的开销将非常高,如何做备份,需要更加明智的考虑。特别是,可能需要在不同备份中备份不同的数据。例如,可以单独地备份数据、二进制日志和操作系统及系统配置
3.复制从备库中备份最大的好处是可以不干扰主库,避免在主库上增加额外的负载。这是一个建立备库的好理由,即使不需要用它做负载均衡或高可用。如果钱是个问题,也可以把备份用的备库用于其他用户,例如报表服务——只要不对其做写操作,以确保备份不会修改数据。备库不必只用于备份的目的;只需要在下次备份时能及时跟上主库,即使有时因作为其他用途导致复制延时也没有关系。当从备库备份时,应该保存所有关于复制进程的信息,例如备库相对于主库的位置。这对于很多情况都非常有用:克隆新的备库,重新应用二进制日志到主库上以获得指定时间点的恢复,将备库提升为主库等。如果停止备库,需要确保没有打开的临时表,因为它们可能导致不饿能重启备库。故意将一个备库延时一段时间对于某些灾难场景非常有用。例如延时复制一小时,当一个不期望的语句在主库上运行后,将有一个小时的时间观察到并在中继日志中方之前停掉复制。然后可以将备库提升为主库,重放少量的日志事件,跳过错误的语句。这比后面要讨论的指定事件点的恢复技术可能要快得多。Percona Toolkit中pt-slave-delay工具可以帮助实现这个方案。备库可能与主库数据不完全一样。许多人认为备库是主库完全一样的副本,但以经验,主库与备库数据不匹配是很常见,并且MySQL没有方法检测这个问题。检测这个问题的唯一方法是使用Percona Toolkit中的pt-table-checksum之类的工具。拥有一个复制的备库可鞥在诸如主库的硬盘烧坏时提供帮助,但却不能提供保证。复制不是备份。
安全地清除老的二进制日志。需要决定日志的过期策略以防止磁盘被二进制日志写满。日志增长多大取决于负载和日志格式(基于行的日志回导致更大的日志记录)。我们建议,如果可能,只要日志有用就尽可能保留。保留日志对于设置复制、分析服务器负载、审计和从上次全备按时间点进行恢复,都很有帮助。当决定想要保留日志多久时,应该考虑这些需求。一个常见的设置是使用expire_log_days变量来告诉MySQL定期清理日志。这个变量直到MySQL4.1才引入;在此之前的版本,必须手动清理二进制日志。因此,你可能看到一些用类似下面的cron项来删除老的二进制日志的建议。```sh0 0 * * * /usr/bin/ find /var/log/mysql -mtime +N -name \"mysql-bin.[0-9]*\" | xargs rm```尽管这是在MySQL 4.1之前清除日志的唯一办法,但在新版本中不要这么做!用rm删除日志会导致mysql-bin.index状态文件与磁盘上的文件不一致,有些语句,例如SHOW MASTER LOGS可能会受到影响而悄然失败。手动修改mysql-bin.index文件也不会修复这个问题。应该用类似下面的cron命令```sh0 0 * * * /usr/bin/mysql -e \"PURGE MASTER LOGS BEFORE CURRENT_DATE - INTERVAL N DAY\"```expire_logs_days设置在服务器启动或MySQL切换二进制日志时生效,因此,如果二进制日志从没有增长和切换,服务器不会清除老条目。此设置时通过查看日志的修改事件而不是内容来决定哪个文件需要被清除。
管理和备份二进制日志。服务器的二进制日志时备份的最重要因素之一。它们对于基于时间点的恢复是必需的,并且通常比数据要小,所以更容易进行频繁的备份。如果有某个时间点的数据备份和所有从那时以后的二进制日志,就可以重放自上次全备以来的二进制日志并\"前滚\"所有的变更。MySQL复制也使用二进制日志。因此备份和恢复的策略经常和复制配置相互影响。二进制日志很\"特别\"。如果丢失了数据,你一定不希望同时丢失了二进制日志。为了让这种情况发送的几率减少到最小,可以在不同的卷上保存数据和二进制日志,即使在LVM下生成二进制日志的快照,也是可以的。为了额外的安全起见,可以将它们保存在SAN上,或用DRBD复制到另外一个设备上。经常备份二进制日志是个好主意。如果不能承受丢失超过30分钟数据的价值,至少需要每30分钟就备份一次。也可以用一个配置--log_slave_update的只读备库,这样可以获得额外的安全性。备库上日志位置与主库不匹配,但找到恢复时正确的位置并不难。最后,MySQL5.6版本的mysqlbinlog有一个非常方便的特性,可以连接到服务器上来实时对二进制日志做镜像,比起运行一个mysqld实例要简单和轻便,它与老版本时向后兼容的。
生成逻辑备份。对于逻辑备份,首先要意识到的是它们并不是以同样方式创建的。实际上有两种类型的逻辑备份:SQL导出和符号分割文件。1.SQL导出SQL导出是很多人所熟悉的,因为它们是mysqldump默认的方式。例如,用默认选项导出一个小表将产生如下输出:可以是如下命令:```bashmysqldump -u root -p sakila actor > myactor.sql```导出文件包含表结构和数据,均以有效的SQL命令形式写出。文件以设置MySQL各种选项的注释开始。这些要么是为了使恢复工作更高效,要么使因为兼容性和正确性。接下来可以看到表结构,然后是数据,最后,脚本重置在导出开始时变更的选项。导出的输出对于还原操作来说是可执行的。这很方便。但mysqldump默认选项读与生成一个巨大的备份却不是太适合。mysqldump不是生成SQL逻辑备份的唯一工具。例如,也可以用mydumper或phpMyAdmin工具来创建。想指出的是,不是某一个特定的工具有多大的问题,而是做SQL逻辑备份本身就有一些缺点。下面是主要问题点:1.Schema和数据存储在一起如果想从单个文件恢复这样做会非常方便,但如果只想恢复一个表或指向恢复数据就很困难了。可以通过导出两次的方法来环节这个问题——一次只导出数据,另外一次只导出Schema——但还会有下一个麻烦2.巨大的SQL语句服务器分析和执行SQL语句的工作量非常大,所以加载数据时会非常慢3.单个巨大的文件大部分文本编辑器不能编辑巨大的或者包含非常长的行的文件。尽管有时候可以用命令行的流编辑器——例如sed或grep——来抽出需要的数据,但保持文件小型化仍然是更合适的4.逻辑备份的成本很高比起逻辑备份这种从存储引擎中读取数据然后通过客户端/服务器协议发送结果集的方式,还有其他更高效的方式这些限制意味着SQL导出在表变大时可能变得不可用。不过,还有另外一个选择;导出数据到符号分割的文件中
LVM快照是如何工作的。LVM使用写时复制(copy-on-write)的技术来创建快照——例如,对整个卷的某个瞬间的逻辑副本,这与数据库中的MVCC有点像,不同的是它只保留一个老的数据版本。注意,我们说的不是物理副本。逻辑副本看起来好像包含了创建快照卷中所有的数据,但实际上一开始快照是不包含数据的。相比复制数据到快照中,LVM只是简单地标记创建快照的时间点,然后对该快照请求读数据时,实际上是从原始卷中读取的。因此初始的复制基本上是一个瞬间就能完成的操作,不管创建快照的卷有多大。当原始卷中某些数据有变化时,LVM在任何变更写入之前,会复制受影响的块到快照预留的区域中。LVM不保留数据的多个\"老版本\",因此对原始卷中变更块的额外写入并不需要对快照做其他更多的工作。换句话说,对每个块只有第一次写入才会导致写时复制到预留的区域。现在,在快照中请求这些块时,LVM会从复制块中而不是从原始卷中读取。所以,可以继续看到快照中相同时间点的数据而不需要阻塞任何原始卷。如图所示。快照会在/dev目录下创建一个新的逻辑卷,可以像挂载其他设备一样挂载它。理论上讲,这种技术可以对一个非常大的卷做快照,而只需要非常少的物理存储空间,但是,必须设置足够的空间,保证在快照打开时,能够保存所有期望在原始卷上更新的块。如果不预留足够的写时复制空间,当快照用完所有的空间后,设备就会变得不可用。这个影响就像拔出一个外部设备:任何从设备上读的备份工作都会因IO错误而失败
```bashlvsLV:homeVG:vgAttr:-wi-aoLSize:40.00GOrigin Snap%:Move Log Copy%:LV:mysqlVG:vgAttr:-wi-aoLSize:225.00GOrigin Snap%:Move Log Copy%:LV:tmpVG:vgAttr:-wi-aoLSize:10.00GOrigin Snap%:Move Log Copy%:LV:varVG:vgAttr:-wi-aoLSize:10.00GOrigin Snap%:Move Log Copy%:```
先决条件和配置。创建一个快照的消耗几乎微不足道,但还是需要确保系统配置可以让你获取在备份瞬间的所有需要的文件的一致性副本。首先,确保系统满足下面这些条件。1.所有的InnoDB文件(InnoDB的表空间文件和InnoDB的事务日志)必须时在单个逻辑卷(分区)。你需要绝对的时间点一致性,LVM不能为多于一个卷做某个时间点一致的快照。(这是LVM的一个限制;其他一些系统没有这个问题)2.如果需要备份表定义,MySQL数据目录必须在相同的逻辑卷中。如果使用另外一种方法来备份表的帝国一,例如只备份Schema到版本控制系统中,就不需要担心这个问题3.必须在卷组中有足够的空闲空间来创建快照。需要多少取决于负载。当配置系统时,应该留一些未分配的空间以便后面做快照。LVM有卷组的概念,它包含一个或多个逻辑卷。可以按照如下的方式查看系统中的卷组:```bashvgsVG:vg#PV:1#LV:4#SN:0Attr:wz--n-VSize:534.18GVFree:249.18G```输出显示了一个分布在一个物理卷上的卷组,它有四个逻辑卷,大概有250GB空间空闲。入股哦需要,可用vgdisplay命令产生更详细的输出。现在让我们看下系统上的逻辑卷
创建、挂载和删除LVM快照。一条命令就能创建快照。只需要决定快照存放的未知和分配给写时复制的空间大小即可。不要纠结于是否适用比想象中的需求更多的空间。LVM不会马上使用完所有指定的空格键,只是为后续适用预留而已。因此多预留一点空间并没有坏处,除非你必须同时为其他快照预留空间。让我们来练习创建一个快照。我们给它16GB的写时复制空间,名字为backup_mysql.```bashlvcreate --size 16G --snapshot --name backup_mysql /dev/vg/mysq```这里特意命名为backup_mysql卷而不是mysql_backup,是为了避免Tab键自动补全造成误会。这有助于避免因为Tab键自动补全导致突然误删除mysql卷组的可能。我们可以适用lvs看看新创建的卷的状态。快照的属性与原设备不同,而且该输出还显示了一点额外的信息:原始卷组和分配了16GB的写时复制空间目前已经使用了多少。备份对此进行监控是个非常好的主意,可以知道是否会因为设备写满而备份失败。可以交互地监控设备的状态,或使用诸如Nagios这样的监控系统。```bashwathc 'lvs | grep backup'```从前面mount的输出可以看到,mysql卷包含一个文件系统。这意味着快照也同样如此,可以像其他文件系统一样挂载。```bashmkdir /tmp/backupmount /dev/mapper/vg-backup_mysql /tmp/backupls -l /tmp/backup/mysql```这里只是为了联系,因此我们卸载这个快照并用lvremove命令将其删除```bashumount /tmp/backuprmdir /tmp/backuplvremove --force /dev/vg/backup_mysql```
文件系统快照和InnoDB.即使锁住所有的表,InnoDB的后台线程仍会继续工作,因此,即使在创建快照时,仍然可以往文件中写入。并且,由于InnoDB没有执行关闭操作。如果服务器意外断电,快照中InnoDB的文件会和服务器意外掉电后文件的遭遇一样。这不是什么问题,因为InnoDB是个ACID系统。任何时刻(例如快照时),每隔提交的事务要么在InnoDB数据文件中要么在日志文件中。在还原快照后启动MySQL时,InnoDB将运行恢复进程,就像服务器断过电一样。它会查找事务日志中任何提交但没有应用到数据文件中的事务然后应用,因此不会丢失任何事务。这正是要强制InnoDB数据文件和日志文件在一起快照的原因。这也是在备份后需要测试的原因。启动一个MySQL实例,把它指向一个新备份,让InnoDB执行崩溃恢复过程,然后检测所有的表。通过这种方法,就不会备份损坏了却还不知道(文件可能由于任何原因损坏)。这么做的另外一个好处是,维拉i需要从备份中还原时会更快,因为已经在备份上运行过一遍恢复程序了。甚至还可以在将快照复制到备份目的地之前,直接在快照上做上面的操作,但增加一点点额外开销。所以需要确保这是计划内的操作
用于在线备份的LVM快照。现在已经知道如何创建、加载和删除快照,可以使用它们来进行备份了。首先看一下如何在不停止MySQL服务的情况下备份InnoDB数据库,这里需要使用一个全局的读锁。连接MySQL服务器并使用一个全局读锁将表刷到磁盘上,然后获取二进制日志的位置:```sqlmysql>FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS;```记录SHOW MASTER STATUS的输出,确保到MySQL的连接处于打开状态,以使读锁不被释放。然后获取LVM的快照并立刻释放该读锁,可以使用UNLOCK TABLES或直接关闭连接来释放锁。最后,加载快照并赋值文件到备份位置。这种方法最主要的问题是,获取读锁可能需要一点时间,特别时当有许多长时间运行的查询时。当连接等待全局读锁时,所有的查询都将被阻塞,并且不可预测这会持续多久。
备份误区2:\"快照就是备份\".一个快照,不论是LVM快照、ZFS快照,还是SAN快照,都不是实际的备份,因为它不包含数据的完整副本。正因为快照是写时复制的,所以它只包含实际数据和快照发生的时间点的数据之间的差异数据。如果一个没有被修改的块在备份副本时被损坏,那就没有该块的正常副本可以用来恢复,并且备份副本时每隔快照看到的都是相同的损坏的块。可以使用快照来\"冻结\"备份时的数据,但不要把快照当作一个备份
规划LVM备份。LVM快照备份也是有开销的。服务器写到原始卷的越多,引发的额外开销也越多。当服务器随机修改许多不同块时,磁头需要需要自写时复制空间来来回回寻址,并且将数据的老版本写到写时复制空间。从快照中读取也有开销,因为LVM需要从原始卷中读取大部分数据。只有快照创建后修改过的数据从写时复制空间读取;因此,逻辑顺序读取快照数据实际上也可能导致磁头来回移动。所以应该为此规划好快照。快照实际上会导致原始卷和快照都比正常的读/写性能要差——如果使用过多的写时复制空间,性能可能会差很多。这会降低MySQL服务器和复制文件进行备份的性能。我们做了基准测试,发现LVM快照的开销要远高于它本应该有的——我们发现性能最多可能会慢5倍,具体取决于负载和文件系统。再规划备份时要记得这一点。规划中另外一个重要的事情是,为快照分配足够多的空间。我们一般采取下面的方法.1.记住,LVM只需要复制每个修改块到快照一次。MySQL写一个块到原始卷中时,它会复制这个块到快照中,然后对复制的块在例外表中生成一个标记。后续对这个块的写不会产生任何到快照的复制2.如果只使用InnoDB,要考虑InnoDB是如何写数据的。InnoDB实际需要对数据写两遍,至少一半的InnoDB的写IO会到双写缓冲区(doublewrite buffer)、日志文件,以及其他磁盘上相对小的区域中。这部分会多次重用相同的磁盘块,因此第一次时对快照有影响,但写过一次以后就不会对快照带来写压力3.接下来,相对于反复修改同样的数据,需要评估有多少IO需要写入到那些还没有复制到快照写时复制的空间的块中,对评估的结果要保留足够的余量4.使用vmstat或iostat来手机服务器每秒写多少块的统计信息5.衡量(或评估)复制备份到其他地方需要多久。换言之,需要在复制起见保持LVM快照打开多长时间。假设评估出有一半的写会导致往快照的写时复制空间的写操作,并且服务器支持10MB/s的写入。如果需要一个小时(3600s)将快照复制到另外一个服务器上,那么将需要1/2 * 10MB * 3600 即18GB的快照空间。考虑到容错,还要增加一些额外的空间。有时候当快照打开时,很容易计算会有多少数据发生改变。让我们看个例子。BoardReader论坛搜索引擎每隔存储节点有1TB的InnoDB表。但是,我们知道最大的开销时加载心数据。每天新增近10GB的数据,因此50GB的快照空间应该完全足够。然而这样来评估不总是正确的。假设在某个时间点,有一个长时间运行的依次修改每个分片的ALTER TABLE操作,它会修改超过50GB的数据;在这个时间点,就不能做备份的操作。为了避免这样的问题,可以稍后再创建快照,因为创建快照后会导致一个负载的高峰。
快照的其他用途和替代方案。快照有更多的其他用途,而不仅仅用于备份。例如,之前提到,在一个有潜在危险的动作之前生成一个\"检查点\"会有帮助。有些系统允许将快照提升为原文件系统,这使得回滚到生成快照的时间点的数据非常简单。文件系统快照不是取得数据瞬间副本的唯一方法。另一个选择是RAID分裂;举个例子,如果有一个三磁盘的软RAID镜像,就可以从该RAID组中移出来一个磁盘单独加载。这样做没有写时复制的代价,并且需要时将此类\"快照\"提升为主副本的操作也很简单。不错,如果要将磁盘加回到RAID集合,就必须重新进行同步,当然,天下没有免费的午餐。
文件系统快照。文件系统快照是一种非常好的在线备份方法。支持快照的文件系统能够瞬间创建用来备份的内容一致的镜像。支持快照的文件系统和设备包括FreeBSD的文件系统、ZFS文件系统、GNU/Linux的逻辑卷管理(LVM)、以及许多的SAN系统和文件存储解决方案,例如NetApp存储。不要把快照和备份相混淆。创建快照是减少必须持有锁的时间的一个简单方法:释放锁后,必须复制文件到备份中。事实上,有些时候甚至可以创建InnoDB快照而不需要锁定。我们将要展示两种使用LVM来对InnoDB文件系统做备份的方法,可以选择最小化锁或零锁的方案。快照对于特别用途的备份是一个非常好的方法。一个例子是在升级过程中遇到有问题而回退的情况。可以在升级前创建一个镜像,这样如果升级有问题,只需要回滚到该镜像。可以对任何不确定和有风险的操作都这么做,例如对一个巨大的表做变更(需要多少时间是未知的)
备份数据。大多时候,生成备份有好的也有差的方法——有时候显而易见的方法并不是好方法。一个有用的技巧时应该最大化利用网络、磁盘和CPU的能力以尽可能快地完成备份。这是一个需要不断取平衡的事情,必须通过实验以找到\"最佳平衡点\"
还原物理备份后启动MySQL.在启动正在恢复的MySQL服务器之前,还有些步骤要做,首先,最重要且最容易忘记的事情,是在启动MySQL服务器之前检查服务器的配置,确保恢复的文件有正确的归属和权限。这些属性必须完全正确,否则MySQL可能无法启动。这些属性因系统的不同而不同,因此要仔细检查是否和之前做的记录温和。一般都需要mysql用户和组拥有这些文件和目录,并且只有这个用户和组拥有可读/写权限。建议观察MySQL启动时的错误日志。在UNIX类系统上,可以如下观察文件。```bashtail -f /var/log/mysql/mysql.err```注意错误日志的准确位置会有所不同。一旦开始监测文件,就额可以启动MySQL服务器并监测错误。如果一切进展顺利,MySQL启动后就有一个恢复好的数据库服务器了。观察错误日志对于新的MySQL版本更为重要。老版本在InnoDB有错时不会启动,但新版本不管怎样都会启动,而只是让InnoDB失效。即使服务器看起来启动没有任何问题,也应该对每隔数据库运行SHOW TABLE STATUS来再次检测错误日志
(sql文件)
(符号分隔样式)加载符号分隔文件。如果是通过SELECT INTO OUTFILE导出的符号分隔文件,可以使用LOAD DATA INFILE通过相同的参数来加载。也可以用mysqlimport,这是LOAD DATA INFILE的一个包装。这种方式依赖命名约定决定从哪里加载一个文件的数据。我们希望你导出了Schema,而不仅是数据。如果是这样,那应该是一个SQL导出,就可以使用上一节中描述的技术来加载。使用LOAD DATA FILE有一个非常好的优化技巧。LOAD DATA INFILE必须直接从文本文件中读取,因此,如果是压缩文件很多人会在加载前先解压缩,这是非常慢的磁盘密集型操作。然而,在支持FIFO\"命名管道\"文件的系统如GNU/Linux上,对这种操作有个很好的方法。首先,创建一个命名管道并将解压缩数据流到它里面。```bashmkfifo /tmp/backup/default/sakila/payment.fifochmod 666 /tmp/backup/default/sakila/payment.fifogunzip -c /tmp/backup/default/sakila/payment.txt.gz > /tmp/backup/default/sakila/payment.fifo```注意到我们使用了一个大于号字符(>)来重定向解压缩输出到payment.fifo文件中——而不是在不同程序之间创建匿名管道的管道符号。管道会等待,直到其他程序打开它并从另外一段读取数据。简单一点说,MySQL服务器可以从管道中读取解压缩后的数据,就像其他文件一样。如果可能,不要忘记尽调二进制日志。```sqlmysql>SET SQL_LOG_BIN = 0; -- Optional > LOAD DATA INFILE 'tmp/backup/defualt/sakila/payment/fifo' > INTO TABLE sakila.payment;```一旦MySQL加载完数据,gunzip就会退出,然后可以删除该命令管道。在MySQL命令行客户端使用SOURCE命令加载压缩的文件也可以使用此技术。Percona Toolkit中的pt-fifo-split程序还可以帮助分块加载大文件,而不是在单个大事务中操作,这样效率更高
还原逻辑备份。如果还原的是逻辑备份而不是物理备份,则与使用操作系统简单地复制文件到适当位置的方式不同,需要使用MySQL服务器本身来加载数据到表中。在加载导出文件之前,应该先花一点时间考虑文件有多大,需要多久加载完,以及在启动之前还需要做什么事情,例如通知用户或禁用掉部分应用。禁掉二进制日志也是个好主意,除非需要将还原操作复制到备库:服务器加载一个巨大的导出文件的代价很高,并且写二进制日志会增加更多的(可能没有必要的)开销。加载巨大的文件对于一些存储引擎也有影响。例如,在单个事务中加载100GB数据到InnoDB就不是个好想法,因为巨大的回滚段将会导致问题。应该以可控大小的块来加载,并且逐个提交事务。有两种类型的逻辑备份,所以相应地有两种类型的还原操作。
基于时间点的恢复。对MySQL做基于时间点的恢复常见的方法是还原最近一次全备份,然后从那个时间点开始重放二进制日志(有时较\"前滚恢复\")。只要有二进制日志,就可以恢复到任何希望的时间点。甚至可以不太费力地恢复单个数据库。主要的缺点是二进制日志重放可能会是一个很慢的过程。它大体上等同于复制。如果有一个备库,并且已经测量到SQL线程的利用率有多高,那么对重放二进制日志会有多快就会心里有数了。例如,,如果SQL线程约有50%被利用,则恢复一周二进制日志的工作可能在三到四天内完成。一个典型场景是对有害的语句的结果做回滚操作,例如DROP TABLE。让我们看一个简化的例子,看只有MyISAM表的情况下该如何做。假如是在半夜,备份任务在运行与下面所列相当的语句,复制数据库到同一服务器上的其他地方。```sqlmysql> FLUSH TABLES WITH READ LOCK; > server1# cp -a /var/lib/mysql/sakila /backup/sakila;mysql> FLUSH LOGS; > server1# mysql -e \"SHOW MASTER STATUS\" --vertical > /backup/master.infomysql> UNLOCK TABLES;```然后,假设有人在晚些时间运行下列语句.```sqlmysql>USE sakila;mysql>DROP TABLE sakila.payment;```为了便于说明,我们先假设可以单独地恢复这个数据库(即此库中地表不涉及跨库查询)。再假设是直到后来出问题才意识到这个有问题地语句。目标是恢复数据库中除了有问题地语句之外所有发生地事务。也就是说,其他表已经做的所有修改都必须保持,包括有问题的语句运行之后的修改。这并不是很难做到。首先,停掉MySQL以阻止更多的修改,然后从备份中仅恢复sakila数据库。```bashserver1# /etc/init.d/mysql stopserver1# mv /var/lib/mysql/sakila /var lib/mysql/sakila.tmpserver1# cp -a /backup/sakila /var/lib/mysql```再到运行的服务器的my.cnf中添加如下配置以禁止正常的连接```cskip-networkingsocket=/tmp/mysql_recover.sock```现在可以安全地启动服务器了。```bashserver1# /etc/init.d/mysql start```下一个任务是从二进制日志中分出需要重放和忽略的语句。事发时,自半夜的备份依赖服务器只创建了一个二进制日志。我们可以用grep来检查二进制日志文件以找到问题语句```bashserver1# mysqlbinlog --database=sakila /var/log/mysql/mysql-bin.000215 | grep -B3 -i 'drop table sakila.payment'```我们可以看到,想忽略的语句在日志文件中的某个位置,下一个语句的位置是多少。可以用下面的命令重放二进制日志直到某个位置,然后从某个位置继续```bashserver1# mysqlbinlog --database=sakila /var/log/mysql/mysql-bin.000215 --stop-position=352 | mysql -uroot -pserver1# mysqlbinlog --database=sakila /var/log/mysql/mysql-bin.000215 --start-position=429 | mysql -uroot -p```接下来要做的是检测数据以确保没问题,然后关闭服务器并撤销对my.cnf的改变,最后重启服务器
InnoDB崩溃恢复。InnoDB在每次启动时都会检测数据和日志文件,以确认是否需要执行恢复过程。而且InnoDB的恢复过程与前面的讨论不是一回事。它并不是恢复备份的数据;而是根据日志文件将事务应用到数据文件,将未提交的变更从数据文件中回滚。精确地描述InnoDB如何进行恢复工作,这有点太过复杂。我们要关注的焦点是当InnoDB有严重问题时如何实际执行恢复。大部分情况下InnoDB可以很好地解决问题。除非MySQL有Bug或硬件问题,否则不需要做任何非常规的事情,哪怕时服务器意外地断电。InnoDB会在启动时执行正常的恢复,然后一切就正常了,在日志文件中,可以看到如下信息。```sqlInnoDB Doing recovery :scanned up to log sequence number 0 40817239InnoDB: Starting an apply batch of log records to the database...```InnoDB会在日志文件中输出恢复进度的百分比信息。有些人说直到整个过程完成才能看到这些信息。耐心点,这个恢复过程是急不来的。如果心急而杀掉进程并重启,只会导致需要更长的恢复事件。如果服务器硬件有严重问题,例如内存或磁盘损坏,或遇到了MySQL或InnoDB的Bug,可能就不得不介入,这是要么进行强制恢复,要么阻止正常恢复发生。
从备份中恢复。如何恢复数据取决于是怎么备份的。可能需要以下部分或全部步骤。1.停止MySQL服务器2.记录服务器的配置和文件权限3.将数据从备份中移到MySQL数据目录4.改变配置5.改变文件权限6.以限制访问模式重启服务器,等待完成启动7.载入逻辑备份文件8.检查和重放二进制日志9.检测已经还原的数据10.以完全权限重启服务器如果有机会使用文件的当前版本,就不要用备份中的文件来代替。例如,如果备份包含二进制日志,并且需要重放这些日志来做基于时间点的恢复,那么不要把当前二进制日志用备份中的老的副本替代。如果有需要,可以将其重命名或移动到其他地方。在恢复过程中,保证MySQL除了恢复进程外不接受其他访问,这一点往往比较重要。我们喜欢以--skip-networking和--socket=/tmp/mysql_recover.sock选项来启动MySQL.以确保它对于已经存在的应用不可访问,直到我们检测完并重新提供服务。这对于按块加载的逻辑备份的恢复来说尤其重要
MySQL Enterprise Backup这个工具之前叫作InnoDB Hot Backup或ibbackup,是从Oracle购买的MySQL Enterprise中的一部分。使用此工具备份不需要停止MySQL,也不需要设置锁或中断正常的数据库活动(但是会对服务器造成一些额外的负载)。它支持类似压缩备份、增量备份和到其他服务器的流备份的特性。这是MySQL\"官方\"的备份工具。
Percona XtraBackupPercona XtraBackup与MySQL Enterprise Backup在很多方面都非常类似,但它是开源并且免费的。除了核心备份工具外,还有一个用Perl写的封装脚本,可以提供更多高级功能。它支持类似流、增量、压缩和多线程(并行)备份操作。也有许多特别的功能,用以降低在高负载的系统上备份的影响。Percona XtraBackup的工作方式是在后台线程不断追踪InnoDB日志文件尾部,然后复制InnoDB数据文件。这是个轻量级侵入过程,依靠特别的检测机制确保复制的数据是一致的。当所有的数据文件被复制完,日志复制线程就结束了。结果是在不同的时间点的所有数据的副本。然后可以使用InnoDB崩溃恢复代码应用事务日志,已达到所有数据文件一致的状态。这一步叫作准备过程。一旦准备好,备份就会完全一致,并且包含文件复制过程最后时间点已经提交的事务。一切都在MySQL外部完成,因此不需要以任何方式连接或访问MySQL.包装脚本包含通过复制备份到原位置的方式进行恢复的能力。还有Lachalan Mulcahy的XtraBack Manager项目
mylvmbackupLenz Grimmer的mylvmbackup是一个Perl脚本,它通过LVM快照帮助MySQL自动备份。此工具首先获取全局读锁,创建快照,释放锁。然后通过tar压缩数据并移除快照。它通过备份时的时间戳命名压缩包。它还有几个高级选项,但总的来说,这是一个执行LVM备份的非常简单明了的工具
Zmanda Recovery Manager适用于MySQL的Zmanda Recover Manger 或ZRM,有免费(GPL)和商业两种版本。企业版提供基于网页图形接口的控制台,用来配置、备份、验证、恢复、报告和调度。开源的版本包含了所有核心功能,但缺少一些额外的特性,例如基于网页的控制台。正如其名,ZRM实际上是一个备份和恢复管理器,而并非单一工具。它封装了自由的基于标准工具和技术,例如mysqldump、LVM快照和Percona XtraBackup等之上的功能。它将许多冗长的备份和恢复工作进行自动化。
mydumper几名MySQL现在和之前的工程师利用他们多年的经验创建了mydumper,用来替代mysqldump。这是一个多线程(并发)的备份和还原MySQL和Drizzle的工具集,有许多很好的特性。大概有许多人会发现多线程备份和还原的速度是这个工具最吸引人的特色。尽管我们知道有些人在生产环境中使用,但我们还没有在任何产品中使用的经验
备份与恢复
概述。MySQL服务器发行包中并没有包含针对许多常用任务的工具,例如监控服务器或比较不同服务器间数据的工具。幸运的是,Oracle的商业版提供了一些扩展工具,并且MySQL活跃的开源社区和第三方公司也提供了一系列的工具,降低了自己\"重复发明轮子\"的需要
接口工具。接口工具可以帮助运行查询,创建表和用户,以及执行其他日常任务等。接下来讲简单介绍一些用于此用途的最流行的工具。一般可以用SQL查询或命令做所有这些或其中大部分的工作——我们这里讨论的工具只是更为方便,可帮助避免错误和加快工作。1.MySQL WorkbechMySQL Workbench是一个一站式的工具,可以完成例如管理服务器、写查询、开发存储过程,以及Schema设计图相关的工作。可以通过一个插件接口来编写自己的工具并集成到这个工作平台上,有一些Python脚本和库就使用了这个插件接口。MySQL Workbench有社区版和商业版两个版本,商业版只是增加了一些高级特性。免费版对于大部分需要早已足够了2.SQLyogSQLyog是MySQL最流行的可视化工具之一,有许多很好的特性。它与MySQL Workbench是同级别的工具,但两个工具都有一些对方没有的特性。SQLyog只能在微软的Windows下使用,拥有全部特性的版本需要付费,但有限制功能免费版本3.phpMyAdminphpMyAdmin是一个流行的管理工具,运行在Web服务器上,并且提供基于浏览器的MySQL服务器访问接口。尽管基于浏览器的访问有时很好,但phpMyAdmin是个大而复杂的工具,曾被指责有许多安全的问题。对此要格外小心。我们建议不要安装在任何可以从互联网访问的地方。4.AdminerAdminer是个基于浏览器的安全的轻量级管理工具,它与phpMyAdmin同类。其他开发者将其定位为phpMyAdmin的更好的替代品。尽管它看起来更安全,但仍建议安装在任何可功开访问的地方时要谨慎
命令行工具集。MySQL包含了一些命令行工具集,例如mysqladmin和mysqlcheck。这些在MySQL手册上都有提及和记录。MySQL社区同样创建了大量高质量的工具包,并有很好的文档支撑这些使用工具集。1.Percona ToolkitPercona Toolkit是MySQL管理员必备的工具包。它源自Baron早期的工具包Maatkit和Aspersa,很多人认为这两个工具应该是正式的MySQL部署必须强制要求使用的。Percona Toolkit包括许多针对类似日志分析、复制完整性检测、数据同步、模式和索引分析、查询建议和数据归档目的的工具。如果刚开始接触MySQL,建议首先学习这些关键的工具:pt-mysql-summary、pt-table-checksum、pt-table-sync和pt-query-digest2。Maatkit and Aspersa这两个工具约从2006年以某种形式出现,两者都被认为是MySQL用户的基本工具。它们现在已经并入Percona Toolkit3.The openark kitShlomi Noach的openark kit包含了可以用来做一系列管理任务的Python脚本4.MySQL Workbench工具集中的某些工具可以作为单独的Python脚本使用除了这些工具外,还有一系列没有太正式包装和维护的工具。许多杰出的MySQL社区成员时不时地贡献工具,其中大多数托管在它们自己的网站或MySQL Forge上。可以通过时不时地查看Planet MySQL博客聚合器获取大量地西悉尼,但不幸的是这些工具没有一个集中地目录
SQL实用集。服务器本身也内置有一系列免费的附加组件和实用集可以使用;其中一些确实相当强大。1.common_schemaShlomi Noach的common_schema享目是一套针对服务器脚本话和管理的强大的代码和视图。common_schema对于MySQL好比jQuery对于JavaScript.2.mysql-sr-libGiuseppe Maxia为MySQL创建了一个存储过程的代码库,3.MySQL UDF仓库Roland Bouman建立了一个MySQL自定义函数的收藏馆。4.MySQL Forge在MySQL Forge上,可以找到上百个社区共享的程序、脚本、代码片段、实用集和技巧及陷阱
开源的监控工具。下面是一些最受欢迎的开源集成监控系统。1.NagiosNagios也许是开源世界中最流行的问题检测和告警系统。它周期性检测监控的服务器并将结果与默认或自定义的阈值相比较。如果结果超出了限制,Nagio会执行某个程序并且(或)把问题的告警发给某些人。Nagios的通信和告警系统可以将其发送到不同的位置,并且对计划内的宕机可以特殊处理。Nagios同样理解服务之间的依赖,因此,如果是因为中间的路由层或者主机本身宕机导致MySQL实例不可用,Nagios不会发送告警来烦你。Nagios能将任何一个可执行文件以插件形式运行,只要给予其正确参数就可得到z正确输出。因此,Nagios插件在多种语言中都存在,例如shell、Perl、Python、Ruby和其他脚本语言。就算找不到一个能真正满足你需求的插件,自己创建一个也很简单。一个插件只需要接收b标准的参数,以一个合适的状态退出,然后选择性地打印Nagios捕获的输出。然而,Nagios也有一些严重的缺点。即使你很了解它,也仍然难以维护。它将所有配置保存在文件而不是数据库中。文件有一个特别容易出错的语法,当系统增长和发展时,修改配置文件就很费事。Nagios可扩展性并不好;你可以很容易地写出监控插件,但这也是你能够做的一切。最后,它的图形化、趋势化和可视化能力都有限。Nagios将一些性能和其他数据存储到MySQL服务器中,一般从中生成图形,但并不像其他一些系统那么灵活。因为不同\"政见\"的原因,使得上面所有的问题继续变得更糟。因为或真实、或臆测的涉及代码、参与者的问题,Nagios至少分化除了两个分支。两个分支的名字分别是Opsview和Icinga。它们比Nagios更受到人们的青睐。2.ZabbitxZabbix是一个同时支持监控和指标收集的完整系统。例如,它将所有配置和其他数据存储到数据库而不是配置文件中。它存储了比Nagios更多的数据类型,因而可以得到更好的趋势和历史报表。其网络画图和可视能力也比Nagios更强,配置更简单,更灵活,且更具可扩展性3.ZenossZenoss是用Python写的,拥有一个基于浏览器的用户界面,使用了Ajax,这使它更快和更高效。它可以自动发现网络上的资源,并将监控、告警、趋势、绘图和记录历史数据整合到一个统一的工具中。Zenoss默认使用SNMP来从远程服务器上收集数据,但也可以使用SSH,并且支持Nagios插件4.Hyperic HQHyperic HQ是一个基于Java的监控系统,比起同级别的其他大部分系统,它更称得上是企业级监控.像Zenoss一样,它可以自动发现网络上的资源和支持Nagios插件,但它的逻辑阻止和架构不同,有点\"笨重\"5.OpenNMSOpenNMS也是用Java开发,有一个活跃的开发社区。它拥有常规的特性,例如监控和告警,但同样也增加了绘图和趋势功能,它的目标是高性能、可扩展、自动化和灵活。像Hyperic一样,它也致力于为大型和关键系统做企业级监控6.Groundwokr Open SourceGroundwork Open Source用一个可移植的接口把Nagios和其他几个工具整合到了一个系统中,对于这个工具最好的描述是:如果你是Nagios、Cacti和其他几个工具方面的专家,并且花了很多时间将它们整合在一起,那很可能你是在闭门造车.
监测工具。以过往的经验来看,大多数MySQL商店需要提供两种类型的监测工具:健康监测工具——检测到异常时告警——和为趋势、诊断、问题排查、容量规划等记录指标的工具。大多数系统仅在这些任务中的一个方面做得很好,而不能两者兼顾。更不幸的是,有十几种工具可选,使得评估和选择一款适合的工具非常耗时。许多监控系统不是专门为MySQL服务器设计。它们是通用系统,用于周期性地检测许多类型的资源,从及其到路由再到软件(例如MySQL)。它们一般有某些类型的插件架构,经常会伴随有一些MySQL插件。一般会在专用服务器上安装监控系统来监测其他服务器。如果是监控重要的资源,它很快会变成架构中至关重要的一部分,因此可能需要采取额外的步骤,例如做监控系统本身的灾备。
MySQL用户工具
MySQL
0 条评论
回复 删除
下一页