MySQL系列-- 5. MySQL高级特性

5. MySQL高级特性

5.1 分区表

  • 对用户来讲,分区表是一个独立的逻辑表,可是底层由多个物理字表组成。
    • 实现分区的代码其实是对一组底层表的句柄对象(Handler Object)的封装。
    • 对分区表的请求,都会经过句柄对象转换成对存储对象的接口调用。
    • 因此分区对于SQL层来讲是一个彻底封装底层实现的黑盒子,对应用是透明的,可是对底层的文件系统来看就很容易发现,每个分区表都有一个使用#分隔命名的表文件。
  • MySQL实现分区表的方式——对底层表的封装——意味着索引也是按照分区的字表定义的,而没有全局索引。这和Oracle不一样,在Oracle中能够更加灵活的定义索引和表是否分区
  • MySQL在建立表时使用PARTITION BY子句定义每一个分区存放的数据。在执行查询的时候,优化器会根据分区定义过滤那些没有咱们须要数据的分区,这样查询就无须扫描全部分区——只须要查找包含须要数据的分区。
  • 分区的一个主要目的是将数据按照一个较粗的粒度分在不一样的表中。这样作能够将相关的数据存放在一块儿,另外,若是想一次批量删除整个分区的数据也会变得很方便。
  • 分区起到很是大做用的场景:
    • 表很是大以致于没法所有都放在内存中,或者只在表的最后部分有热点数据,其余均是历史数据。
    • 分区表的数据更容易维护。例如,想批量删除大量数据可使用清除整个分区的方式。另外,还能够对一个独立分区进行优化、检查、修复等操做。
    • 分区表的数据能够分布在不一样的物理设备上,从而高效地利用多个硬件设备。
    • 可使用分区表来避免某些特殊的瓶颈,例如InnoDB的单个索引的互斥访问、ext3文件系统的inode锁竞争等。
    • 若是须要,还能够备份和恢复独立的分区,这在很是大的数据集的场景下效果很是好。
  • 分区表部分比较重要的限制:
    • 一个表最多只能有1024个分区
    • 在MySQL5.1中,分区表达式必须是整数,或者是返回整数的表达式。在MySQL5.5中,某些场景中能够直接使用列来进行分区
    • 若是分区字段中有主键或者惟一索引的列,那么全部主键列和惟一索引列都必须包含进来。
    • 分区表中没法使用外键约束。

5.1.1 分区表的原理

  • 如前所述,分区表由多个相关的底层表实现,这些底层表也是由句柄对象(Handler object)标识,因此能够直接访问各个分区。
    • 存储引擎管理分区的各个底层表和管理普通表同样(全部的底层表都必须使用相同的存储引擎),分区表的索引只是在各个底层表上各自加上一个彻底相同的索引
    • 从存储引擎的角度来看,底层表和一个普通表没有任何不一样,存储引擎也无须直到这是一个普通表仍是一个分区表的一部分。
  • 分区表上的操做:
    • SELECT查询:当查询一个分区表的时候,分区层先打开并锁住全部的底层表,优化器先判断是否能够过滤部分分区,而后再调用对应的存储引擎接口访问各个分区的数据。
    • INSERT操做:当写入一条记录时,分区层先打开并锁住全部的底层表,而后肯定哪一个分区接收这条记录,再将记录写入对应底层表
    • DELETE操做:当删除一条记录,分区层先打开并锁住全部的底层表,而后肯定数据对应的分区,最后对相应底层表进行删除操做。
    • UPDATE操做:当更新一条记录时,分区层先打开并锁住全部的底层表,MySQL先肯定须要更新的记录在哪一个分区,而后取出数据并更新,在判断更新后的数据应该放在哪一个分区,最后对底层表进行写入操做,并对原数据所在的底层表进行删除操做。
  • 有些操做是支持过滤的。
    • 当删除一条记录时,MySQL须要先找到这条记录,若是WHERE条件刚好和分区表达式匹配,就能够将全部不包含这条记录的分区都过滤掉。这对UPDATE语句一样有效。
    • 若是是INSERT操做,则自己就是只命中一个分区,其余分区都会被过滤掉。MySQL先肯定这条操做属于哪一个分区,再将记录写入对应的底层分区表,无须对任何其余分区进行操做。
  • 虽然每一个操做都会”先打开并锁住全部的底层表“,但这并非说分区表在处理的过程当中是锁住全表的。若是存储引擎可以本身实现行级锁,例如InnoDB,则会在分区层释放对应表锁。这个加锁和解锁过程与普通InnoDB上的查询相似。

5.1.2 分区表的类型

  • MySQL支持多种分区表。最多的是根据范围进行分区,每一个分区存储落在某个范围的记录,分区表达式能够是列,也能够包含列的表达式。
  • PARTITION分区子句中可使用各类函数,可是表达式返回的值必须是一个肯定的整数,且不能是一个常数。
  • MySQL还支持键值、哈希和列表分区,这其中有些还支持子分区。在MySQL5.5中,还可使用RANGE COLUMNS类型的分区,这样即便是基于时间的分区也无须再将其转换成一个整数。
    • 按时间分区的InnoDB表,系统经过子分区可下降索引的互斥访问的竞争。最近一年的分区的数据会被很是频繁地访问,这会致使大量的互斥量的竞争。使用哈希子分区能够将数据切成多个小片,大大下降互斥量的竞争问题。
  • 其余的分区技术:
    • 根据键值进行分区,来减小InnoDB的互斥量竞争
    • 使用数学模函数来进行分区,而后将数据轮询放入不一样的分区。例如,能够对日期作模7的运算,或者更简单地使用返回周几的函数,若是只想保留最近几天的数据,这样分区很方便
    • 假设表有一个自增的主键列id,但愿根据时间将最近的热点数据集中存放。那么必须将时间戳包含在主键当中才行,而这和主键自己的意义相矛盾。这种状况下可使用这样的分区表达式来实现一样的目的: HASH(id DIV 1000000),这将为100万数据创建一个分区。一方面实现了当初分区的目的,另外一方面比起使用时间范围分区还避免了一个问题,就是当超过必定阈值时,若是使用时间范围分区就必须新增分区。

5.1.3 如何使用分区表

  • 假设从一个很是大有10亿条记录的表找出最近几个月的数据:
    • 由于数据量巨大,确定不能在每次查询的时候扫描全表
    • 考虑到索引在空间和维护上的消耗,也不但愿使用索引。
      • 除非是覆盖查询,不然服务器须要根据索引扫描的结果回表。
      • 若是真的使用索引,会发现数据不是按照想要的方式汇集的,并且会有大量的碎片产生,致使一个查询产生大量的随机IO
    • 剩下的路:
      • 让全部查询都只在数据表上作顺序扫描
      • 将数据表和索引所有都缓存在内存里
      • 使用分区
  • 理解分区时还能够将其看成索引的最初形态,以代价很是小的方式定位到须要的数据在哪一片区域。在这一片区域中,能够作顺序扫描,能够建索引,能够将数据缓存到内存中,等等。由于分区无须额外的数据结构记录每一个分区有哪些数据——分区不须要精肯定位每条数据的位置,也就无须额外的数据结构——所以代价很是低。
  • 保证大数据量的可扩展性的策略:
    • 全量扫描数据,不要任何索引:
      • 可使用简单的分区方式存放表,不要任何索引,根据分区的规则大体定位须要的数据位置。只要可以使用WHERE条件,将须要的数据限制在少数分区中,则效率是很高的。固然,也须要作一些简单的运算保证查询的响应时间可以知足需求。
      • 使用该策略假设不用将数据彻底放入到内存中,同时还假设须要的数据所有在磁盘上。由于内存相对很小,数据很快会被挤出内存,因此缓存起不了任何做用。
      • 这个策略适用于以正常的方式访问大量数据的时候。
      • 必须将查询须要扫描的分区个数限制在一个很小的数量。
    • 索引数据,并分离热点:
      • 若是数据有明显的“热点”,并且除了这部分数据,其余数据不多被访问到,那么能够将这部分热点数据单独放在一个分区中,让这个分区的数据能有有机会都缓存在内存中。
      • 这样查询就能够只访问一个很小的分区表,可以使用索引,也可以有效地使用缓存。

5.1.4 什么状况下会出问题

上一节介绍的两个分区策略都基于两个很重要的假设:查询可以过滤掉不少额外的分区,分区自己并不会带来不少额外的代价。node

可能会遇到问题的场景:mysql

  • NULL值会使分区过滤无效算法

    分区的表达式的值能够是NULL:第一个分区是一个特殊分区。sql

    • 假设按照PARTITION BY RANGE YEAR(order_date)分区,那么全部order_date为NULL或者是一个很是值的时候,记录都会放到第一个分区。
      • 假设有以下查询:WHERE order_date BETWEEN '2012-01-01' AND '2012-01-31' ,实际上MySQL会检查两个分区,由于YEAR()在接收非法值时会返回NULL而把记录放到第一个分区。
    • 若是第一个分区很是大,特别是当使用"全量扫描数据,不要任何索引"的策略时,代价会很是大。
    • 优化技巧:
      • 建立一个无用的第一个分区,例如:PARTITION p_nulls VALUES LESS THAN (0)。这样即便须要检查第一个分区,代价也很是小
      • (最优)MySQL5.5之后不须要第一个优化技巧,由于能够直接使用列自己而不是基于列的函数进行分区。PARTITION BY RANGE COLUMNS(order_date)
  • 分区列和索引列不匹配:
    • 若是定义的索引列和分区列不匹配,会致使查询没法进行分区过滤。
      • 假设在列a上定义了索引,而在列b上进行分区。由于每一个分区都有其独立的索引,索引扫描列a上的索引就须要扫描每个分区内对应的索引。
    • 应该避免创建和分区列不匹配的索引,除非查询中还同时包含了能够过滤分区的条件:
      • 其余问题:若是在一个关联查询中,分区表在关联顺序中是第二个表,而且关联使用的索引和分区条件不匹配。那么关联时针对第一个表符合条件的每个行,都须要访问并搜索第二个表的全部分区。
  • 选择分区的成本可能很高
    • 不一样类型的分区,因为其实现方式不一样,因此它们的性能也不一样
      • 尤为是范围分区,对于回答“这一行属于哪一个分区”这样的成本可能会很是高,由于服务器须要扫描全部的分区的列表来找到正确的答案。相似这样的线性搜索的效率不高,随着分区数的增加,成本会愈来愈高。
      • 其余的分区类型,如键分区和哈希分区,则没有这样的问题
    • 对大多数系统来讲,100个左右的分区是没有问题的。
  • 打开锁并锁住全部底层表的成本可能很高
    • 当查询访问分区表的时候,MySQL须要打开锁并锁住全部底层表,这是分区表的另外一个开销。
      • 这个操做在分区过滤以前发生,所以没法经过分区过滤下降此开销,而且该开销也和分区类型无关,会影响全部的查询。
    • 对一些自己操做很是快的查询,好比根据主键查找单行,会带来明显的额外开销。
    • 优化技巧:
      • 用批量操做的方式来下降单个操做的此类开销,例如使用批量插入或者LOAD DATA INFILE、一次删除多行数据,等等
      • 限制分区的个数
  • 维护分区的成本可能很高
    • 某些分区维护操做的速度会很是快,例如新增或者删除分区(删除一个大分区可能会很慢,不过这是另外一回事)
    • 而有些操做,如重组分区或者相似ALTER语句的操做,速度会比较慢,由于须要复制数据。

分区实现中的一些其余限制:数据库

  • 全部分区都必须使用相同的存储引擎
  • 分区函数中可使用的函数和表达式也有一些限制
  • 某些存储引擎不支持分区
  • 对于MyISAM的分区表,不能再使用LOAD INDEX INTO CACHE操做
  • 对于MyISAM表,使用分区表时须要打开更多的文件描述符。每个分区对于存储引擎来讲都是一个独立的表,即便分区表只占用一个表缓存条目,文件描述符仍是须要多个。

5.1.5 查询优化

  • 访问分区表,需在WHERE条件中带入分区列,即便有时候看似多余,这样就可让优化器过滤掉无须访问的分区。编程

    • MySQL只能在使用分区函数的列的自己进行比较才能过滤分区,而不能根据表达式的值去过滤分区,即便这个表达式就是分区函数也不行。这和查询中使用独立的列才能使用索引的道理是同样的。缓存

      -- 没法使用分区
      mysql> EXPLAIN PARTITIONS SELECT * FROM sales_by_day WHERE YEAR(day) = 2010\G;
      -- 可以使用分区
      mysql> EXPLAIN PARTITIONS SELECT * FROM sales_by_day
      -> WHERE day BETWEEN '2010-01-01' AND '2010-12-31'\G;复制代码
  • 优化器在处理查询的过程当中老是尽量聪明地去过滤分区。例如,若分区表是关联操做中的第二张表,且关联条件是分区键,MySQL就只会在对应的分区里匹配行。(EXPLAIN没法显示这种状况下的分区过滤,由于这是运行时的分区过滤,而不是查询优化阶段的)安全

5.1.6 合并表

合并表是一种早期的、简单的分区实现,和分区表相比有一些不一样的限制,而且缺少优化。合并表容许用户单独访问各个子表。分区表是将来的发展趋势,合并表是一种将被淘汰的技术,在将来版本可能会被删除,在这里不作过多阐述。服务器

5.2 视图

  • 视图自己是一个虚拟表,不存听任何数据。在使用SQL语句访问视图的时候,它返回的数据是MySQL从其余表生成的。网络

    • 视图和表是在同一个命名空间,MySQL在不少地方和表是一样对待的。不过不能对视图建立触发器,也不能使用DROP TABLE命令删除视图。
    • MySQL5.0版本以后开始引进。
    • 我的理解:视图不会对查询产生任何优化,只是对结果进行一个更好的展现,由于其底层的原理是查询原有的表。某些状况下能够帮助提高性能。
  • 工做原理:

    -- 实现视图最简单的办法是将SELECT语句的结果存放到临时表中。
    mysql> CREATE VIEW Oceania AS
    -> SELECT * FROM Country WHERE Continent = 'Oceania'
    -> WITH CHECK OPTION;
    -- 当须要访问视图的时候,可直接访问这个临时表
    mysql> SELECT Code, Name FROM Oceania WHERE Name = 'Australia';
    -- MySQL使用的并算法:重写含有视图的查询,将视图的定义SQL直接包含进查询的SQL中:
    mysql> SELECT Code, Name FROM Country
    -> WHERE Continent = 'Oceania' AND Name = 'Australia';
    -- MySQL 使用的临时表算法,如下SQL是为展现用的。这样作会有明显的性能问题,优化器也很难优化在这个临时表上的查询。
    mysql> CREATE TEMPORARY TABLE TMP_Oceania_123 AS
    -> SELECT * FROM Country WHERE Continent = 'Oceania';
    mysql> SELECT Code, Name FROM TMP_Oceania_123 WHERE Name = 'Australia';复制代码

    MySQL使用合并算法临时表算法 来处理视图。若是可能,尽量使用合并算法。

    • MySQL甚至能够嵌套地定义视图,也就是在一个视图上再定义另外一个视图。
    • 能够在EXPLAIN EXTENDED以后使用SHOW WARNINGS来查看使用视图的查询重写的结果
      • 若是是采用临时表算法实现的视图,EXPLAIN中的select_type会显示为派生表(DERIVED)。若是产生的底层派生表很大,那么执行EXPLAIN可能会很是慢。由于在5.5及以前的版本中,EXPLAIN是须要实际执行并产生派生表的。

    两种算法的实现细节:

    视图的两种实现
    视图的两种实现

    使用临时表算法实现视图的场景:

    • 视图中包含GROUP BY、DISTINCT、任何聚合函数、UNION、子查询等,只要没法在原表记录和视图记录中创建一一映射的场景。

    视图的实现算法是视图自己的属性,和做用在视图上的查询语句无关。例如,能够为一个基于简单查询的视图制定使用临时表算法:CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM sakila.actor;,这样不管基于执行什么样的查询,视图都会生成一个临时表。

5.2.1 可更新视图

  • 可更新视图(updatable view)是指能够经过更新这个视图来更新视图涉及的相关表
    • 只要指定了合适的条件,就能够更新、删除甚至向视图中写入数据
    • 更新视图的查询也能够是一个关联语句,可是被更新的列必须来自同一个表中
    • 全部使用临时表算法实现的视图都没法被更新
    • CHECK OPTION子句表示任何经过视图更新的行,都必须符合视图自己的WHERE条件定义。因此不能更新视图之外的列
    • MySQL不支持在视图上建任何触发器。某些关系数据库容许在视图上简历INSTEAD OF触发器来精确控制在修改视图数据时作些什么。

5.2.2 视图对性能的影响

  • 某些状况下视图也能够帮助提高性能,并且视图还能够和其余提高性能的方式叠加使用。
  • 提高性能的应用场景:
    • 在重构schema的时候,使得在修改视图底层表结构的时应用代码还可能继续不报错的运行
    • 实现基于列的权限控制,却不须要真正的系统中建立权限,所以没有任何额外的开销
    • 使用伪临时视图:
      • MySQL虽然不能建立只在当前连接中存在的真正的临时视图,可是能够建一个特殊名字的视图,而后在链接结束的时候删除该视图。这样在链接过程当中就能够在FROM子句中使用这个视图,MySQL处理视图和子查询的代码路径彻底不一样,因此它们的性能也不一样
      • 可使用链接ID做为视图名字的一部分来避免冲突。在应用发生崩溃和别的意外致使未清理临时视图的时候,这个技巧使得清理临时视图变得更简单。
    • 使用临时表算法实现的视图,在某些时候性能查询会很糟糕(虽然可能比直接使用等效查询语句更好一点)
      • MySQL以递归的方式执行这类视图,先会执行外层查询,即便外层查询优化器将其优化得很好,可是MySQL优化器可能没法像其余的数据库那样作更多的内外结合的优化。外层查询的WHERE条件没法“下推”到构建视图的临时表的查询中,临时表也没法创建索引。
  • 注意视图背后的复杂性,可能它引用了不少表。若是打算使用视图来提高性能,须要作比较详细的测试。即便是合并算法实现的视图也会有额外开销,并且视图性能很难预测。由于在MySQL的优化器中,视图的代码执行路径也彻底不一样,这部分代码测试还不够全面,可能会有一些隐藏缺陷和问题,因此目前的视图还不是那么成熟。

5.2.3 视图的限制

  • MySQL还不支持物化视图(指视图结果数据存放在一个能够查看的表中,并按期从原始表刷新数据到这个表),也不支持在视图中建立索引。可使用构建缓存表或者汇总表的办法来模拟物化视图和索引

  • MySQL并不会保存视图定义的原始SQL语句,因此不能经过执行SHOW CREATE VIEW后再简单地修改其结果的方式来从新定义视图。

    • 若是打算修改视图,而且无法找到视图的原始的建立语句的话,能够经过使用视图.frm文件最后一行获取一些信息。若是有FILE权限,甚至可直接使用LOAD_FILE()来读取.frm中的视图建立信息,在加上一些字符处理工做。

      mysql> SELECT
      -> REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      -> REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      -> SUBSTRING_INDEX(LOAD_FILE('/var/lib/mysql/world/Oceania.frm'),
      -> '\nsource=', −1),
      -> '\\_','\_'), '\\%','\%'), '\\\\','\\'), '\\Z','\Z'), '\\t','\t'),
      -> '\\r','\r'), '\\n','\n'), '\\b','\b'), '\\\"','\"'), '\\\'','\''),
      -> '\\0','\0')
      -> AS source;复制代码

5.3 外键约束

  • InnoDB是目前MySQL中惟一支持外键的内置存储引擎。
  • 使用外键是有成本的。
    • 好比外键一般都要求每次在修改数据时都要在另一张表中多执行一次查找操做。虽然InnoDB强制外键使用索引,但仍是没法消除这种约束检查的开销。若是外键列的选择性很低,则会致使一个很是大且选择性很低的索引。
  • 某些场景下外键会提高一些性能:
    • 若是想确保两个相关表始终有一致的数据,那么使用外键比在应用程序中检查一致性的性能要高得多。
    • 外键在相关数据的删除和更新上,也比在应用中维护要更高效,不过,外键维护操做是逐行进行的,因此这样的更新会比批量删除和更新要慢。
  • 外键约束使得查询须要额外访问一些别的表,这也意味着须要额外的锁。
    • 若是向子表中写入一条记录,外键约束会让InnoDB检查对应的父表的记录,也就须要对父表对应记录进行加锁操做,来确保这条记录不会在这个事务完成之时就被删除了。这会致使额外的锁等待,甚至会致使一些死锁。由于没有直接访问这些表,因此这类死锁问题每每很难排除。
  • 有时,可使用触发器来代替外键,对于相关数据的同时更新外键更合适,可是若是外键只是用做数值约束,那么触发器或者显式地限制取值会更好些(这里,能够直接使用ENUM类型)
  • 若是只是用外键作约束,那一般在应用程序里实现该约束会更好。外键会带来很大的额外消耗。

5.4 在MySQL内部存储代码(暂时无用,简单介绍)

在将来一段时间还不会用到,须要用到再看,感受更适合DBA,这里只列举经常使用的方式。

5.4.1 存储过程和函数

5.4.2 触发器

能够在执行INSERT、UPDATE或者DELETE的时候,执行一些特定的操做。能够在MySQL中指定是在SQL语句执行前触发仍是在执行后触发。

5.4.3 事件

相似于LINUX的定时任务,不过彻底是在MySQL内部实现。

5.4.4 在存储过程当中保留注释

5.5 游标(暂时无用,简单介绍)

MySQL在服务器中提供只读的、单向的游标,并且只能在存储过程或者更底层的客户端API中使用。由于游标中指向的对象都是存储在临时表中而不是实际查询到的数据,因此MySQL游标老是可读的。

5.6 绑定变量

  • 绑定变量的SQL语句:INSERT INTO tbl(col1, col2, col3) VALUES (?, ?, ?);。绑定变量的SQL,使用问号标记能够接收参数的位置,当真正须要执行具体查询的时候,则使用具体值代替这些问号。
  • 当建立一个绑定变量SQL时,客户端(如C或JAVA等)向服务器发送了一个SQL语句的原型。服务器端收到这个SQL语句的框架后,解析并存储这个SQL语句的部分执行计划,返回给客户端一个SQL语句处理句柄。之后每次执行这类查询,客户端都制定使用这个句柄。
  • MySQL在使用绑定变量的时候能够更高效地执行大量重复语句的缘由:
    • 在服务器端只须要解析一次SQL语句。
    • 在服务器端某些优化器的工做只须要执行一次,由于它会缓存一部分的执行计划。
    • 以二进制的方式只发送参数和句柄,比起每次发送ASCII码文本效率更高。不过最大的节省仍是来自于BLOB和TEXT字段,绑定变量的形式能够分块传输,而无须一次性传输。二进制协议在客户端也能够节省不少内春,减小了网络开销,还节省了将数据从存储原始格式转换成文本格式的开销。
    • 仅仅是参数而不是整个查询语句须要发送到服务器端,因此网络开销会更小。
    • MySQL在存储参数的时候,直接将其存放到缓存中,再也不须要在内存中屡次复制。
  • 绑定变量相对也更加安全。无须在应用程序中处理转义,一则更简单明了,二则也大大减小了SQL注入和攻击的风险。

5.6.1 绑定变量的优化

理论上有些优化器只须要作一次,但实际上,下面的操做仍是都会被执行。根据优化器何时工做,能够将优化分为三类:

  • 在准备阶段:服务器解析SQL语句,移除不可能的条件,并重写子查询
  • 在第一次执行的时候:若是可能的话,服务器先简化嵌套循环的关联,并将外关联转换成内关联
  • 在每次SQL语句执行时,服务器作以下事情:
    • 过滤分区
    • 若是可能的话,尽可能移除COUNT()、MIN()和MAX()
    • 移除常数表达式
    • 检测常量表
    • 作必要的等值传播
    • 分析和优化ref、range和索引优化等访问数据的方法。
    • 优化关联顺序。

5.6.2 SQL接口的绑定变量(暂时无用,简单介绍)

最主要的用途就是在存储过程当中使用。

5.6.3 绑定变量的限制

  • 绑定变量是会话级别的,因此链接之间不能共用绑定变量句柄。一样地,一旦链接断开,则原来的句柄也不能再使用。(链接池和持久化链接能够在必定程度上缓解这个问题)
  • MySQL5.1以前,绑定变量的SQL是不能使用查询缓存的。
  • 并非全部的时候使用绑定变量都能得到更好的性能。若是只是执行一次SQL,那么使用绑定变量的方式五一比直接执行多了一次额外的准备消耗阶段,并且还须要一次额外的网络开销。(要正确的使用绑定变量,还须要在使用完成以后,释放相关的资源)
  • 当前版本下,还不能在存储函数中使用绑定变量,可是在存储过程当中可使用
  • 若是老是忘记释放绑定变量资源,则在服务器端很容易发发生资源泄漏。绑定变量SQL老是的限制是一个全局限制,因此某一个其余的错误可能会对全部其它的线程都产生影响。
  • 有些操做,如BEGIN,没法在绑定变量中完成。

三种绑定变量类型的部分区别:

  • 客户端模拟的绑定变量:客户端的驱动程序接收到一个带参数的SQL,再将指定的值带入其中,最后将完整的查询发送到服务器端。
  • 服务器端的绑定变量:客户端使用特殊的二进制协议将带参数的字符串发送到服务器端,而后使用二进制协议将具体的参数值发送给服务器端执行。
  • SQL接口的绑定变量:客户端先发送一个带参数的字符串到服务器端,这相似与使用PREPARE的SQL语句,而后发送设置参数的SQL,最后使用EXECUTE来执行SQL。全部这些都是用普通的文本传输协议。

5.7 用户自定义函数(暂时无用,简单介绍)

使用支持C语言调用约定的任何编程语言来实现用户自定义函数(UDF)。UDF必须事先编译后并动态连接到服务器上。

5.8 插件(暂时无用,简单介绍)

插件类型:

  • 存储过程插件
  • 后台插件
  • INFORMATION_SCHEMA插件
  • 全文解析插件
  • 审计插件
  • 认证插件

5.9 字符集和校对

字符集是指一种从二进制编码到某类字符符号的映射,能够参考如何使用一个字节来表示英文字符。校对是指一组用于某个字符集的排序规则。

5.9.1 MySQL如何使用字符集

每种字符集均可能有多种校对规则,而且都有一个默认的校对规则,每一个校对规则都是针对某个特定的字符集,所以把字符集和校对规则统称为字符集。

MySQL有不少选择用于控制字符集,这些选项和字符集很容易混淆。只有基于字符的值才真正的有字符集的概念。对于其余类型的值,字符集只是一个设置,指定用哪一种字符集来作比较或者其余操做。

MySQL的设置:

  • 建立对象时的默认设置:

    • 建立数据库的时候,将根据服务器上character_set_server设置来设定该数据库的默认字符集
    • 建立表的时候,将根据数据库的字符集设置指定这个表的字符集设置
    • 建立列的时候,将根据表的设置指定列的字符集设置。
    • 以上三个阶层,每一层都只是指定一个默认值,当这一层没有指定字符集的时候,默认值才会生效。
  • 服务器和客户端通讯时的设置:

    • 服务器和客户端通讯的时候,他们可能使用不一样的字符集。这时,服务器端将进行必要的翻译转换工做:

      • 服务器端老是假设客户端是按照character_set_client设置的字符来传输数据和SQL语句的。
      • 当服务器收到客户端的SQL语句时,它先将其转换成字符集character_set_connection。它仍是用这个设置来决定如何将数据转换成字符串。
      • 当服务器端返回数据或者错误信息给客户端时,它会将其转换成character_set_result。

      客户端和服务器的字符集
      客户端和服务器的字符集

    • 根据须要,可使用SET NAMES或者SET CHARACTER语句来改变上面的设置。不过在服务器上使用这个命令只能改变服务器端的设置。客户端程序和客户端的API也须要使用正确的字符集才能避免在通讯时出现问题。

MySQL比较两个字符串的大小时,经过将其转换成同一个字符集再进行比较,若是两个字符集不兼容的话,则会抛出错误。MySQL还会为每一个字符串设置一个“可转换性”,这个设置决定了值的字符集的优先级,于是会印象MySQL作字符集隐式转换后的值。

  • 还可使用前缀和COLLATE子句来指定字符串的字符集或者校对字符集。

    mysql> SELECT _utf8 'hello world' COLLATE utf8_bin;复制代码

一些特殊状况:

  • 诡异的character_set_database设置:当改变默认数据库的时候,这个变量也会跟着改变。
  • LOAD DATA INFILE:数据库老是将文件中的字符按照字符集character_set_database来解析
  • SELECT INTO OUTFILE:将结果不作任何转码地写入文件
  • 嵌入式转义序列:MySQL会根据character_set_client的设置来解析转义序列,即便字符串中包含前缀或者COLLATE子句也同样。由于对解析器来讲,前缀并非一个指令,只是一个关键字。

5.9.2 选择字符集和校对规则

  • 可使用命令SHOW CHARACTERSET和SHOW COLLATION来查看MYSQL支持的字符集和校对规则。

  • 极简原则:最好先为服务器或者数据库选择一个合理的字符集,而后根据不一样的实际状况,让某些列选择合适的字符集。

  • 对于校对规则一般须要考虑的一个问题是,是否以大小写敏感的方式比较字符串,或者是以字符串编码的二进制值来比较大小。二进制校对规则直接使用字符的字节进行比较,而大小写敏感的校对规则在多字节字符集时如德语有更复杂的比较规则。

  • MySQL如何选择字符集和校对规则:

    MySQL如何选择字符集和校对规则
    MySQL如何选择字符集和校对规则

5.9.3 字符集和校对规则如何影响查询

某些字符集和校对规则可能会须要更多的CPU操做、消耗更多的内存和存储空间,甚至还会影响索引的正常使用。

  • 不一样的字符集和校对规则之间的转换可能会带来额外的系统开销。
    • 只有排序查询要求的字符集与服务器数据的字符集相同的时候,才能使用索引来排序。索引根据数据列的校对规则进行排序。
  • MySQL会在须要的时候进行字符集转换:
    • 当时用两个字符集不一样的列来关联两个表的时候,MySQL会尝试转换其中一个列的字符集。
  • UTF-8是一种多字节编码,它存储一个字符会使用变成的字节数。在MySQL内部,一般使用一个定长的时间来存储字符串,在进行相关操做,这样的目的是但愿老是保证缓存中有足够的空间来存储字符串。
    • 在多字节字符集中,一个字符再也不是一个字节。能够用LENGTH()和CHAR_LENGTH()来计算字符串的长度。在多字节字符集中,二者返回的结果会不一样,所以要使用后者
    • 若是要索引一个UTF-8字符集的索引,MySQL会假设每个字符都是三个字节,索引最长索引前缀的限制一下缩短到原来的三分之一。对MySQL使用索引有一些影响,好比没法使用索引覆盖扫描。
    • 若是所有直接使用UTF-8字符集,从性能角度来并很差,只会消耗更多的存储空间,由于不少应用无须使用该字符集。
  • 考虑字符集须要根据存储的具体内存来决定:
    • 存储的内容主要是英文字符,可使用UTF-8,由于其只占用一个字节
    • 存储一些非拉丁语系的字符,可使用cpl256
    • 存储别的语言,使用UTF-8。
    • 当从某个具体的语种编码转成UTF-8时,存储空间的使用会相对增长。若是使用的是InnoDB表,那么字符集的改变可能会致使数据的大小超过能够在页内存储的临界值,须要保存在额外的外部存储区,这会致使严重的空间浪费和空间碎片。
  • 有时候根本不须要使用任何的字符集。一般只有在作大小写无关的比较、排序、字符串操做的时候才须要使用字符集。若是数据库不关心字符集,那么能够直接将全部东西存储到二进制列中,包括UTF-8编码数据。这么作可能还须要一个列记录字符的编码集,致使不少难以排除的错误。所以若是可能建议尽可能不要这么作。

5.10 全文索引(暂时无用,简单介绍)

  • 全文索引有着本身独特的语法,没有索引也能够工做,若是有索引效率会更高。
  • 全文索引能够支持各类字符内容的搜索,也支持天然语言搜索和布尔搜索。
  • 只有在MyISAM引擎支持,5.6版本后的InnoDB也已经实验性质的支持
    • MyISAM的全文索引是一种特殊的B-Tree索引,共有两层。第一层是全部关键字,而后对于每个关键字的第二层,包含的是一组相关的“文档指针”

5.10.1 天然语言的全文索引

计算每个文档对象和查询的相关度。相关度是基于匹配的关键词个数,以及关键词在文档中出现的个数。在整个索引中出现次数越少的词语,匹配的相关度就越高,相反很是常见的单词就不会被搜索。

5.10.2 布尔全文索引

能够在查询中自定以某个被搜索词语的相关性。布尔搜索经过停用词列表过滤掉那些噪声词,另外还要求搜索的关键词长度必须大于ft_min_word_len并小于ft_max_word_len。搜索返回的结果是未经排序的。

5.10.3 MySQL5.1中全文索引的变化

5.10.4 全文索引的限制和替代方案

  • 限制:
    • 只有一种影响相关性的方法:词频
    • 数据量大小
    • 还会影响优化器的工做。索引选择、WHER子句、ORDER BY都有可能不是按照预计的方式工做。

5.10.5 全文索引的配置和优化

  • 按期地进行全文索引重建等平常维护可提高性能
  • 保证索引缓存足够大,从而保证全部的全文索引都能缓存在内存中
  • 提供一个好的停用词列表
  • 忽略一些过短的单词能够提高全文索引的效率
  • 停用词表和容许最小词长均可以减小索引词语来提高全文索引效率,但同时会下降搜索的精确度。
  • 当向一个全文索引的表中导入大量数据的时候,最后先DISABLE KEYS来禁用全文索引,而后在导入数据后再ENABLE KEYS来创建全文索引。
  • 若是数据集很是大,则须要对数据进行手动分区,而后将数据分布到不一样的节点,再作并行的搜索。

5.11 分布式(XA)事务

存储引擎的事务特性能勾保证在存储引擎级别实现ACID,而分布式事务则让存储引擎级别的ACID扩展到数据库层面,甚至扩展到多个数据库之间,这须要两个阶段提交实现:

  • 第一阶段:XA事务中须要一个事务协调器来保证全部的事务参与者都完成了准备工做。
  • 第二阶段:若是协调器收到全部的参与者都准备好的消息,就会告诉全部的事务能够提交了。
  • MySQL在这个XA事务过程当中扮演一个参与者的角色,而不是协调者

5.11.1 内部XA事务

  • 做用:协调内部存储引擎和二进制日志
  • MySQL中各个存储引擎是彻底独立的,彼此不知道对方的存在,因此一个跨存储引擎的事务就须要一个外部的协调者。若是不使用XA协议,例如,跨存储引擎的事务提交就只是顺序地要求每一个存储引擎格子提交,若是在某个存储提交过程当中发生系统崩溃,就会破坏事务的特性。
  • 若是将MySQL记录的二进制日志操做看做是一个独立的存储引擎,在存储引擎提交的同时,须要将提交的信息写入二进制文件,这就是一个分布式事务,只不过二进制日志的参与者是MySQL自己。
  • XA事务为MySQL带来巨大的性能降低。从MySQL5.0开始,它破坏了MySQL内部的“批量提交”(一种经过单磁盘IO操做完成多个事务提交的技术),使得MySQL不得不进行屡次额外的fsync()调用。

5.11.2 外部XA事务

  • 做用:MySQL能够参与到外部的分布式事务中
  • MySQL可以做为参与者完成一个外部的分布式事务。但它对XA协议支持并不完整,例如,XA协议要求在一个事务中的多个链接能够作关联,但目前版本的MySQL还不能支持。
  • 由于通讯延迟和事务参与者自己可能失败,因此外部XA事务比内部消耗会更大。
    • 若是在广域网中使用XA事务,一般会由于不可预测的网络性能致使事务失败。
    • 若是有太多不可控因素,例如,不稳定的网络通讯或者用户长时间地等待而不提交,则最好避免使用XA事务。任何可能让事务提交发生延迟的操做代价都很大,由于它影响的不只是本身自己,还会让全部参与者都在等待。
  • 还可使用别的方式实现高性能的分布式事务。例如,能够在本地写入数据,并将其放入队列,而后在一个更小、更快的事务中自动分发。还可使用MySQL自己的复制机制来发送数据。不少应用程序均可以彻底避免使用分布式事务。
  • XA事务是一种在多个服务器之间同步数据的办法。若是因为某些缘由不能使用MySQL自己的复制,或者性能不是瓶颈的时候,能够尝试使用。

5.12 查询缓存

MySQL的缓存类型:

  • 某些场景下实现缓存查询的执行计划,对于相同类型的SQL就能够跳过SQL解析和执行计划生成阶段
  • 缓存完整的SELECT查询结果,也就是“查询缓存”

MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存,MySQL会马上返回结果,跳过了解析、优化和执行阶段。

  • 查询缓存系统会跟踪查询中涉及的每一个表,若是这些表发生变化,那么和这个表相关的全部的查询缓存数据都将失效。这种机制效率看起来很低,可是实现代价很小,而这点对于一个很是繁忙的系统来讲很是重要。
  • 查询缓存对应用是彻底透明的。应用程序无须关心MySQL是经过查询缓存返回仍是实际执行返回的结果。

随着如今的通用服务器愈来愈大,查询缓存被发现是一个影响服务器扩展性的因素。它可能成为整个服务器的资源竞争单点,在多核服务器上还可能致使服务器僵死。建议默认关闭查询缓存,若是查询缓存做用很大的话,那就配置一个很小的查询缓存空间(如几十兆)。

5.12.1 MySQL如何命中查询缓存

  • 缓存存放在一个引用表中,经过一个哈希值引用,这个哈希值包括了以下因素:即查询自己、当前要查询的数据库、客户端协议的版本等一些其余可能会影响返回结果的信息。

    • 当判断缓存是否命中时,MySQL不会解析、“正规化”或者参数化查询语句,而是直接使用SQL语句和客户端发送过来的其余原始信息。任何字符上的不一样,例如空格、注释,都会致使缓存不命中。

    • 当查询语句中有一些不肯定的数据时,则不会被缓存。例如包含函数NOW()或者CURRENT_DATE的查询都不会被缓存。

      • 包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL库中的系统表,或者任何包含级别权限的表,都不会被缓存。
      • 在检查查询缓存以前,MySQL经过一个大小写不敏感的检查看看SQL语句是否以SEL开头。
      • 而检查查询缓存的时候,MySQL还不会解析SQL语句,因此MySQL并不知道查询语句中是否包含有返回不肯定数据的函数。可是MySQL在任什么时候候只要发现不能被缓存的部分,就会禁止这个查询被缓存。
      -- 若是但愿换成一个带日期的查询,那么最好将其日期提早计算好,而不要直接使用函数
      ... DATE_SUB(CURRENT_DATE, INTERVAL 1 DAY) -- Not cacheable!
      ... DATE_SUB('2007-07-14’, INTERVAL 1 DAY) -- Cacheable复制代码
    • 子查询和存储过程都没办法使用查询缓存,另外5.1版本以前,绑定变量也没法使用。由于查询缓存是在完整的SELECT语句基础上的,并且只是在刚收到SQL语句的时候才检查。

    • 查询缓存在不少时候能够提高查询性能,但自己是一个加锁排他操做,另外打开查询缓存对读和写都会带来额外的消耗:

      • 读查询在开始以前必须先检查是否命中缓存
      • 若是这个读查询能够被缓存,那么当完成执行后,MySQL若发现查询缓存中没有这个查询,会将其结果存入查询缓存,这会带来额外的系统消耗。
      • 这对写操做也会有影响,由于当向某个表写入数据的时候,MySQL必须将对应表的全部缓存都设置失效。若是查询缓存很是大或者碎片不少,这个操做就可能带来很大系统消耗(设置了不少的内存给查询缓存用的时候)
    • 对InnoDB来讲,事务的一些特性会限制查询缓存的做用。当一个语句在事务中修改了某个表,MySQL会将这个表的对应的查询缓存都设置失效,而事实上,InnoDB的多版本特性会暂时将这个修改对其它事务屏蔽。

      • 在这个事务提交以前,这个表的相关查询是没法被缓存的,因此全部在这个表上面的查询,内部或外部的事务,都只能在该事务提交后才被缓存。所以,长时间运行的事务,会大大下降查询缓存的命中率。
    • 若是查询缓存使用了很大量的内存,缓存失效操做就可能会成为一个很是严重的问题瓶颈。

      • 若是缓存中存放了大量的查询结果,那么缓存失效操做时整个系统均可能会僵死一会。由于这个操做是靠一个全局锁操做保护的,全部须要作该操做的查询都要等待这个锁,并且不管是检测是否命中缓存,仍是缓存失效检测都须要等待这个全局锁。

5.12.2 查询缓存如何使用内存

  • 查询缓存是彻底存储在内存中。

    • 除了查询结果,须要缓存的还有不少别的维护相关的数据。这些基本的管理维护数据结构大概须要40KB的内存资源。
    • 用于查询缓存的内存被分红一个个的数据块,数据块是变长的。
      • 每个数据块中,存储了本身的类型、大小和存储的数据自己,还外加指向前一个和后一个数据块的指针。数据块的类型有:存储查询结果、存储查询和数据表的映射、存储查询文本等等。
  • 理想流程:

    • 当服务器启动的时候,它先初始化查询缓存须要的内存。这个内存池初始是一个完整的空闲块,大小就是所配置的查询缓存再减去用于维护元数据的数据结构锁消耗的空间。
      • 经过函数malloc()向操做系统申请内存,在整个流程只在初次建立查询缓存的时候执行一次。
    • 当有查询结果须要缓存的时候,MySQL先从大的空闲块中申请一个数据块用于存储结果。
      • 这个数据块须要大于参数query_cache_min_res_unit的配置,即便查询结果远远小于此,仍须要至少申请query_cache_min_res_unit空间。由于须要在查询开始返回结果的时候就分配空间,而此时是没法预知查询结果到底有多大的,因此MySQL没法为每个查询结果精确分配大小刚好匹配的缓存空间。
      • 这个内存块会尽量小(也可能选择较大的,这里不介绍细节),而后将结果存入其中。由于须要先锁住数据块,而后找到合适大小的数据块,因此相对来讲,分配内存块是一个很是慢的操做,MySQL尽可能避免这个操做的次数。
      • 这里的分配内存块,是指在空闲块列表中找到一个合适的内存块,或者从正在使用的、待淘汰的内存块中回收再使用。也就是说,MySQL本身管理内存而不依赖与操做系统的内存管理。
    • 若是数据块所有用完,但仍有剩余数据须要存储,MySQL会申请一块新数据块(仍然是尽量小)继续存储结果数据。
    • 当查询完成时,若是申请的内存空间仍有剩余,MySQL会将其释放,并放入空闲内存部分。

    查询缓存如何分配内存来存储结果数据
    查询缓存如何分配内存来存储结果数据

  • 实际流程:

    • 假设平均查询结果很是小,服务器在并发地向不一样的两个链接返回结果,返回完结果后MySQL回收剩余数据块空间时会发现,回收的数据块小于query_cache_min_res_unit,因此不可以直接在后续的内存块分配中使用。考虑到这种状况,数据块的分配就更复杂些。

      • 在收缩第一个查询结果使用的缓存空间时,就会在第二个查询结果之间留下一个“空隙”——很是小的空闲空间,由于小于query_cache_min_res_unit而不能再次被查询缓存使用。这类空隙称为碎片,在内存管理、文件系统管理上都是经典问题。
      • 有不少状况下都会致使碎片,例如缓存失效时,可能致使留下过小的数据块没法在后续缓存中使用。

      查询缓存中存储查询结果后剩余的碎片
      查询缓存中存储查询结果后剩余的碎片

5.12.3 什么状况下查询缓存能发挥做用

  • 只有当缓存带来的资源节约大于其自己的资源消耗时才会给系统带来性能提高。
  • 任何SELECT语句没有从查询缓存中返回都称为“缓存未命中”,缓存未命中的缘由:
    • 查询语句没法被缓存,多是由于查询中包含一个不肯定的函数,或者查询结果太大。这都会致使状态值Qcache_not_cached增长
    • MySQL从未处理这个查询,因此结果也从未曾被缓存过
    • 以前缓存了查询结果,但因为查询缓存的内存用完,须要将某些缓存清除,或者因为数据表被修改致使缓存失效
  • 服务器上有大量缓存未命中,但实际上最大多数查询都被缓存了,必定是有以下状况发生:
    • 查询缓存尚未完成预热。也就是说,MySQL尚未将查询结果都缓存起来。
    • 查询语句以前从未被执行过。若是应用程序不会重复执行一条查询语句,那么即便完成预热仍然会有不少缓存未被命中。
    • 缓存失效的操做太多。
      • 缓存碎片、内存不足、数据修改都会致使缓存失效。
      • 若是配置了足够的缓存空间,并且query_cache_min_resunit设置也合理的话,那么缓存失效应该主要是数据修改致使的。能够经过Com*查看数据修改的状况(包括Com_update,Com_delete),也能够经过Qcache_lowmem_prunes来查看有多少次失效是因为内存不足致使的。
  • 评估是否使用查询缓存的方法:
    • 理论上,能够经过打开或者关闭缓存时候的系统效率来决定是否须要开启查询缓存。可是很难评估查询缓存是否可以带来性能提高。
      • SHOW STATUS只能提供一个全局的性能指标,也很难评估性能的提高
    • 对于那些须要消耗大量资源的查询一般都是很是适合缓存。
      • 一些汇总计算查询,如COUNT()
      • 复杂的SELECT语句,如多表JOIN后还须要排序和分页,这类查询每次执行消耗都很大,可是返回的结果集却很小,很是适合查询缓存。不过须要注意,涉及表上的UPDATE、DELETE和INSERT相比SELECT来讲要很是少。
    • 判断查询是否有效的直接数据是命中率,就是使用查询缓存返回结果占总查询的比率。
      • 当MySQL接收到一个SELECT查询时,要么增长Qcache_hints的值,要么增长Com_select的值。
      • 查询缓存命中率是一个很难判断的值。命中率多大才是好的?只要查询缓存带来的效率提高大于它的消耗,即便只有30%的命中率也能够;缓存了哪些查询也很重要,例如,被缓存的查询自己消耗很是大,即便缓存命中率低也能够接受
    • 考虑缓存命中率的同时,一般还须要考虑缓存失效带来的额外消耗。
      • 极端的办法,对某一个表先作一次只有查询的测试,而且全部的查询都命中缓存,另外一个相同的表只作修改操做。这时,查询缓存的命中率是100%,但由于会给更新操做带来额外的消耗,因此查询缓存并不必定会带来整体效率提高。这里,全部的更新语句都会作一次缓存失效检查,而检查的结果都是相同的,这会给系统带来额外的资源浪费。
    • MySQL中若是更新操做和带缓存的操做混合,查询缓存带来的好处很难衡量。
      • 若是缓存的结果在失效前没有被任何其余的SELECT语句使用,那么此次缓存操做就是浪费时间和内存。
      • 能够经过查看Qcache_select和Qcache_inserts的相对值来查看。若是每次查询操做都是缓存未命中,而后须要将查询结果放到缓存中,这两个值应该差很少。因此在缓存完成预热后,最好的状况是Query_inserts远远小于Query_select
    • 命中率和“INSERT和SELECT比例”都没法直观地反应缓存的效率,还有另外一个直观的办法:命中和写入的比例,即Qcache_hints和Qcache_inserts的比率
      • 根据经验,这个比值大于3:1时一般查询缓存是有效的,最好可以达到10:1.
      • 若是应用没有达到这个比率,能够考虑禁用查询缓存,除非可以经过精确的计算得知:命中带来的性能提高大于缓存失效的消耗,而且查询缓存并无成为系统的瓶颈。
    • 观察查询缓存内存的实际使用状况,来肯定是否须要缩小或者扩大查询缓存。
      • 若是查询缓存达到几十兆这样的数量级,是有潜在风险的。(这和硬件以及系统大小有关)
    • 须要和系统的其余缓存一块儿考虑,如InnoDB的缓存池,或者MyISAM的索引缓存。
    • 最好的判断查询缓存是否有效的办法仍是经过查看某类查询时间消耗是否增大或者减小来判断。

5.12.4 如何配置和维护查询缓存·

  • 配置:

    • query_cache_type:是否打开查询缓存。能够是ON、OFF或者DEMAND。DEMAND表示只有在查询语句中明确写明SQL_CACHE的语句才放入查询缓存。这个变量能够是会话级别也能够是全局级别。
    • query_cache_size:查询缓存使用的总内存空间,单位是字节,必须是1024的整数倍,不然MySQL实际分配的数据可能会有不一样。
    • query_cache_min_res_unit:在查询缓存中分配内存块时的最小单位。
    • query_cache_limit:MySQL可以缓存的最大查询结果。
      • 若是查询结果大于这个值,则不会被缓存。由于查询缓存在数据生成的时候就开始尝试缓存数据,因此只有当结果所有返回后,MySQL才知道查询结果是否超出限制
      • 若是超出,MySQL则增长状态值Qcache_not_cached,并将结果从查询缓存中删除。若是事先知道有不少这样的状况发生,那么建议在查询语句中加入SQL_NO_CACHE来避免查询缓存带来的额外消耗。
    • query_cache_wlock_invalidate:若是某个数据表被其余的链接锁住,是否仍然从查询缓存中返回结果。默认是OFF。
  • 减小碎片

    • 没有什么办法可以彻底避免碎片,可是合适的query_cache_min_res_unit能够减小由碎片致使的内存空间浪费。
      • 设置合适的值能够平衡每一个数据块的大小和每次存储结果时内存块的申请次数,实际上是在平衡内存浪费和CPU消耗。
      • 这个值过小,则浪费的空间更少,可是会致使更频繁的内存块申请操做。若是太多,则碎片会不少。
    • 这个参数的最合适大小和应用程序的查询结果的平均大小直接相关。
      • 能够经过内存实际消耗(query_cache_size-Qcache_free_memory)除以Qcache_queries_in_cache计算单个查询的平均缓存大小。
      • 若是查询结果大小很不均匀,那么碎片和反复的内存块分配可能没法避免。
      • 若是发现缓存了一个很是大的结果,能够经过参数query_cache_limit限制能够缓存的最大查询结果。
    • 能够经过参数Qcache_free_blocks来观察碎片,反映了查询缓存中内存块的多少。
      • 若是Qcache_free_blocks刚好达到Qcache_total_blocks/2,那么查询缓存就有严重的碎片问题。
      • 若是还有不少空闲块,而状态值Qcache_lowmem_prunes还不断增长,则说明因为碎片致使了过早的删除查询缓存结果。
    • 可使用FLUSH QUERY CACHE完成碎片整理。这个命令会将全部的查询缓存从新排序,并将全部的空闲空间都汇集到查询缓存的一块区域上。
      • 会访问全部的查询缓存,在这期间任何其余的链接都没法访问查询缓存,从而致使服务器僵死一段时间。所以,建议保持查询缓存空间足够小
      • 清空缓存由RESET QUERY CACHE完成
  • 提升查询缓存的使用率

    • 若是查询缓存再也不有碎片问题,但命中率仍然很低,还多是查询缓存内存空间过小致使的。若是MySQL没法为一个新的查询缓存结果的时候,则会删除某个老的查询缓存
    • 当删除老的查询缓存时,会增长状态值Qcache_lowmem_prunes。若是这个值增加的很快,多是由如下两个缘由致使的:
      • 若是还有不少空闲块,那么碎片可能就是罪魁祸首
      • 若是这时没什么空闲块,就说明在这个系统压力下,分配的查询缓存空间不够大。能够经过检查状态值Qcache_free_memory来查看还有多少没有使用的内存。

    如何分析和配置查询缓存
    如何分析和配置查询缓存

5.12.5 InnoDB和查询缓存

  • 由于InnoDB有本身的MVVC机制,InnoDB会控制在一个事务中是否可使用查询缓存,InnoDB会同时控制对查询缓存的读和写操做。
    • 事务是否能够访问查询缓存决定于当前的事务ID,以及对应的数据表上是否有锁。每个InnoDB表的内存数据字典都保存了一个事务ID,若是当前事务ID小于该事务ID,则没法访问查询缓存。
    • 若是表上有任何锁,那么对这个表的任何查询语句都是没法被缓存的。
  • InnoDB下的查询缓存:
    • 全部大于该表计数器的事务才能够直接使用(读和写)查询缓存。
    • 该表的计数器并非直接更新为对该表进行加锁操做的事务ID,而是被更新成一个系统事务ID。因此,会发现该事务自身后续的更新操做也没法读取和修改查询缓存。
  • 查询缓存存储、检索和失效操做都是在MySQL层面完成,InnoDB没法绕过或者延迟这个行为。
    • 可是InnoDB能够在事务中显式地告诉MySQL什么时候应该让某个表的查询缓存都失效。在有外键限制的时候这是必须的,例如某个SQL有ON DELETE CASCADE。
  • 原则上,在InnoDB的MVVC架构下,当某些修改不影响其余事务读取一致的数据时,是可使用查询缓存的。可是这样实现起来会很负责,InnoDB作了简化,让全部有加锁操做的事务都不使用任何查询缓存,这个限制不是必须的。

5.12.6 通用查询缓存优化

  • 用多个小表代替一个大表对查询缓存有好处。这个设计将会使得失效策略可以在一个更合适的粒度上进行。固然,不要让这个原则过度影响设计,毕竟其它的一些优点很容易就能弥补。
  • 批量写入时只须要作一次缓存失效,因此相比单条写入的效率要高。注意不要同时作延迟写和批量写,不然可能会致使服务器僵死较长时间。
  • 由于缓存空间太大,在过时操做的时候可能会致使服务器僵死。一个简单的办法就是控制缓存空间的大小,或者直接禁用查询缓存。
  • 没法在数据库或则表级别控制查询缓存,可是能够经过SQL_CACHE和SQL_NO_CACHE来控制某个SELECT语句是否须要进行缓存。还能够修改会话级别的query_cache_type来控制查询缓存。
  • 对于写密集型的应用来讲,直接禁用查询缓存可能会提升系统性能。关闭查询缓存能够移除全部相关的消耗,例如将query_cache_size设置为0
  • 由于对互斥信号量的竞争,有时直接关闭查询缓存对读密集型的应用也会有好处。
  • 若是不想全部的查询都进入查询缓存,能够将query_cache_type设置为DEMAND,而后在但愿缓存的查询中加上SQL_CACHE

5.12.7 查询缓存的替代方案

查询缓存的工做原则是:执行查询最快的方式就是不去执行。可是查询仍然要发送到服务器端,服务器端还须要作一点点工做。所以能够直接在客户端进行缓存。

5.13 总结

  • 分区表:分区表是一种粗粒度的、简易的索引策略,适用于大数据量的过滤场景。最适合的场景是,在没有合适的索引时,对其中几个分区进行全表扫描,或者是只有一个分区和索引是热点,并且这个分区和索引都可以在内存中;限制单表分区数不要超过150个,而且注意某些致使没法作分区过滤的细节,分区表对于单条记录的程序并无什么优点,须要注意这类查询的性能。
  • 视图:对好几个表的复杂查询,使用视图有时候会大大简化问题。当视图使用临时表时,没法将WHERE条件下推到各个具体的表,也不能使用任何索引,须要特别注意这类查询的性能。若是为了遍历,使用视图是很合适的。
  • 外键:外键限制会将约束放到MySQL中,这对于必须维护外键的场景,性能会更高。不过这也会带来额外的复杂性和额外的索引消耗,还会增长多表之间的交互,会致使系统中有更多的锁和竞争。外键能够被看做是一个确保系统完整性的额哇的特性,可是若是设计的是一个高性能的系统,那么外键就会显得很臃肿了。不少人在更在乎系统的性能的时候都不会使用外键,而是经过应用程序来维护。
  • 存储过程
  • 绑定变量
  • 插件
  • 字符集:字符集是一种字节到字符之间的映射,而校对规则是指一个字符集的排序方法。不少人都使用Latin1(默认字符集,对英语和某些欧洲语言有效)或者UTF-8。若是使用的是UTF-8,那么在使用临时表和缓冲区的时候须要注意:MySQL会按照每一个字符三个字节的最大占用空间来分配存储空间,这可能消耗更多的内存或者磁盘空间。注意让字符集和MySQL字符集配置相符,不然可能会因为字符集转换让某些索引没法正常工做。
  • 全文索引
  • XA事务:不多会有人用MySQL的XA事务特性。除非你真正明白参数innodb_support_xa的意义,不然不要修改这个参数的值,并非只有显示使用XA事务时才须要设置这个参数。InnoDB和二进制日志也是须要使用XA事务来作协调的,从而确保在系统崩溃的时候,数据可以一致地恢复。
  • 查询缓存:彻底相同的查询在重复执行的时候,查询缓存能够当即放回结果,而无须在数据库中从新执行一次。根据经验,在高并发压力环境中查询缓存会致使系统性能的降低,甚至僵死。若是必定要使用查询缓存,那么不要设置太大内存,并且只有在明确收益的时候才使用。查询缓存是一个很是方便的缓存,对应用程序彻底透明,无须任何额外的编码,可是若是但愿有更高效的查询缓存,建议使用memacched等其余缓存方案。
相关文章
相关标签/搜索