《高性能MySQL》

        之前我对于MySQL的认识只是停留在会用的层面,虽然上一个游戏用的数据库就是MySQL,但也只是满足了使用需求,没怎么深入了解。这段时间读了《高性能MySQL》这本书,深入了解了MySQL并且学习到了如何更好的运用数据库。这本书的阅读基础是建立在读者对数据库有一定的使用经验上的。全书一直围绕着如何用好MySQL的这一主题来展开各个模块的内容,其中包括MySQL的底层机制、如何结合需求建表、各种数据类型的特性、索引的使用、查询语句优化、如何测试数据库、如何备份还原数据库以及各种工具的使用等等。下面会列一些比较重要的知识点作为记录,供大家参考。

MySQL架构及存储引擎

        MySQL最与众不同的是它的存储引擎架构,这种架构的设计将查询处理以及其他系统任务和数据的存储分离,这种处理和存储分离的设计可以在使用时根据性能、特性以及其他需求来选择不同的存储引擎。

        上图线框内的部分包括了MySQL除存储数据以外的大部分功能,例如处理客户端连接,授权认证,查询解析,分析,优化,缓存以及所有的内置函数(日期,时间,数字,加密等),还有存储过程,触发器,视图等。这一层对于所有MySQL的使用都是一样的,但下一层的存储引擎就可以有多种选择,而这些不同的存储引擎支持的隔离级别不一样,效率也不一样,锁的粒度也不一样,用户可以根据自己的需求选择不同的存储引擎使用。

        存储引擎是表级别的,对于不同的表可以使用不同的存储引擎,但不建议混用。最常用的存储引擎有InnoDB与MyISAM,MySQL5.1及之前版本,默认的存储引擎是MyISAM,但之后的版本默认存储引擎是InnoDB。InnoDB相比于MyISAM,它基于聚簇索引建立,支持行级锁,采用MVCC支持高并发并实现了四个标准的隔离级别,支持崩溃后安全修复,无论是效率还是安全性,在大部分需求下都会比MyISAM工作得更好。并不是说MyISAM一无是处,但在没有在明确清楚两者的区别前,不要轻易采用MyISAM。

        除了上述两个存储引擎之外,还有很多其他的存储引擎可以选择。但这些存储引擎都只有在特定情况下才会工作得比较好,例如适合做日志插入、查询的Archive引擎,直接存储成逗号分割文件的CSV引擎,所有数据都在内存中的Memory引擎等。除非真正明白这些引擎的特点并且确实贴合需求,不然不要轻易采用。

数据类型与表的设计

        整数:TINYINT(8位)、SMALLINT(16位)、MEDIUMINT(24位)、INT(32位)、BIGINT(64位),UNSIGNED属性表示是否可以为负数。值得注意的是,MySQL可以为整数类型指定宽度,例如INT(11),对于大多数应用这是没有意义的,它不会限制值的合法范围,只是规定了MySQL的一些交互工具用来显示字符的个数。对于存储和计算来说,INT(1)和INT(20)是相同的。

        实数:FLOAT(32位)和DOUBLE(64位)是标准的浮点运算(近似计算、非精确),DECIMAL用于精确的实数存储,例如DECIMAL(18,9)表示整数位9位,小数位9位,一共18位。CPU本身是不支持DECIMAL的直接计算的,MySQL自身实现了DECIMAL的高精度计算,但效率会比浮点数差。值得注意的是,DECIMAL的存储与计算是MySQL自身实现支持的,在跟客户端传输和客户端使用上要自行处理。

        字符串:VARCHAR类型用于存储可变长度的字符串,它仅使用必要的空间存储,然后使用额外的1到2个字节记录字符串长度,节省了存储空间,对计算也有帮助。与之对应的CHAR类型用于存储定长的字符串,CHAR总是根据定义的字符串长度来分配空间,会删除末尾的空格,使用时要注意。

        数据块:BLOB与TEXT都是为存储很大的数据而设计的,BLOB采用二进制来存储,TEXT采用字符方式来存储。BLOB没有排序规则与字符集,TEXT有。使用InnoDB存储引擎时,太大的VARCHAR会转换成BLOB来存储,而BLOB与TEXT太大时,InnoDB又会专门为其分配一块存储区域来存储,行中只存一个1-4字节的指针来索引,非常灵活。

        日期:DATETIME8位,存储是的具体的年月日时分秒,精确到秒,与时区无关。TIMESTAMP4位,保存的是从1970年1月1日午夜以来的秒数,与时区有关,并且无法保存1970年前的时间。值得注意的是,如果在多个时区存储或访问数据,TIMESTAMP和DATATIME的行为会很不一样,上层系统要清楚了解。

        关于表的设计要从需求出发,通用的经验有:1.避免过度设计,尽量少的列与尽量少的关联;2.选择合适的数据类型,简单够用的就是好的;3.把握好范式与反范式的度。范式化的设计避免数据冗余,更新更快,存储空间更少,通常都被认为是好的设计,但同时要意识到范式带来的关联同样会影响性能。如果经常用到的查询需求都要关联很多张表来完成的话,这样的设计也不见得是一个好设计。

索引

        索引(key)对于良好的性能非常关键,索引与查询是相对的,如何建立索引要与如何查询数据一齐考虑。通常索引类型有两种,一种是BTree索引,一种是哈希索引,常用的存储引擎(InnoDB、MyISAM)都是采用BTree索引。虽然InnoDB与MyISAM都是采用BTree索引,但他们的试用方式还是不一样的。MyISAM的数据底层是按照插入顺序顺序存储的,BTree索引建立起来后只是保存了指向底层数据的指针。而InnoDB则是把数据按照主键直接保存在BTree中,这就是聚簇索引。聚簇索引的好处就是:1.数据与索引保存在一齐,访问更快;2.使用覆盖索引可以直接试用页节点中的主键值。但聚簇索引也有本身的一些缺点,例如:1.如果插入不是按主键顺序插入,则插入速度会降低,还可能会触发页分裂操作;2.全表扫描会变慢,因为数据是稀疏存储的;3.二级索需要两次BTree查询,因为通过key得到主键后,还需要主键去查最终存储数据的BTree。基于上述所说的情况,使用InnoDB时,如果没有很好的数据聚簇时,定义一个自增长的额外数据作为主键是一个不错的办法。

        评判索引是否适合查询有一个三星标准:索引将相关的记录放到一起获得一星;如果索引中的数据顺序与查询中的排列顺序一致则获得二星;如果索引中的列包含了查询中需要的全部数据则三星。根据这个三星原则其实可以引伸出许多高效使用索引的方法:例如查询中的限制条件(WHERE)尽量有建立索引(1星);例如OrderBy的顺序要与索引列的顺序一致(2星);例如建立覆盖索引把需要查询到的数据都包括在索引中(3星)。

        除了上述所说的使用索引技巧外,还有一些点是用索引时要注意的。一是避免冗余索引,在建立了key(A,B)后,再建立key(A)就是多余的。二是建立索引需要代价的,盲目创建过多索引会影响Insert、Update与Delete等操作的速度。

查询

         优化查询大部分情况下都是要结合索引一齐考虑的,除了索引相关的优化外,还有就是考虑是否访问了大量不必要的数据。例如SELECT * 是否是必须的,排序后是否需要所有数据,如果不是的话加入Limit可以减少排序及传输的数据。还有联表查询时内联,外联与全联的选择。另外延迟关联也是一个减少访问数据量的优化技巧。

        分析查询的方法:EXPLAIN,EXPLAIN的结果有许多,其中type列反映了访问类型。访问类型有很多种,从慢到快的有:全表扫描、索引扫描、范围扫描、唯一索引查询、常数引用等。rows表示MySQL预估需要访问多少行数据来完成查询。Extra表示MySQL能够使用何种方式来应用WHERE条件,性能从高到低有三种:1.在索引中使用WHERE条件来过滤不匹配的记录,这是在存储引擎层完成的;2.使用覆盖扫描来返回记录,这是在MySQL服务层完成的;3.从数据表中返回数据,然后过滤不满足条件的记录,这也是在MySQL服务层完成的,但要先读全部数据,是最慢的。

        MySQL服务层也会帮我们做很多的优化,其中的细节无法在此展开,但其中一个特殊的优化需要我们了解原理后,自行选择是否使用和配置如何使用的,它就是查询缓存。在解析查询语句之前,如果打开了查询缓存,MySQL会先检查这个缓存是否命中,命中就会直接返回缓存的结果,这里要注意的是查询语句是通过哈希查找缓存的,任何字节的不同(符号、大小写、换行等)都会导致缓存查找失败。另外要注意的是,查询缓存是有开销的,打开了缓存的话,每次执行查询前都要先查找是否命中缓存,然后执行完的查询及结果要记录到缓存里,最重要的是缓存对应的表如果数据发生变化就要遍历所有缓存,把变化涉及的缓存去掉。缓存用的空间越大,缓存命中的机会也越大,同时数据变化后要做的清理也会更多。所以是否要开启缓存,缓存设置多大,都是要认真考虑的问题,配置不好可能反而影响效率。

         除了查询本身的优化外,还要考虑查询执行时的系统情况。第一是网络情况,MySQL客户端与服务器之间的通信协议是半双工的,在任何一个时刻,要么客户端向服务器发数据,要么服务器向客户端发数据,不能同时发生的。服务器发送给客户端的数据很多时,会分多个数据包发送,但客户端必须完整的接收完整个返回结果,无法中途停止。第二个是并发情况,如果会有同时对一张表、一行数据竞争读写时,也会有明显的效率问题,通过SHOW FULL PROCESSLIST 命令可以查看当前连接处于什么状态。

高级特性

        分区表:分区表功能实际上就是手动指定一张逻辑表可以如何划分成多张物理子表。使用分区表的好处是,当数据非常庞大、或者存在明显的热点数据时,读写操作都可以只操作某一部分的子表,而不用涉及整个总表。分区表可以通过指定值或者通过指定范围来完成。

        视图:MySQL不支持物化视图,在MySQL中视图就是一张虚拟的表,本身不存放数据,只是把某些表中的数据抽出来汇总。视图有两种实现方法,一是合并算法,二是临时表算法,MySQL会优先使用合并算法创建视图,但如果用到临时表算法的视图则无法被更新。

        外键约束:外键约束就是规定某一个表(A)的一个字段一定要是另一个表(B)的主键,在对A、B表进行了外键约束后,对这个字段的相应操作都会有限制,具体资料不在此展开。需要注意的是,外键约束会带来额外的复杂性和额外的索引消耗,还会增加多表之间的交互,会导致系统中更多的锁与竞争。

        触发器:触发器可以让你在执行INSERT、UPDATE、或者DELETE的时候,执行一些特定的操作。

        事件:事件是指定MySQL在某个时候执行一段SQL代码,或者每隔一个时间间隔执行一段SQL代码。

        还有许多高级特性,例如存储过程、绑定变量、全文索引、XA事务等,不在此展开细说。

基准测试

        基准测试是针对系统设计的一种压力测试,是评估数据库系统在不同压力下工作情况的一种有效方式。考虑系统性能的指标通常有四个:1.吞吐量、2.响应时间或延迟、3.并发性、4.可拓展性。有一些工具可以帮助我们测试MySQL和基于MySQL的系统的性能,例如mysqlslap、MySQL Benchmark Suite、sysbench等,sysbench是至少需要熟悉的。

备份

        备份MySQL的数据有两种方式:逻辑备份与物理备份。逻辑备份是指将数据包含在一种MySQL能够解析的格式中,可以是SQL语句或者一某个符号分割的文本。物理备份指的就是备份MySQL存储在硬盘上的文件。逻辑备份的好处就是通用灵活,备份出来的数据与存储引擎、MySQL版本等都没有关系,可以导回任意的数据库中,通常来说(例如InnoDB存储引擎)文件也会更小。它相比物理备份最大的缺点是从MySQL中导出数据和通过SQL语句将其加载回去的开销。

        除了上述两种备份数据的方式之外,建立一个备库也是一个很好的方法。备库指的是另外用一个独立的数据库定期延时一段时间从主库上同步数据。备库的好处不仅仅在于可以备份数据,还可以在备库上面做一些统计(不需要考虑延迟那部分数据的差异)与查询而不影响主库。

        除了备份数据以外,备份二进制日志也是很有帮助的。如果有某个时间点的数据备份和所有从那时以后的二进制日志,就可以重放自上次全备份以来的二进制日志并前滚所有变更。

        比较好的备份策略是结合上述的三种手段。例如每天凌晨系统空闲时导出逻辑备份数据,同时保存自那个时间点起的二进制日志,然后再设置一个备库每两个小时从主库同步一次。当然具体策略还要根据实际情况来考虑。

最后

        全书的内容还有很多,这里只是总结一下大的知识点以及记录一些自己的看法。想要深入了解MySQL还是要多看看书并在实际项目中运用起来。

Tagged , , . Bookmark the permalink.

Comments are closed.