干货:鲜为人用的MySQL高级特性与玩法!

上一篇文章《万字总结:学习MySQL优化原理,这一篇就够了!》文末给你们留有两个开放的问题:html

 

  1. 有很是多的程序员在分享时都会抛出这样一个观点:尽量不要使用存储过程,存储过程很是不容易维护,也会增长使用成本,应该把业务逻辑放到客户端。既然客户端都能干这些事,那为何还要存储过程?前端

  2. JOIN自己也挺方便的,直接查询就行了,为何还须要视图呢?mysql

 

本文会试着回答这两个问题,但愿能给你一些参考。git

 

如今能够思考一个问题,若是数据量很是大的状况下,您根据业务选择了合适的字段,精心设计了表和索引,还仔细检查了全部的SQL,并确认已经没什么问题,但性能仍然不能知足您的要求,该怎么办?还有其它优化策略吗?答案是确定的。接下来继续和您讨论一些经常使用的MySQL高级特性以及其背后的工做原理。程序员

 

分区表github

 

合理地使用索引能够极大提高MySQL的查询性能,但若是单表数据量达到必定的程度,索引就没法起做用,由于在数据量超大的状况下,除非覆盖索引,因回表查询会产生大量的随机I/O,数据库的响应时间可能会达到不可接受的程度。并且索引维护(磁盘空间、I/O操做)的代价也会很是大。算法

 

所以,当单表数据量达到必定程度时(在MySQL4.x时代,MyISAM存储引擎业内公认的性能拐点是500W行,MySQL5.x时代的性能拐点则为1KW - 2KW行级别,具体需根据实际状况测试),为了提高性能,最为经常使用的方法就是分表。sql

 

分表的策略能够是垂直拆分(好比:不一样订单状态的订单拆分到不一样的表),也能够是水平拆分(好比:按月将订单拆分到不一样表)。但总的来讲,分表能够看做是从业务角度来解决大数据量问题,它在必定程度上能够提高性能,但也大大提高了编码的复杂度,有过这种经历的同窗可能深有体会。数据库

 

在业务层分表大大增长了编码的复杂程度,并且处理数据库的相关代码会大量散落在应用各处,维护困难。那是否能够将分表的逻辑抽象出来,统一处理,这样业务层就不用关心底层是否分表,只须要专一在业务便可。答案固然是确定的,目前有很是多的数据库中间件均可以屏蔽分表后的细节,让业务层像查询单表同样查询分表后的数据。若是再将抽象的逻辑下移到数据库的服务层,就是咱们今天要讲的分区表。编程

 

分区能够看做是从技术层面解决大数据问题的有效方法,简单理解能够认为是MySQL底层帮咱们实现分表,分区表是一个独立的逻辑表,底层由多个物理子表组成。存储引擎管理分区的各个底层表和管理普通表同样(全部底层表必须使用相同的存储引擎),分区表的索引也是在各个底层表上各自加上一个彻底相同的索引。从存储引擎的角度来看,底层表和普通表没有任何不一样,存储引擎也无须知道。在执行查询时,优化器会根据分区的定义过滤那些没有咱们须要数据的分区,这样查询就无需扫描全部分区,只须要查找包含须要数据的分区就能够了。

 

为了更好地理解分区表,咱们从一个示例入手:一张订单表,数据量大概有10TB,如何设计才能使性能达到最优?

 

首先能够确定的是,由于数据量巨大,确定不能走全表扫描。使用索引的话,你会发现数据并非按照想要的方式汇集,并且会产生大量的碎片,最终会致使一个查询产生成千上万的随机I/O,应用随之僵死。因此须要选择一些更粗粒度而且消耗更少的方式来检索数据。好比先根据索引找到一大块数据,而后再在这块数据上顺序扫描。

 

这正是分区要作的事情,理解分区时还能够将其看成索引的最初形态,以代价很是小的方式定位到须要的数据在哪一片“区域”,在这片“区域”中,你能够顺序扫描,能够建索引,还能够将数据都缓存在内存中。由于分区无须额外的数据结构记录每一个分区有哪些数据,因此其代价很是低。只须要一个简单的表达式就能够表达每一个分区存放的是什么数据。

 

对表分区,能够在建立表时,使用以下语句:

 

 

分区子句中可使用各类函数,但表达式的返回值必须是一个肯定的整数,且不能是一个常数。MySQL还支持一些其它分区,好比键值、哈希、列表分区,但在生产环境中不多见到。在MySQL5.5之后可使用RANGE COLUMNS类型分区,这样即便是基于时间分区,也无需再将其转化成一个整数。

 

下面简单看看分区表上的各类操做逻辑:

 

  • SELECT:当查询一个分区表时,分区层先打开并锁住全部的底层表,优化器先判断是否能够过滤部分分区,而后在调用对应的存储引擎接口访问各个分区的数据。

 

  • INSERT:当插入一条记录时,分区层先打开并锁住全部的底层表,而后肯定哪一个分区接收这条记录,再将记录写入对应的底层表,DELETE操做与其相似。

 

  • UPDATE:当更新一条数据时,分区层先打开并锁住全部的底层表,而后肯定数据对应的分区,而后取出数据并更新,再判断更新后的数据应该存放到哪一个分区,最后对底层表进行写入操做,并对原数据所在的底层表进行删除操做。

 

有些操做是支持条件过滤的。例如,当删除一条记录时,MySQL须要先找到这条记录,若是WHERE条件刚好和分区表达式匹配,就能够将全部不包含这条记录的分区都过滤掉,这对UPDATE语句一样有效。若是是INSERT操做,自己就只命中一个分区,其它分区都会被过滤。

 

虽然每一个操做都会 “先打开并锁住全部的底层表”,但这并非说分区表在处理过程当中是锁住全表的。若是存储引擎可以本身实现行级锁,例如InnoDB,则会在分区层释放对应表锁。这个加锁和解锁的操做过程与普通InnoDB上的查询相似。

 

在使用分区表时,为了保证大数据量的可扩展性,通常有两个策略:

 

  • 策略一:全量扫描数据,不用索引。即只要可以根据WHERE条件将须要查询的数据限制在少数分区中,效率是不错的。

 

  • 策略二:索引数据,分离热点。若是数据有明显的“热点”,并且除了这部分数据,其它数据不多被访问到,那么能够将这部分热点数据单独存放在一个分区中,让这个分区的数据可以有机会都缓存在内存中。这样查询就能够只访问一个很小的分区表,可以使用索引,也可以有效地利用缓存。

 

分区表的优势是优化器能够根据分区函数来过滤一些分区,但很重要的一点是要在WHERE条件中带入分区列,有时即便看似多余的也要带上,这样就可让优化器可以过滤掉无须访问的分区,若是没有这些条件,MySQL就须要让对应的存储引擎访问这个表的全部分区,若是表很是大的话,就可能会很是慢。

 

上面两个分区策略基于两个很是重要的前提:查询都可以过滤掉不少额外的分区、分区自己并不会带来不少额外的代价。而这两个前提在某些场景下是有问题的,好比:

 

1. NULL值会使分区过滤无效


假设按照PARTITION BY RANGE YEAR(order_date)分区,那么全部order_date为NULL或者非法值时,记录都会被存放到第一个分区。因此WHERE order_date BETWEEN '2017-05-01' AND ‘2017-05-31’,这个查询会检查两个分区,而不是咱们认为的2017年这个分区(会额外的检查第一个分区),是由于YEAR()在接收非法值时会返回NULL。若是第一个分区的数据量很是大,并且使用全表扫描的策略时,代价会很是大。为了解决这个问题,咱们能够建立一个无用的分区,好比:PARTITION p_null values less than (0)。若是插入的数据都是有效的话,第一个分区就是空的。

 

在MySQL5.5之后就不须要这个技巧了,由于能够直接使用列自己而不是基于列的函数进行分区:PARTITION BY RANGE COLUMNS(order_date)。直接使用这个语法可避免这个问题。

 

2. 分区列和索引列不匹配


当分区列和索引列不匹配时,可能会致使查询没法进行分区过滤,除非每一个查询条件中都包含分区列。假设在列a上定义了索引,而在列b上进行分区。由于每一个分区都有其独立的索引,因此在扫描列b上的索引就须要扫描每个分区内对应的索引,固然这种速度不会太慢,可是可以跳过不匹配的分区确定会更好。这个问题看起来很容易避免,但须要注意一种状况就是,关联查询。若是分区表是关联顺序的第2张表,而且关联使用的索引与分区条件并不匹配,那么关联时对第一张表中符合条件的每一行都须要访问并搜索第二张表的全部分区(关联查询原理,可点击连接查看第一篇文章)

 

3. 选择分区的成本可能很高


分区有不少种类型,不一样类型的分区实现方式也不一样,因此它们的性能也不尽相同,尤为是范围分区,在确认这一行属于哪一个分区时会扫描全部的分区定义,这样的线性扫描效率并不高,因此随着分区数的增加,成本会愈来愈高。特别是在批量插入数据时,因为每条记录在插入前,都须要确认其属于哪个分区,若是分区数太大,会形成插入性能的急剧降低。所以有必要限制分区数量,但也不用太过担忧,对于大多数系统,100个左右的分区是没有问题的。

 

4. 打开并锁住全部底层表的成本在某些时候会很高


前面说过,打开并锁住全部底层表并不会对性能有太大的影响,但在某些状况下,好比只须要查询主键,那么锁住的成本相对于主键的查询来讲,成本就略高。

 

5. 维护分区的成本可能会很高


新增和删除分区的速度都很快,可是修改分区会形成数据的复制,这与ALTER TABLE的原理相似,须要先建立一个历史分区,而后将数据复制到其中,最后删除原分区。所以,设计数据库时,考虑业务的增加须要,合理的建立分区表是一个很是好的习惯。在MySQL5.6之后的版本可使用ALTER TABLE EXCHAGE PARTITION语句来修改分区,其性能会有很大提高。

 

分区表还有一些其余限制,好比全部的底层表必须使用相同的存储引擎,某些存储引擎也不支持分区。分区通常应用于一台服务器上,但一台服务器的物理资源老是有限的,当数据达到这个极限时,即便分区,性能也可能会很低,因此这个时候分库是必须的。但无论是分区、分库仍是分表,它们的思想都是同样的,你们能够好好体会下。

 

视图

 

对于一些关联表的复杂查询,使用视图有时候会大大简化问题,所以在许多场合下均可以看到视图的身影,但视图真如咱们所想那样简单吗?它和直接使用JOIN的SQL语句有何区别?视图背后的原理又了解多少?

 

视图自己是一个虚拟表,不存听任何数据,查询视图的数据集由其余表生成。MySQL底层经过两种算法来实现视图:临时表算法(TEMPTABLE)和合并算法(MERGE)。所谓临时表算法就是将SELECT语句的结果存放到临时表中,当须要访问视图的时候,直接访问这个临时表便可。而合并算法则是重写包含视图的查询,将视图定义的SQL直接包含进查询SQL中。经过两个简单的示例来体会两个算法的差别,建立以下视图:

 

 

现要从未支付订单中查询购买者为csc的订单,可使用以下查询:

 

 

使用临时表来模拟视图:

 

 

使用合并算法将视图定义的SQL合并进查询SQL后的样子:

 

 

MySQL能够嵌套定义视图,即在一个视图上在定义另外一个视图,能够在EXPLAN EXTENDED以后使用SHOW WARNINGS来查看使用视图的查询重写后的结果。若是采用临时表算法实现的视图,EXPLAIN中会显示为派生表(DERIVED),注意EXPLAIN时须要实际执行并产生临时表,因此有可能会很慢。

 

明显地,临时表上没有任何索引,并且优化器也很难优化临时表上的查询,所以,若有可能,尽可能使用合并算法会有更好的性能。那么问题来了:合并算法(相似于直接查询)有更好的性能,为何还要使用视图?

 

首先视图能够简化应用上层的操做,让应用更专一于其所关心的数据。其次,视图可以对敏感数据提供安全保护,好比:对不一样的用户定义不一样的视图,可使敏感数据不出如今不该该看到这些数据的用户视图上;也可使用视图实现基于列的权限控制,而不须要真正的在数据库中建立列权限。再者,视图能够方便系统运维,好比:在重构Schema的时候使用视图,使得在修改视图底层表结构的时候,应用代码还能够继续运行不报错。

 

基于此,使用视图其实更多的是基于业务或者维护成本上的考虑,其自己并不会对性能提高有多大做用(注意:此处只是基于MySQL考虑,其余关系性数据库中视图可能会有更好的性能,好比Oracle和MS SQL Server都支持物化视图,它们都比MySQL视图有更好的性能)。并且使用临时表算法实现的视图,在某些时候性能可能会很是糟糕,好比:

 

 

现要统计每日的收入与支出,有相似于上面的收入表,可使用以下SQL:

 

 

这个查询中,MySQL先执行视图的SQL,生成临时表,而后再将sale_per_day表和临时表进行关联。这里WHERE字句中的BETWEEN条件并不能下推到视图中,于是视图在建立时,会将全部的数据放到临时表中,而不是一个月数据,而且这个临时表也不会有索引。

 

固然这个示例中的临时表数据不会太大,毕竟日期的数量不会太多,但仍然要考虑生成临时表的性能(若是costs表数据过大,GROUP BY有可能会比较慢)。并且本示例中索引也不是问题,经过上一篇咱们知道,若是MySQL将临时表做为关联顺序中的第一张表,仍然可使用sale_per_day中的索引。但若是是对两个视图作关联的话,优化器就没有任何索引可使用,这时就须要严格测试应用的性能是否知足需求。

 

咱们不多会在实际业务场景中去更新视图,所以印象中,视图是不能更新的。但实际上,在某些状况下,视图是能够更新的。可更新视图是指经过更新这个视图来更新视图涉及的相关表,只要指定了合适的条件,就能够更新、删除甚至是向视图中插入数据。经过上文的了解,不难推断出:更新视图的实质就是更新视图关联的表,将建立视图的WHERE子句转化为UPDATE语句的WHERE子句,只有使用合并算法的视图才能更新,而且更新的列必须来自同一个表中。回顾上文建立视图的SQL语句,其中有一句:WITH CHECK OPTION,其做用就是表示经过视图更新的行,都必须符合视图自己的WHERE条件定义,不能更新视图定义列之外的列,不然就会抛出check option failed错误。

 

视图还有一个容易形成误解的地方:“对于一些简单的查询,视图会使用合并算法,而对于一些比较复杂的查询,视图就会使用临时表算法。”但实际上,视图的实现算法是视图自己的属性决定的,跟做用在视图上的SQL没有任何关系。那何时视图采用临时表算法,何时采用合并算法呢?通常来讲,只要原表记录和视图中的记录没法创建一一映射的关系时,MySQL都将使用临时表算法来实现视图。好比建立视图的SQL中包含GROUP BY、DISTINCT、UNION、聚合函数、子查询的时候,视图都将采用临时表算法(这些规则在之后的版本中,可能会发生改变,具体请参考官方手册)。

 

相比于其它关系型数据库的视图,MySQL的视图在功能上会弱不少,好比Oracle和MS SQL Server都支持物化视图。物化视图是指将视图结果数据存放在一个能够查询的表中,并按期从原始表中刷新数据到这张表中,这张表和普通物理表同样,能够建立索引、主键约束等等,性能相比于临时表会有质的提高。但遗憾的是MySQL目前并不支持物化视图,固然MySQL也不支持在视图中建立索引。

 

存储过程与触发器

 

回到第二个问题,有很是多的人在分享时都会抛出这样一个观点:尽量不要使用存储过程,存储过程很是不容易维护,也会增长使用成本,应该把业务逻辑放到客户端。既然客户端都能干这些事,那为何还要存储过程?

 

若是有深刻了解过存储过程,就会发现存储过程并无你们描述的那么不堪。我曾经经历过一些重度使用存储过程的产品,依赖到什么程度呢?就这么说吧,上层的应用基本上只处理交互与动效的逻辑,全部的业务逻辑,甚至是参数的校验均在存储过程当中实现。曾经有出现过一个超大的存储过程,其文件大小达到惊人的80K,可想而知,其业务逻辑有多么复杂。在大多数人眼中,这样的技术架构简直有点不可理喻,但实际上这款产品很是成功。

 

其成功的缘由在必定程度上得益于存储过程的优势,因为业务层代码没有任何侵入业务的代码,在不改变前端展现效果的同时,能够很是快速的修复BUG、开发新功能。因为这款产品须要部署在客户的私有环境上,快速响应客户的需求就变得尤其重要,正是得益于这种架构,能够在客户出现问题或者提出新需求时,快速响应,极端状况下,咱们能够在1小时内修复客户遇到的问题。正是这种快速响应机制,让咱们得到大量的客户。

 

固然存储过程还有其余的优势,好比,能够很是方便的加密存储过程代码,而不用担忧应用部署到私有环境形成源代码泄露、能够像调试其余应用程序同样调试存储过程、能够设定存储过程的使用权限来保证数据安全等等。一切都很是美好,但咱们的产品是基于MS SQL Server实现的,其能够经过T-SQL很是方便的实现复杂的业务逻辑。你能够把T-SQL看作是一门编程语言,其包含SQL的全部功能,还具有流程控制、批处理、定时任务等能力,你甚至能够用其来解析XML数据。关于T-SQL的更多信息能够参考MSDN,主流的关系型数据库目前只有MS SQL Server支持T-SQL,所以,MySQL并不具有上文描述的一些能力,好比,MySQL的存储过程调试很是不方便(固然能够经过付费软件来得到很好的支持)。

 

除此以外,MySQL存储过程还有一些其余的限制:

 

  • 优化器没法评估存储过程的执行成本。

  • 每一个链接都有独立的存储过程执行计划缓存,若是有多个链接须要调用同一个存储过程,将会浪费缓存空间来缓存相同的执行计划。

 

所以,在MySQL中使用存储过程并非一个太好的策略,特别是在一些大数据、高并发的场景下,将复杂的逻辑交给上层应用实现,能够很是方便的扩展已有资源以便得到更高的计算能力。并且对于熟悉的编程语言,其可读性会比存储过程更好一些,也更加灵活。不过,在某些场景下,若是存储过程比其余实现会快不少,而且是一些较小的操做,能够适当考虑使用存储过程。

 

和存储过程相似的,还有触发器,触发器可让你在执行INSERT、UPDATE和DELETE时,执行一些特定的操做。在MySQL中能够选择在SQL执行以前触发仍是在SQL执行后触发。触发器通常用于实现一些强制的限制,这些限制若是在应用程序中实现会让业务代码变得很是复杂,并且它也能够减小客户端与服务器之间的通讯。MySQL触发器的实现很是简单,因此功能很是有限,若是你在其余数据库产品中已经重度依赖触发器,那么在使用MySQL触发器时候须要注意,由于MySQL触发器的表现和预想的不一致。

 

首先对一张表的每个事件,最多只能定义一个触发器,并且它只支持“基于行的触发”,也就是触发器始终是针对一条记录的,而不是针对整个SQL语句。若是是批量更新的话,效率可能会很低。其次,触发器能够掩盖服务器本质工做,一个简单的SQL语句背后,由于触发器,可能包含了不少看不见的工做。再者,触发器出现问题时很难排查。最后,触发器并不必定能保证原子性,好比MyISAM引擎下触发器执行失败了,也不能回滚。在InnoDB表上的触发器是在同一个事务中执行完成的,因此她们的执行是原子的,原操做和触发器操做会同时失败或者成功。

 

虽然触发器有这么多限制,但它仍有适用的场景,好比,当你须要记录MySQL数据的变动日志,这时触发器就很是方便了。

 

外键约束

 

目前在大多数互联网项目,特别是在大数据的场景下,已经不建议使用外键了,主要是考虑到外键的使用成本:外键一般要求每次修改数据时都要在另一张表中执行一次查找操做。在InnoDB存储引擎中会强制外键使用索引,但在大数据的状况下,仍然不能忽略外键检查带来的开销,特别是当外键的选择性很低时,会致使一个很是大且选择性低的索引。

 

若是向子表中插入一条记录,外键约束会让InnoDB检查对应的父表的记录,也就须要对父表对应记录进行加锁操做,来确保这条记录不会在这个事务完成之时就被删除了。这会致使额外的锁等待,甚至会致使一些死锁。

 

高并发场景下,数据库很容易成为性能瓶颈,天然而然的就但愿数据库能够水平扩展,这时就须要把数据的一致性控制放到应用层,也就是让应用服务器能够承担压力,这种状况下,数据库层面就不能使用外键。

 

所以,当不用过多考虑数据库的性能问题时,好比一些内部项目或传统行业项目(其使用人数有限,并且数据量通常不会太大),使用外键是一个不错的选择,毕竟想要确保相关表始终有一致的数据,使用外键要比在应用程序中检查一致性方便简单许多,此外,外键在相关数据的删除和更新操做上也会比在应用中要高效。

 

绑定变量

 

可能你们看到“绑定变量”这个词时,会有一点陌生,换个说法可能会熟悉一些:PreparedStatement。绑定变量的SQL,使用问号标记能够接收参数的位置,当真正须要执行具体查询的时候,则使用具体的数值代替这些问号,好比:

 

 

为何要使用绑定变量?众所周知的缘由是能够预先编译,减小SQL注入的风险,除了这些呢? 

 

当建立一个绑定变量SQL时,客户端向服务器发送了一个SQL语句原型,服务器收到这个SQL语句的框架后,解析并存储这个SQL语句的部分执行计划,返回给客户端一个SQL语句处理句柄,今后之后,客户端经过向服务器发送各个问号的取值和这个句柄来执行一个具体查询,这样就能够更高效地执行大量重复语句。缘由以下:

 

  • 服务器只须要解析一次SQL语句。

  • 服务器某些优化器的优化工做也只须要作一次,由于MySQL会缓存部分执行计划。

  • 通讯中仅仅发送的是参数,而不是整个语句,网络开销也会更小,并且以二进制发送参数和句柄要比发送ASCII文本的效率更高。

 

须要注意的是,MySQL并非总能缓存执行计划,若是某些执行计划须要根据参入的参数来计算时,MySQL就没法缓存这部分执行计划。

 

使用绑定变量的最大陷阱是:你知道其原理,但不知道它是如何实现的。有时候,很难解释以下3种绑定变量类型之间的区别:

 

  • 客户端模拟的绑定变量:客户端的驱动程序接收一个带参数的SQL,再将参数的值带入其中,最后将完整的查询发送到服务器。

 

  • 服务器绑定变量:客户端使用特殊的二进制协议将带参数的SQL语句发送到服务器端,而后使用二进制协议将具体的参数值发送给服务器并执行。

 

  • SQL接口的绑定变量:客户端先发送一个带参数的SQL语句到服务器端,这相似于使用prepared的SQL语句,而后发送设置的参数,最后在发送execute指令来执行SQL,全部这些都是用普通的文本传输协议。

 

好比某些不支持预编译的JDBC驱动,在调用connection.prepareStatement(sql)时,并不会把SQL语句发送给数据库作预处理,而是等到调用executeQuery方法时才把整个语句发送到服务器,这种方式就相似于第1种状况。所以,在程序中使用绑定变量时,理解你使用的驱动经过哪一种方式来实现就显得颇有必要。延伸开来讲,对于本身使用的框架、开源工具,不该仅仅停留在会使用这个层面,有时间能够深刻了解其原理和实现,否则有可能被骗了都不知道哦。

 

用户自定义函数

 

MySQL自己内置了很是多的函数,好比SUM、Count、AVG等等,可实际应用中,咱们经常须要更多。大多数状况下,更强大的功能都是在应用层面实现,但实际上MySQL也提供了机会让咱们能够去扩展MySQL函数,这就是用户自定义函数(user-defined function),也称为:UDF。须要注意UDF与存储过程和经过SQL建立函数的区别,存储过程只能使用SQL来编写,而UDF没有这个限制,可使用支持C语言调用约定的任何编程语言来实现。

 

UDF必须事先编译好并动态连接到服务器上,这种平台相关性使得UDF在不少方面都很强大,UDF速度很是快,并且能够访问大量操做系统功能,还可使用大量库函数。若是须要一个MySQL不支持的统计聚合函数,而且没法使用存储过程来实现,并且还想不一样的语言均可以调用,那么UDF是不错的选择,至少不须要每种语言都来实现相同的逻辑。

 

所谓能力越大,责任也就越大,UDF中的一个错误可能直接让服务器崩溃,甚至扰乱服务器的内存和数据,所以,使用时须要注意其潜在的风险。在MySQL版本升级时也须要注意,由于你可能须要从新编译或者修改这些UDF,以便让它们能在新版本中工做。

 

这里有一个简单的示例来展现如何建立UDF:将结果集转化为JSON,具体的代码请参考:lib_mysqludf_json

(连接 https://github.com/mysqludf/lib_mysqludf_json

 

 

其大体的实现流程:使用C语言实现逻辑 -> 编译成.so文件 -> 建立函数 -> 使用函数。UDF在实际工做中可能不多使用,但做为开发者的咱们,了解这么一款强大的工具,在解决棘手问题时,也让咱们有了更多的选择。

 

字符集

 

关于字符集大多数人的第一印象可能就是:数据库字符集尽可能使用UTF8,由于UTF8字符集是目前最适合于实现多种不一样字符集之间的转换的字符集,能够最大程度上避免乱码问题,也能够方便之后的数据迁移。But why?

 

字符集是指一种从二进制编码到某类字符符号的映射,能够参考如何使用一个字节来表示英文字母。校对规则是指一组用于某个字符集的排序规则,即采用何种规则对某类字符进行排序。MySQL每一类编码字符都有其对应的字符集和校对规则。MySQL对各类字符集的支持都很是完善,但同时也带来一些复杂性,某些场景下甚至会有一些性能牺牲。

 

一种字符集可能对应多种校对规则,且都有一个默认校对规则,那在MySQL中是如何使用字符集的?在MySQL中能够经过两种方式设置字符集:建立对象时设置默认值、客户端与服务器通讯时显式设置。

 

MySQL采用“阶梯”式的方式来设定字符集默认值,每一个数据库,每张表都有本身的默认值,它们逐层继承,最终最靠底层的默认设置将影响你建立的对象。好比,建立数据库时,将根据服务器上的character_set_server来设置数据库的默认字符集。一样的道理,根据database的字符集来指定库中全部表的字符集,无论是对数据库,仍是表和列,只有当它们没有显式指定字符集时,默认字符集才会起做用。

 

当客户端与服务器通讯时,它们可使用不一样的字符集,这时候服务器将进行必要的转换工做。当客户端向服务器发送请求时,数据以character_set_client设置的字符集进行编码;而当服务器收到客户端的SQL或者数据时,会按照character_set_connection设置的字符集进行转换;当服务器将要进行增删改查等操做前会再次将数据转换成character_set_database(数据库采用的字符集,没有单独配置即便用默认配置,具体参考上文),最后当服务器返回数据或者错误信息时,则将数据按character_set_result设置的字符集进行编码。服务器端可使用SET CHARACTER SET来改变上面的配置,客户端也能够根据对应的API来改变字符集配置。客户端和服务器端都使用正确的字符集才能避免在通讯中出现问题。

 

一、如何选择字符集?

 

在考虑使用何种字符集时,最主要的衡量因素是存储的内容,在可以知足存储内容的前提下,尽可能使用较小的字符集。由于更小的字符集意味着更少空间占用、以及更高的网络传输效率,也间接提升了系统的性能。若是存储的内容是英文字符等拉丁语系字符的话,那么使用默认的latin1字符集彻底没有问题,若是须要存储汉字、俄文、阿拉伯语等非拉丁语系字符,则建议使用UTF8字符集。固然不一样字符在使用UTF8字符集所占用的空间是不一样的,好比英文字符在UTF8字符集中只使用一个字节,而一个汉字则占用3个字节。

 

除了字符集,校对规则也是咱们须要考虑的问题。对于校对规则,通常来讲只须要考虑是否以大小写敏感的方式比较字符串或者是否用字符串编码的二进制来比较大小,其对应的校对规则的后缀分别是_cs、_ci和_bin。大小写敏感和二进制校对规则的不一样之处在于,二进制校对规则直接使用字符的字节进行比较,而大小写敏感的校对规则在多字节字符集时,如德语,有更复杂的比较规则。举个简单的例子,UTF8字符集对应校对规则有三种:

 

  • utf8_bin将字符串中的每个字符用二进制数据存储,区分大小写。

  • utf8_general_ci不区分大小写,ci为case insensitive的缩写,即大小写不敏感。

  • utf8_general_cs区分大小写,cs为case sensitive的缩写,即大小写敏感。

 

好比,建立一张表,使用UTF8编码,且大小写敏感时,可使用以下语句:

 

 

所以,在项目中直接使用UTF8字符集是彻底没有问题的,但须要记住的是不要在一个数据库中使用多个不一样的字符集,不一样字符集之间的不兼容问题很难缠。有时候,看起来一切正常,可是当某个特殊字符出现时,一切操做都会出错,并且你很难发现错误的缘由。

 

二、字符集对数据库性能有影响吗?

 

某些字符集和校对规则可能会须要多个的CPU操做,可能会消耗更多的内存和存储空间,这点在前文已经说过。特别是在同一个数据库中使用不一样的字符集,形成的影响可能会更大。

 

不一样字符集和校对规则之间的转换可能会带来额外的系统开销,好比,数据表sales在buyer字段上有索引,则能够加速下面的ORDER BY操做:

 

 

只有当SQL查询中排序要求的字符集与服务器数据的字符集相同时,才能使用索引进行排序。你可能会说,这不是废话吗?其实否则,MySQL是能够单独指定排序时使用的校对规则的,好比:

 

 

当使用两个字符集不一样的列来关联两张表时,MySQL会尝试转换其中一个列的字符集。这和在数据列外面封装一个函数同样,会让MySQL没法使用这个列上的索引。关于MySQL字符集还有一些坑,但在实际应用场景中遇到的字符集问题,其实不是特别的多,因此就此打住。

 

结语

 

MySQL还有一些其它高级特性,但在大多数场景下咱们不多会使用,所以这里也没有讨论,但多了解一些老是好的,至少在须要时,你知道有这样一个东西。咱们老是会认为本身所学的知识就像碎片同样不成体系,又找不到解决办法,那你有没有想过也许是碎片不够多的缘故?点太少,天然不能链接成线,线太少,天然不能结成网。于是,没有其它办法,保持好奇心、多学习、多积累,量变总有一天会质变,写在这儿,与你们共勉吧。

 

前面我写的一些文章里面会有提到过,架构设计是一种平衡的艺术,其实质应该是一种妥协,是对现有资源的一种妥协。有时候咱们会不自觉陷入某一个点,好比为了追求数据的扩展性,不少人一上来就开始分库分表,而后把应用搞得很是复杂,到最后表里尚未装满数据,项目就已经死了。因此在资源有限或者将来还不可知的状况下,尽可能使用数据库、语言自己的特性来完成相应的工做,是否是会更好一点。解决大数据问题,也不仅是分库分表,你还应该还能够想到分区;有些业务即便在分布式环境下也不必定非要在业务层完成,合理使用存储过程和触发器,也许会让你更轻松。

 

参考资料

Baron Scbwartz 等著;宁海元 周振兴等译;高性能MySQL(第三版); 电子工业出版社, 2013

 

经做者赞成受权转载,来源:简书

原文地址:http://dbaplus.cn/news-11-1568-1.html

相关文章
相关标签/搜索