高性能mysql:MySQL高级特性

分区表


分区表是一个独立的逻辑表,底层由多个物理子表构成。实现分区的代码其实是对一组底层表的句柄对象的封装。对分区表的请求,都会经过句柄对象转化成对存储引擎的接口调用。node

MySQL实现分区表的方式——对底层表的封装——意味着索引也是按照分区的子表定义的,而没有全局索引。mysql

MySQL在建立表的时使用PARTITION BY子句定义每一个分区存放的数据。在执行查询的时候,优化器会根据分区定义过滤那些没有咱们须要数据的分区,这样查询就无须扫描全部分区——只须要查找包含须要数据的分区就能够了。算法

分区的一个主要目的是将数据按照一个较粗的粒度分在不一样的表中。sql

使用场景:数据库

  • 表很是大以致于没法所有都放在内存中,或者只在表的最后部分有热点数据,其余均是历史数据。
  • 分区表的数据更容易维护。例如,想大批量删除大量数据可使用清除整个分区的方式。另外,还能够对一个独立分区进行优化、检查、修复等操做。
  • 分区表的数据能够分布在不一样的物理设备上,从而高效地利用多个硬件设备。
  • 可使用分区表来避免某些特殊的瓶颈,例如InnoDB的单个索引的互斥访问、ext3文件系统的inode锁竞争等。
  • 若是须要,还能够备份和恢复独立的分区,这在很是大的数据集的场景下效果很是好。

分区表自己也有一些限制:编程

  • 一个表最多只能有1024个分区。
  • 在MySQL5.1中,分区表达式必须是整数,或者是返回整数的表达式。在MySQL5.5中,某些场景中能够直接使用列来进行分区。
  • 若是分区字段中有主键或惟一索引的列,那么全部主键列和惟一索引列都必须包含进来。
  • 分区表中没法使用外键约束。
分区表的原理

分区表由多个相关的底层表实现,这些底层表也是由句柄对象表示,因此也能够直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表同样,分区表的索引只是在各个底层表上各自加上一个彻底相同的索引。从存储引擎的角度来看,底层表和一个普通表没有任何不一样,存储引擎也无需知道这是一个普通表仍是一个分区表的一部分。缓存

分区表上的操做按照下面的操做逻辑进行:安全

SELECT查询服务器

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

INSERT操做

当写入一条记录时,分区层先打开并锁住全部的底层表,而后肯定哪一个分区接手这条记录,在将记录写入对应底层表。

DELETE操做

当删除一条记录时,分区层先打开并锁住全部的底层表,而后确立数据对应的分区,最后对相应底层表进行删除操做。

UPDATE操做

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

有些操做是支持过滤的。MySQL先肯定须要更新的记录在哪一个分区,再将记录写入对应的底层分区表,无需对任何其余分区进行操做。

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

分区表的类型

MySQL支持键值、哈希、范围、列表分区,这其中有些还支持子分区。根据范围进行分区,每一个分区存储落在某个范围的记录,分区表达式能够是列,也能够是包含列的表达式。

CREATE TABLE sales(
order_date DATETIME NOT NULL,
-- Other columns omitted
) ENGINE=InnoDB PARTITION BY RANGE(YEAR(order_date))(
PARTITION p_2010 VALUES LESS THAN(2010),
PARTITION p_2011 VALUES LESS THAN(2011),
PARTITION p_2012 VALUES LESS THAN(2012),
PARTITION p_catchall VALUES LESS THAN MAXVALUE
);

PARTITION分区子句中可使用各类函数。但有一个要求,表达式返回的值是一个肯定的整数,且不能是一个常数。在MySQL5.5中,还可使用RANGE COLUMNS类型的分区,这样即便是基于时间的分区也无须在将其转化成一个整数。

按时间分区的InnoDB表,系统经过子分区可下降索引的互斥访问的竞争。最近一年的分区的数据会被很是频繁地访问,这会致使大量的互斥量的竞争。使用哈希子分区能够将数据切成多个小片,大大下降互斥量的竞争问题。

其余的分区技术包括: 根据键值进行分区,来减小InnoDB的互斥量竞争。 使用数学模函数来进行分区,而后将数据轮询放入不一样的分区。 假设表有一个自增的主键列id,但愿根据时间最近的热点数据集中存放。那么必须将时间戳包含在主键当中才行,这和主键自己的意义相矛盾。这种状况能够这样达到目的HASH(id DIV 1000000)

如何使用分区表

假设从一个很是大的表中查询出一段时间的记录,由于数据量巨大,确定不能在每次查询的时候都扫描全表。考虑到索引在空间和维护上的消耗,也不但愿使用索引。即便真的使用索引,你会发现数据并非按照想要的方式汇集的,并且会有大量的碎片产生,最终会致使一个查询产生成千上万的随机I/O,应用程序也随机僵死。状况好一点的时候,也许能够经过一两个索引解决一些问题。不过多数状况下,索引不会有任何做用。这时候只有两条路可选:让全部的查询都只在数据表上作顺序扫描,或者将数据表和索引所有都缓存在内存里。

在数据量超大的时候,B-Tree索引就没法起做用了。除非是索引的覆盖查询,不然数据库服务器须要根据索引扫描的结果回表,查询全部符合条件的记录,若是数据量巨大,这将产生大量随机I/O,数据库的响应时间将大到不可接手的程度。

理解分区时还能够将其当作索引的最初形态,以代价很是小的方式定位到须要的数据在哪一片"区域"。在这片"区域"中,能够作顺序扫描、索引、将数据缓存到内存等等。由于分区无须额外的数据结构记录每一个分区有哪些数据——分区不须要精肯定位每条数据的位置,也就无须额外的数据结构——因此代价很是低。只须要一个简单的表达式就能够表达每一个分区存放的是什么数据。

保证大数据量的可扩展性,通常有下面两个策略:

  • 全量扫描数据,不要任何索引 可使用简单的分区存放表,不要任何索引,根据分区的规则大体定位须要的数据位置。只要可以使用WHERE条件,将须要的数据限制在少数分区中,则效率是很高的。这个策略适用于以正常的方式访问大量数据的时候。可是必须将查询须要扫描的分区个数限制在一个很小的数量

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

什么状况下会出问题

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

NULL值会使分区过滤无效

关于分区表一个容易让人误解的地方就是分区的表达式的值能够是NULL:第一个分区是一个特殊分区。假设按照PARTITION BY RANGE YEAR(order_date)分区,那么全部order_date为NULL或者是一个非法值的时候,记录都会被存放到第一个分区。如今假设有下面的查询:WHERE order_date BETWEEN '2012-01-01' AND '2012-01-31'。实际上,MySQL会检查两个分区,检查第一个分区是由于YEAR()函数在接收非法值的时候可能会返回NULL值,那么这个范围的值可能会返回NULL而被存放到第一个分区了。

若是第一个分区很是大,特别是当使用"全量扫描数据,不要任何索引"的策略时,代价会很是大。并且扫描两个分区来查找列也不是咱们使用分区的初衷。

分区列和索引列不匹配

若是定义的索引列和分区列不匹配,会致使查询没法进行分区过滤。假设在列a上定义了索引,而在列b上进行了分区。由于每一个分区都有其独立的索引,因此扫描列b上的索引就须要扫描每个分区对应的索引。

选择分区的成本可能很高

分区有不少类型,不一样类型分区的实现方式也不一样,因此他们的性能也各不相同。尤为是范围分区,对于回答"这一行属于哪一个分区"、"这些符合查询条件的行在哪些分区"这样的问题成本可能会很是高,由于服务器须要扫描全部的分区定义的列表来找到正确答案。相似这样的线性搜索的效果不高,因此随着分区数的增加,成本会愈来愈高。

打开并锁住全部底层表的成本可能很高

当查询访问分区表的时候,MySQL须要打开并锁住全部的底层表,这是分区表的另外一个开销。这个操做在分区过滤以前发生,因此没法经过分区过滤下降此开销,而且改开销也和分区类型无关,会影响全部的查询。

维护分区的成本可能很高

  • 目前分区实现中的一些其余限制:
  • 全部分区都必须使用相同的存储引擎。
  • 分区函数中可使用的函数和表达式也有一些限制。
  • 某些存储引擎不支持分区。
  • 对于MyISAM的分区表,不能再使用LOAD INDEX INTO CACHE操做
  • 对于MyISAM表,使用分区表时须要打开更多的文件描述符。虽然看起来是一个表,其实背后有不少独立的分区,每个分区对于存储引擎来讲都是一个独立的表。这样即便分区表只占用一个表缓存条目,文件描述符仍是须要多个。所以,即便已经配置了合适的表缓存,以确保不会超过操做系统的单个进程能够打开的文件描述符的个数,但对于分区表而言,仍是会出现超过文件描述符限制的问题。
查询优化

引入分区给查询优化带来了一些新的思路。分区最大的优势就是优化器能够根据分区函数来过滤一些分区。根据粗粒度索引的优点,经过分区过滤一般可让查询扫描更少的数据。

对于访问分区表来讲,很重要的一点是要在WHERE条件中带入分区列,有时候即便看似多余的也要带上,这样就可让优化器可以过滤无须访问的分区。

这个查询访问全部的分区。

MySQL只能在使用分区函数的列自己进行比较时才能过滤分区,而不能根据表达式的值去过滤分区,即便这个表达式就是分区函数也不行。

这里写的WHERE条件中带入的是分区列,而不是基于分区列的表达式,因此优化器可以利用这个条件过滤部分分区。一个很重要的原则是:即使在建立分区时可使用表达式,但在查询时却只能根据列来过滤分区。

合并表

合并表是一种早期的、简单的分区实现,和分区表相比有一些不一样的限制,而且缺少优化。分区表严格来讲是一个逻辑上的概念,用户没法访问底层的各个分区,对用户来讲分区是透明的。

合并表至关于一个容器,里面包含了多个真实表。能够在CREATE TABLE中使用一种特别的UNION 语法来指定包含哪些真实表。

最后创建的合并表和前面的各个真实表字段彻底相同,在合并表中有的索引各个真实子表也有,这是建立合并表的前提条件。各个子表在对应列上都有主键限制,可是最终的合并表中仍然出现了重复值。

INSERT_METHOD=LAST告诉MySQL,将全部的INSERT语句都发送给最后一个表。指定FIRST或者LAST关键字是惟一能够控制行插入到合并表的哪个子表的方式。

INSERT语句的执行结果能够在最终的合并表中看到,也能够在对应的子表中看到:

删除一个合并表,它的子表不受任何影响,而若是直接删除其中一个子表则可能会有不一样的后果,这要视操做系统而定。

  • 在使用CREATE语句建立一个合并表的时候,并不会检查各个子表的兼容性。若是子表的定义稍有不一样,那么MySQL就可能建立出一个后面没法使用的合并表。

  • 根据合并表的特性,不难发现,在合并表上没法使用REPLACE语法,没法使用自增字段。

  • 若是一个查询访问合并表,那么它须要访问全部子表。这会让根据键查找单行的查询速度变慢,若是可以只访问一个对应表,速度确定将更快。限制合并表中的子表数量特别重要,特别是当合并表是某个关联查询的一部分的时候,由于这时访问一个表的记录数可能会将比较操做传递到关联的其余表中,这时减小记录的访问就是减小整个关联操做。

    • 执行范围查询时,须要在每个子表各执行一次,这比直接访问单个表的性能要差不少,并且子表越多,性能越差。
    • 全表扫描和普通表的全表扫描速度相同。
    • 在合并表上作某一键和主键查询时,一旦找到一行数据就会中止。
    • 子表的读取顺序和CREATE TABLE语句中的顺序相同。

合并表的各个子表能够直接被访问,它还具备一些MySQL5.5分区所不能提供的特性:

  • 一个MyISAM表能够是多个合并表的子表。
  • 能够经过直接复制.frm、.MYI、.MYD文件,来实如今不一样的服务器之间复制各个子表。
  • 在合并表中能够很容易的添加新的子表:直接修改合并表的定义就能够了。
  • 能够建立一个合并表,让它只包含须要的数据。
  • 若是想对某个子表作备份、恢复、修改、复制或者别的操做时,能够先将其从合并表中删除,操做结束后再将其加回去。
  • 可使用myisampack来压缩全部的子表。

视图


视图自己是一个虚拟表,不存听任何数据。在使用SQL语句访问视图的时候,它返回的数据是MySQL从其余表中生成的。视图和表是在同一个命名空间,MySQL在不少地方对于视图和表是一样对待的。不过视图和表也有不一样,例如,不能对视图建立触发器,也不能使用DROP TABLE命令删除视图。

CREATE VIEW Oceania AS SELECT * FROM Country WHERE Continent = 'Oceania' WITH CHECK OPTION;

实现视图最简单的方法是将SELECT 语句的结果存放到临时表中。当须要访问视图的时候,直接访问这个临时表就能够了。

SELECT Code,Name FROM Oceania WHERE Name ='BeiJing';
CREATE VIEW Oceania AS SELECT * FROM Country WHERE Continent = 'Oceania' WITH CHECK OPTION;
SELECT Code,Name FROM Oceania WHERE Name ='BeiJing';

这样作会有明显的性能问题,优化器也很难优化在这个临时表上的查询。实现视图更好的方法是,重写含有视图的查询,将视图的定义SQL直接包含进查询的SQL中。

SELECT Code,Name FROM Country WHERE Continent = 'Oceania' AND Name ='BeiJing';

MySQL使用合并算法和临时表算法处理视图。

若是视图中包含GROUP BY、DISTINCT、任何聚合函数、UNION、子查询等,只要没法在原表记录和视图记录中创建一一映射的场景中,MySQL都将使用临时表算法来实现视图。

视图的实现算法是视图自己的属性,和做用在视图上的查询语句无关。

可更新视图

可更新视图是指能够经过更新这个视图来更新这个视图来更新视图涉及的相关表。只要指定了合适的条件,就能够更新、删除甚至向视图写入数据。

UPDATE Oceania SET Population = Population * 1.1 WHERE Name = 'BeiJing';

若是视图定义中包含了GROUP BY、UNION、聚合函数,以及其余一些特殊状况,就不能更新了。视图更新的查询也能够是一个关联语句,可是有个一限制,被更新的列必须来自同一个表中。全部使用临时表算法实现的视图都没法被更新。

视图对性能的影响

在MySQL中某些状况下视图也能够帮助提高性能。并且视图还能够提高性能的方式叠加使用。例如,在重构schema的时候可使用视图,使得在修改视图底层表结构的时候,应用代码还可能继续不报错的运行。

可使用视图实现基于列的权限控制,却不须要真正的在系统中建立列权限,所以没有额外的开销。

CREATE VIEW public.employeeinfo AS SELECT firstname,lastname FROM private.employeeinfo;
GRANT SELECT ON public.* TO pulic_user;

咱们这里使用链接ID做为视图名字的一部分来避免冲突。

MySQL先执行视图的SQL生成临时表,而后再将sales_per_day和临时表关联。这里的WHERE子句中的BETWEEN条件并不能下推到视图中,因此视图在建立的时候仍须要将全部的数据都放到临时表中。并且临时表中不会有索引。

若是打算使用视图来提高性能,须要作比较详细的测试。即便是合并算法实现的视图也可能会有额外的开销,并且视图的性能很难预测。

视图的限制

MySQL还不支持物化视图(物化视图是指将视图结果数据放在一个能够查看的表中,并按期从原始表中刷新数据到这个表中)。MySQL也不支持在视图中建立索引。

能够找到定义视图原始SQL语句

外键约束


InnoDB是目前MySQL中惟一支持外键的内置存储引擎。

使用外键是有成本的。好比外键一般都要求每次在修改数据时都要在另一张表中多执行一次检查操做。在某些场景下,外键会提高一些性能。若是想确保两个相关表始终有一致的数据,那么使用外键比在应用程序检查一致性的性能要高得多,外键在相关数据的删除和更新上,比应用在维护要更高效。

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

对于相关数据的同时更新外键更合适,可是若是外键只是用做数值约束,那么触发器或者显式地限制取值会更好些。

若是只是使用外键约束,那一般在应用程序里实现该约束会更好。外键会带来很大的额外消耗。

在MySQL内部存储代码


MySQL容许经过触发器、存储过程、函数的形式来存储代码。从MySQL5.1开始,还能够在定时任务中存放代码,这个定时任务也被称为"事件"。存储过程和存储函数都被统称为"存储函数"。

不一样类型的存储代码的主要区别在于其执行的上下文——也就是输入和输出。存储过程和存储函数均可以接收参数而后返回值,可是触发器和事件不行。

存储代码的优势:

  • 它在服务器内部执行,离数据最近,另外在服务器上执行还能够节省宽带和网络延迟。
  • 这是一种代码重用。能够方便地统一业务规则,保证某些行为老是一致,因此也能够为应用提供必定的安全性。
  • 它能够简化代码的维护和版本更新。
  • 他能够帮助提高安全,好比提供更细粒度的权限控制。
  • 服务器端能够缓存存储过程的执行计划,这对于须要反复调用的过程,会大大下降消耗。
  • 在服务器端部署的,备份、维护均可以在服务器端完成。
  • 它能够在应用开发和数据库开发人员之间更好地分工。

存储代码的缺点:

  • MySQL自己没有提供好用的开发和调试工具,因此编写MySQL的存储代码比其余数据库要更难些。
  • 较之应用程序的代码,存储代码效率要稍微差些。存储代码中可使用的函数很是有限,使用存储代码很难编写复杂的字符串维护功能,也能难实现太复杂的逻辑。
  • 存储代码可能会给应用程序代码的部署带来额外的复杂性。
  • 由于存储程序都部署在服务器内,因此可能有安全隐患。若是将非标准的加密功能放在存储过程当中,那么若数据库被攻破,数据也就泄漏了。
  • 存储过程会给数据库服务器增长额外的压力,而数据库服务器的性能扩展性相比应用服务器要差不少。
  • MySQL并无什么选项能够控制存储过程的资源消耗,因此在存储过程当中的一个小错误,可能直接把服务器拖死。
  • MySQL的存储代码功能还很是很是弱。
  • 调试MySQL的存储过程是一件很困难的事情。
  • 它和基于语句的二进制日志复制合做得并不太好。
存储过程和函数

MySQL的架构自己和优化器的特性使得存储代码有一些自然限制,他的性能也必定程度受限于此。

  • 优化器没法使用关键字DETERMINISTIC来优化单个查询中屡次调用存储函数的状况。
  • 优化器没法评估存储函数的执行成本。
  • 每一个链接都有独立的存储过程的执行计划缓存。若是有多个链接须要调用同一个存储过程,将会浪费缓存空间来反复缓存一样的执行计划。
  • 存储程序和复制是一组诡异的组合。最好不要复制对存储程序的调用。

咱们一般会但愿存储程序越小、越简单越好。但愿将更加复杂的处理逻辑交给上层的应用实现,一般这样会使代码更易读、易维护,也会更灵活。

存储过程要快不少,很大程度由于它无须网络通讯开销、解析开销和优化器开销等。

触发器

触发器可让你在执行INSERT、UPDATE、或者DELETEA的时候,执行一些特定的操做。能够在MySQL中指定是在SQL语句执行以前触发仍是在执行后触发。触发器自己没有返回值,不过他们能够读取或者改变触发SQL语句所影响的数据。

由于触发器能够减小客户端和服务器之间的通讯,因此触发器能够简化应用逻辑,还能够提升性能。

MySQL触发器的实现很是简单,因此功能也有限。

  • 对每个表的每个事件,最多只能定义一个触发器。

  • MySQL只支持"基于行的触发"——也就是说,触发器始终是针对一条记录的,而不是针对整个SQL语句的。若是变动的数据集很是大的话,效率会很低。

  • 触发器能够掩盖服务器背后的工做,一个简单的SQL语句背后,由于触发器,可能包含了不少看不见的工做。

  • 触发器的问题也很难排查,若是某个性能问题和触发器相关,会很难分析和定位。

  • 触发器可能致使死锁和锁等待。若是触发器失败,那么原来的SQL语句也会失败。

由于性能的缘由,不少时候没法使用触发器来维护汇总和缓存表。使用触发器而不是批量更新的一个重要缘由就是,使用触发器能够保证数据老是一致的。

触发器并不能必定保证更新的原子性。一个触发器在更新MyISAM表的时候,若是遇到什么错误,是没有办法作回滚操做的。

在InnoDB表上的触发器是在同一个事物中完成的,因此它们执行的操做是原子的,原子操做和触发器操做会同时失败或者成功。不过,若是在InnoDB表上建触发器去检查数据的一致性,须要特别当心MVCC,稍不当心,你可能会得到错误的结果。你想实现外键约束,可是不打算使用InnoDB的外键约束。若打算编写一个BEFOREN INSERT触发器来检查写入的数据对应列在另外一个表中是不是存在的,但若你在触发器中没有使用SELECT FOR UPDATE,那么并发的更新语句可能会马上更新对应记录,致使数据不一致。

触发器很是有用,尤为是实现一些约束、系统维护任务、以及更新反范式化数据的时候。还可使用触发器来记录数据变动日志。这对实现一些自定义的复制会很是方便。

事件

事件指定MySQL在某个时候执行一段SQL代码,或者每隔一个时间间隔执行一段SQL代码。一般,会把复杂的SQL都封装到一个存储过程当中,这样事件在执行的时候只须要作一个简单的CALL调用。

事件在一个独立事件调度线程中被初始化,这个线程和处理链接的线程没有任何关系。它不接受任何参数,也没有任何的返回值。能够在MySQL的日志中看到命令的执行日志。还能够在表INFORMATION_SCHEMA.EVENTS中看到各个事件状态。

事件实现机制自己的开销并不大,可是事件须要执行SQL,则肯能会对性能有很大的影响。更进一步,事件和其余的存储程序同样,在和基于语句的复制一块儿工做时,可也能会触发一样的问题。事件的一些典型应用包括按期地维护任务、重建缓存、构建汇总表来模拟物化视图,或者存储用于监控和诊断的状态值。

在存储程序中保留注释

存储过程、存储函数、触发器、事件一般都会包含大量的重要代码,在这些代码中加上注释很是有必要。

一个将注释存储到存储程序中的技巧就是使用版本相关的注释,由于这样的注释可能被MySQL服务器执行。服务器和客户端都知道这不是普通的注释,因此也就不会删除这些注释。

游标

MySQL在服务器端提供只读、单向的游标,并且只能在存储过程或者更底层的客户端API中使用。由于MySQL游标中指向的对象都是存储在临时表中而不是实际查询到的数据,因此MySQL游标老是只读的。它能够逐行指向查询结果,而后让程序作进一步的处理。在一个存储过程当中,能够有多个游标,也能够在循环中"嵌套"地使用游标。

由于是使用临时表实现的,因此它在效率上给开发人员一个错觉。当你打开一个游标的时候须要执行整个查询。

Oracle或者SQL Server的用户不会认为这个存储过程有什么问题,可是在MySQL中,这会带来不少没必要要的额外操做。

游标也会让MySQL执行一些额外的I/O操做,而这些操做的效率可能很是低。由于临时内存表不支持BLOB和TEXT类型,若是游标返回的结果包含这样的列的话,MySQL就必须建立临时磁盘表来存放。当临时表大于tmp_table_size的时候,MySQL也仍是会在磁盘上建立临时表。

绑定变量


当建立一个绑定变量SQL时,客户端向服务器发送了一个SQL语句的原型。服务器端收到这个SQL语句框架后,解析并存储这个SQL语句的部分执行计划,返回给客户端一个SQL语句处理句柄。之后每次执行这类查询,客户端都指定使用这个句柄。

绑定变量的SQL,使用问好标记能够接收参数的位置,当真正须要执行具体查询的时候,则使用具体值代替这些问号。

MySQL在使用绑定变量的时候能够更高效地执行大量的重复语句:

  • 在服务器端只须要解析一次SQL语句。
  • 在服务器端某些优化器的工做只须要执行一次,由于他会缓存一部分的执行计划。
  • 以二进制的方式只发送参数和句柄,比起每次都发送ASCII码文本效率更高,一个二进制的日期字段只须要三个字节,但若是是ASCII码则须要十个字节。
  • 仅仅是参数——而不是整个查询语句——须要发送到服务器端,因此网络开销会更小。
  • MySQL在存储参数的时候,直接将其存放到缓存中,再也不须要再内存中屡次复制。

绑定变量相对也更安全。无须再应用程序中处理转义,一则更简单了,二则也大大减小了SQL注入和攻击的风险。

绑定变量的优化

对使用绑定变量的SQL,MySQL可以缓存其部分执行计划,若是某些执行计划须要根据传入的参数来计算时,MySQL就没法缓存这部分的执行计划。

在准备阶段 服务器解析SQL语句,移除不可能的条件,而且重写子查询。

在第一次执行的时候

若是可能的话,服务器先简化嵌套循环的关联,并将外关联转化成内关联。

在每次SQL语句执行时

服务器作如下事情

  • 过滤分区
  • 若是可能的话,尽可能移除COUNT()、MIN()和MAX()
  • 移除常数表达式
  • 检测常量表
  • 作必要的等值传播
  • 分析和优化ref,range和索引优化等访问数据的方法
  • 优化关联顺序。
SQL接口的绑定变量

MySQL支持了SQL接口的绑定变量。不使用二进制传输协议也能够直接以SQL的方式使用绑定变量。

最主要的用途就是在存储过程当中使用。在MySQL5.0版本中,就能够在存储过程当中使用绑定变量,其语法和前面介绍的SQL接口和绑定变量相似。这意味着,能够在存储过程当中构建并执行"动态"的SQL语句,这里的"动态"是指能够经过灵活的拼接字符串等参数构建SQL语句。

编写存储过程时,SQL接口的绑定变量一般能够很大程度地帮助咱们调试绑定变量,若是不是在存储过程当中,SQL接口的绑定变量就不是那么有用了。由于SQL接口的绑定变量,它既没有使用二进制传输协议,也没有可以节省带宽,相反还老是须要增长至少一额外网络传输才能完成一次传输。

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

三种绑定变量的区别:

  • 客户端模拟的绑定变量

客户端的驱动程序接收一个带参数的SQL,在将指定的值带入其中,最后将完整的查询发送到服务器端。

  • 服务器端的绑定变量

客户端使用特殊的二进制协议将带参数的字符串发送到服务器端口,而后使用二进制协议将具体的参数值发送给服务器端并执行。

  • SQL接口的绑定变量

客户端先发送一个带参数的字符串到服务端,这相似于使用PREPARE的SQL语句,而后发送设置参数的SQL,最后使用EXECUTE来执行SQL。全部这些都使用普通的文本传输协议。

用户自定义函数


MySQL支持用户自定义函数(UDF)。存储过程只能使用SQL来编写,而UDF没有这个限制,你可使用支持C语言调用约定的任何编程语言来实现。

UDF必须事先编译好并动态连接到服务器上,这种平台相关性使得UDF在不少方面都很强大。UDF速度很是快,并且能够访问大量操做系统的功能,还可使用大量函数库。

能力越大,责任越大。因此在UDF中的一个错误极可能会让服务器直接崩溃,甚至扰乱服务器的内存或者数据,另外,全部C语言具备的潜在风险,UDF也都有。

插件


  • 存储过程插件 存储过程插件能够帮你在存储过程运行后在处理一次运行结果。
  • 后台插件 后台插件可让你的程序在MySQL中运行,能够实现本身的网络监听、执行本身的按期任务。
  • INFORMATION_SCHEMA插件 这个插件能够提供一个新的内存INFORMATION_SCHEMA表。
  • 全文解析插件 这个插件提供一种处理文本的功能,能够根据本身的需求来对一个文档进行分词,因此若是给定一个PDF文档目录,可使用这个插件对这个文档进行分词处理。也能够用此来加强查询执行过程当中的词语匹配功能。
  • 审计插件 审计插件在查询执行的过程当中的某些固定点被调用,因此它能够用做记录MySQL的事件日志。
  • 认证插件 认证插件既能够在MySQL客户端也能够在它的服务端,可使用这类插件来扩展MySQL的认证功能。

字符集和校对


字符集是指一种从二进制编码到某类字符符号的映射,能够参考如何使用一个字节表来表示英文字母。"校对"是指一组用于某个字符集的排序规则。MySQL4.1和以后的版本中,每一类编码字符都有其对应的字符集和校对规则。

#####MySQL如何使用字符集

每种字符集均可能有多种校对规则,而且都有一个默认的校对规则。每一个校对规则都是针对某个特定的字符集的,和其余的字符集没有关系。校对规则和字符集老是一块儿使用。

只有基于字符的值才真正的"有"字符集的概念。对于其余类型的值,字符集只是一个设置,指定用哪种字符集来作比较或者其余操做。

MySQL的设置能够分为两类:建立对象时的默认值、在服务器和客户端通讯时的设置。

建立对象时的默认设置

MySQL服务器有默认的字符集和校对规则,每一个数据库也有本身的默认值,每一个表也有本身的默认值。这是一个逐层继承的默认设置,最终最靠底层的默认设置将影响你建立的对象。

  • 建立数据库的时候,将根据服务器上的character_set_server,设置来设定该数据库的默认字符集。
  • 建立表的时候,将根据数据库的字符集设置指定这个表的字符集设置。
  • 建立列的时候,将根据表的设置指定列的字符集设置。

真正存放数据的是列,更高"阶梯"的设置只是指定默认值。一个表的默认字符集设置没法影响存储在这个表中某个列的值。只有当建立列而没为列指定字符集的时候,若是没有指定字符集,表的默认字符集才有做用。

服务器和客户端通讯时的设置

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

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

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

MySQL如何比较两个字符串的大小

若是比较的两个字符串的字符集不一样,MySQL会先将其转成同一个字符集再进行比较。若是两个字符集不兼容的话,则会抛出错误。MySQL5.0和更新的版本常常会作这样的瘾式转换。

一些特殊状况

  • 诡异的character_set_database设置 character_set_database设置的默认值和默认数据库的设置相同。当改变默认数据库的时候,这个变量也会跟着变。
  • LOAD DATA INFINE 当使用LOAD DATA INFINE的时候,数据库老是将文件中的字符按照字符集character_set_database来解析。在MySQL5.0和更新版本中,能够在LOAD DATA INFINE中使用子句CHARACTER SET来设定子集,不过最好不要依赖这个设定。咱们发现制定字符集最好的方式是先使用USE指定数据库,在执行SET NAMES来设定字符集,最后再加载数据。MySQL在加载数据的时候,老是以一样的字符集处理全部数据,而无论表中的列是否有不一样的字符集设定。
  • SELECT INTO OUTFILE MySQL会将SELECT INTO OUTFILE的结果不作任何转码地写入文件。
  • 嵌入式转义序列 MySQL会跟据character_set_client的设置来解析转义序列,即便是字符串中包含前缀或者COLLATE子句也同样。这是由于解析器在处理字符串的转义字符时,彻底不关心校对规则——对解析器来讲,前缀并非一个指令,它只是一个关键字而已。
选择字符集和校对规则

对于校对规则一般须要考虑的一个问题是,是否以大小写敏感的方式比较字符串,或者是以字符串编码的二进制值来比较大小。他们对应的校对规则的前缀分别是_cs、_ci和_bin,根据须要很容易选择。大小写敏感和二进制校对规则的不一样之处在于,二进制校对规则直接使用字符的字节进行比较,而大小写敏感的校对规则在多字节字符集时,有更复杂的比较规则。

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

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

全文索引


全文索引能够支持各类字符内容的搜索(包括CHAR、VARCHAR和TEXT类型),也支持天然语言搜索和布尔搜索。标准的MySQL中,只有MyISAM引擎支持全文索引。在MySQL5.6中,InnoDB已经实验性质的支持全文索引了。MyISAM对全文索引的支持有不少限制,例如表级别锁对性能的影响、数据文件的崩溃、崩溃后的恢复等。

MyISAM的全文索引是一类特殊的B-Tree索引,共有两层。第一层是全部关键字,而后对于每个关键字的第二层,包含的是一组相关的"文档指针"。

  • 停用词列表中的词都不会被索引。默认的停用词根据通用英语的使用来设置,可使用参数ft_stopwrod_file指定一组外部文件来使用自定义的停用词。
  • 对于长度大于ft_min_word_len的词语和长度小于ft_max_word_len的语句,都不会被索引。
天然语言的全文索引

天然语言搜索引擎将计算每个文档对象和查询的相关度。这里,相关度是基于匹配的关键词个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。

全文索引的语法和普通查询略有不一样。能够根据WHERE子句中的MATCH AGAINST来区分查询是否使用全文索引。

函数MATCH()将返回关键词匹配的相关度,是一个浮点数字。在一个查询中使用两次MATCH()函数并不会有额外的消耗,MySQL会自动识别并只进行一次搜索。若是将MATCH()函数放到ORDER BY子句中,MySQL将会使用文件排序。

在MATCH()函数中指定的列必须和在全文索引中指定的列彻底相同,不然就没法使用全文索引。这是由于全文索引不会记录关键字是那一列的。

布尔全文索引

短语搜索的速度会比较慢。只使用全文索引是没法判断是否精确匹配短语的,一般还须要查询原文肯定记录中是否包含完整的短语。因为须要进行回表过滤,因此速度会很慢。

MySQL5.1中全文索引的变化

在MySQL5.1Z中引入了一些和全文索引相关的改进,包括一些性能上的提高和新增插件式的解析。

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

MySQL全文索引中只有一种判断相关性的方法:词频。索引也不会记录索引词在字符串中的位置,因此位置也就没法用在相关性上。

数据量大小也是一个问题。MySQL的全文索引只有所有在内存中的时候,性能才很是好。若是内存没法装载所有索引,那么搜索速度可能会很是慢。当你使用精确短语搜索时,想要好的性能,数据和索引都须要在内存中。相比其余的索引类型,当INSERT、UPDATE和DELETE操做进行时,全文索引的操做代价都很大:

  • 修改一段文本中的100个单词,须要100次索引操做,而不是一次。
  • 通常来讲列长度并不会太影响其余的索引类型,可是若是是全文索引,三个单词的文本和10000个单词的文本,性能可能会相差几个数量级。
  • 全文索引会有更多的碎片,可能须要作更多的OPTIMIZE TABLE操做。

全文索引还会影响查询优化器的工做。索引选择、WHERE子句、ORDER BY都有可能不是按照你所预想的方式来工做

  • 若是查询中使用了MATCH AGAINST子句,而对应列上又有可用的全文索引,那么MySQL就必定会使用这个全文索引。即便有其余的索引,MySQL不会去比较哪一个索引的性能更好。因此,即便这时有更合适的索引可使用,MySQL仍会置之不理。
  • 全文索引只能用做全文搜索匹配。任何其余操做,如WHERE条件比较,都必须在MySQL完成全文搜索返回记录后才能进行。
  • 全文索引不存储索引列的实际值。也就不可能用做索引覆盖扫描。
  • 除了相关性排序,全文索引不能用做其余的排序。若是查询须要作相关性之外的排序操做,都须要使用文件排序。

假若有一百万个文档记录,在文档的做者author字段上有一个普通的索引,在文档内容字段content上有全文索引。如今咱们要搜索做者是123,文档中又包含特定词语的文档。不少人可能会按照下面的方式来写查询语句:

...... WHERE MATCH(content) AGAINST('High Performance MySQL') AND author = 123;

而实际上,这样作的效率很是低。由于这里使用了MATCH AGAINST,并且刚好上面有全文索引,因此MySQL优先选择使用全文索引,即先搜索全部的文档,查找是否有包含关键词的文档,而后返回记录看看做者是不是123。因此这里也就没有使用author字段上的索引。

一个代替方案是将author列包含到全文索引中。能够在author列的值前面附上一个不常见的前缀,而后将这个带前缀的值存放到一个单独的filters列中,并单独维护该列。

...... WHERE MATCH(content,filters) AGAINST('High Performance MySQL + author_id_123' IN BOOLEAN MODE);

若是author列的选择性很是高,那么MySQL可以根据做者信息很快地将须要过滤的文档记录限制在一个很小的范围内,这个查询的效率也就很是好。若是author列的选择性很低,那么这个替代方案的效率会比前面那个更糟。

使用全文索引的时候,一般会返回大量结果并产生大量随机I/O,若是和GROUP BY一块儿使用的话,还须要经过临时表或者文件进行排序分组,性能会很是很是糟糕。

全文索引的配置和优化

全文索引的平常维护可以大大提高性能。"双B-Tree"的特殊结构、在某些文档中比其余文档要包含多得多的关键字,这都使得全文索引比起普通索引有更多的碎片问题。因此须要常用OPTIMIZE TABLE来减小碎片。若是应用是I/O密集型的,那么按期的进行全文索引重建可让性能提高不少。

若是但愿全文索引可以高校地工做,还须要保证索引缓存足够大,从而保证全部的全文索引都能缓存在内存中。一般,能够为全文索引设置单独的键缓存,保证不会被其余的缓存缓存挤出内存。

提供一个好的停用词。默认的停用词表对经常使用英语来讲可能很不错,可是若是是其余语言或者某些专业文档就不合适了,例如技术文档。

忽略一些过短的单词也能够提高全文索引的效率。索引单词的最小长度能够经过参数ft_min_word_len配置。修改该参数能够过滤更多的单词,让查询速度更快,可是也会下降精确度。

当向一个有全文索引的表中导入大量数据的时候,最好先经过命令DISABLE KEYS来禁用全文索引,而后在导入结束后使用ENABLE KEYS来创建全文索引。由于全文索引的更新是一个消耗很大的操做,因此上面的细节会帮你节省大量时间。

若是数据集特别大,则须要对数据进行手动分区,而后将数据分布到不一样的节点,再作并行的搜索。

分布式(XA)事物


存储引擎的事物特性可以保证在存储引擎级别实现ACID,而分布式事务则让存储引擎级别的ACID能够扩展到数据库层面,甚至能够扩展到多个数据库之间——这须要经过两阶段提交实现。MySQL5.0和更新版本的数据库已经开始支持XA事务了。

XA事务中须要一个事务协调器来保证全部的事务参与者都完成了准备工做。若是协调器收到全部的参与者都准备好的消息,就会告诉全部的事务能够提交了。

MySQL中有两种XA事务。一方面,MySQL能够参与到外部的分布式事务中;另外一方面,还能够经过XA事务来协调存储引擎和二进制日志。

内部XA事物

MySQL自己的插件式架构致使在其内部须要使用XA事物。MySQL中各个存储引擎是彻底独立的,彼此不知道对方的存在,因此一个跨存储引擎的事物就须要一个外部的协调者。若是不使用XA协议,例如跨存储引擎的事务提交就只是顺序地要求每一个存储引擎各自提交。若是在某个存储提交过程当中发生系统崩溃,就会破坏事物的特性。

XA事务为MySQL带来巨大的性能降低。从MySQL5.0开始,它破坏了MySQL内部的"批量提交",使得MySQL不得不进行屡次额外的fsync()调用。

外部XA事物

MySQL可以做为参与者完成一个外部的分布式事务。但它对XA协议支持并不完整。由于通讯延迟和参与者自己可能失败,因此外部XA事务比内部消耗会更大。若是在广域网中使用XA事务,一般会由于不可预测的网络性能致使事务失败。若是有太多不可控因素,则最好避免使用XA事务。任何可能让事务提交发生延迟的操做代价都很大,由于它影响的不只仅是本身自己,它还会让全部参与者都在等待。

查询缓存


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

查询缓存系统会跟踪查询中涉及的每一个表,若是这些表发生变化,那么和这个表相关的全部的缓存数据都将失效。这种机制效率看起来比较低,由于数据表变化时颇有可能对应的查询结果并无更改,可是这种简单实现代价很小,而这点对于一个很是繁忙的系统来讲很是重要。

查询缓存对应用程序是彻底透明的。应用程序无须关系MySQL是经过查询缓存返回的结果仍是实际执行返回的结果。

随着如今的通用服务器愈来愈强大,查询缓存被发现是一个影响服务器扩展性的因素。它可能成为整个服务器的资源竞争单点,在多核服务器上还可能致使服务器僵死。

MySQL如何判断缓存命中

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

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

当查询语句中有一些不肯定的数据时,则不会被缓存。若是查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql库中的系统表,或者任何包含列级别权限的表,都不会被缓存。

在检查查询缓存的时候,尚未解析SQL语句,因此MySQL并不知道查询语句中是否包含这类函数。在检查查询缓存以前,MySQL只作一件事情,就是经过一个大小写不敏感的检查看看SQL语句是否是以SEL开头。

若是查询语句中包含任何的不肯定函数,那么在查询缓存中是不可能找到缓存结果的。

打开查询缓存对读和写操做都会带来额外的消耗:

  • 读查询在开始以前必须先检查是否命中缓存。
  • 若是这个读查询能够被缓存,那么当完成执行后,MySQL若发现查询缓存中没有这个查询,会将其结果存入查询缓存,这会带来额外的系统消耗。
  • 这对写操做也会有影响,由于当向某个表写入数据的时候,MySQL必须将对应表的全部缓存都设置失效。

若是查询缓存使用了大量的内存,缓存失效操做就可能成为一个很是严重的问题瓶颈。若是缓存中存放了大量的查询结果,那么缓存失效操做时整个系统均可能会僵死一下子。由于这个操做是靠一个全局锁操做保护的,全部须要作该操做的查询都要等待这个锁,并且不管是检测是否命中缓存、仍是缓存失效检测都须要等待这个全局锁。

查询缓存如何使用内存

MySQL用于查询缓存的内存被分红一个个的数据快,数据块是变长的。没一个数据块中,存储了本身的类型、大小和存储的数据自己,还外加指向前一个和后一个数据块的指针。数据块的类型有:存储查询结果、存储查询、和数据表的映射、存储查询文本等等。

当有查询结果须要缓存的时候,MySQL先从大的空间块中申请一个数据块用于存储结果。这个数据块须要大于参数query_cache_min_res_unit的配置,即便查询结果远远小于此,仍须要至少申请query_cache_min_res_unit空间。由于须要在查询开始返回结果的时候就分配空间,而此时是没法预知查询结果到底多大,因此MySQL没法为每个查询结果精确分配大小刚好匹配的缓存空间。

当须要缓存一个查询结果的时候,它先选择一个尽量小的内存块,而后将结果存入其中。若是数据块所有用完,但仍有剩余数据须要存储,那么MySQL会申请一块新数据块——仍然是尽量小的数据块——继续存储结果数据。当查询完成时,若是申请的内存空间还有剩余,MySQL会将其释放,并放入空闲内存部分。

分配内存块不是指经过函数malloc()向操做系统申请内存,这个操做只在初次建立查询缓存的时候执行一次。分配内存块是指空闲块列表中找到一个合适的内存块,或者从正在使用的、待淘汰的内存块中回收再使用。也就是说,这里MySQL本身管理一大块内存,而不依赖操做系统的内存管理。

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

并非什么状况下查询缓存都会提升系统性能的。缓存和失效都会带来额外的消耗,因此只有当缓存带来的资源节约大于其自己的资源消耗时才会给系统带来性能提高。

理论上,能够经过观察打开或者关闭查询缓存时候的系统效率来决定是否须要开启查询缓存。关闭查询缓存时,每一个查询都须要完整的执行,每一次写操做执行完成后马上返回;打开查询缓存时,每次读请求先检查缓存是否命中,若是命中则马上返回,不然就完整的执行查询,每次写操做则须要检查查询缓存中是否须要失效的缓存,而后在返回。

评估打开查询缓存是否可以带来性能提高却并不容易。还有一些外部的因素须要考虑,例如查询缓存能够下降查询执行的时间,可是却不能减小查询结果传输的网络消耗,若是这个消耗是系统的主要瓶颈,那么查询缓存的做用也很小。

MySQL在SHOW STATUS中只能提供一个全局的性能标准,因此很难根据此来判断查询缓存是否可以提高性能。

对于那些须要消耗大量资源的查询一般都是很是适合缓存的。不过须要注意的是,涉及的表上UPDATE、DELETE和INSERT操做相比SELECT来讲要很是少才行。

一个判断查询缓存是否有效的直接数据就是命中率,就是使用查询缓存返回结果占总查询的比率。Qcache_hits/(Qcache_hits+Com_select)。

  • 查询语句没法被缓存,多是由于查询中包含一个不肯定的函数,或者查询结果太大而没法缓存。

  • MySQL从未处理这个查询,因此结果也从未曾被缓存过。

  • 以前缓存了查询结果,可是因为查询缓存的内存用完了,MySQL须要将某些缓存"逐出",或者因为数据表被修改致使缓存失效。

  • 查询缓存尚未完成预热。也就是说,MySQL尚未机会将查询结果都缓存起来。

  • 查询语句以前从未执行过。若是你的应用程序不会重复执行一条查询语句,那么即便完成预热仍然会有不少缓存未命中。

  • 缓存失效操做太多了 缓存碎片、内存不足、数据修改都会形成缓存失效。若是配置了足够的缓存空间,并且query_cache_min_res_unit设置也合理的话,那么缓存失效应该主要是数据修改致使的。

"命中率"和"INSERTS和SELECT比率"都没法直观地反应查询缓存的效率。另外一个指标:"命中和写入"比率,即Qcache_hits和Qcache_inserts的比值。根据经验来看,这个比值大于3:1的一般查询缓存是有效的,不过这个比率最好可以达到10:1。

一般能够经过观察查询缓存内存的实际使用状况,来肯定是否须要缩小或者扩大查询缓存。若是查询缓存空间长时间都有剩余,那么建议缩小;若是常常因为空间不足而致使查询缓存失效,那么则须要增大查询缓存。另外还须要和系统的其余缓存一块儿考虑。

最好的判断查询缓存是否有效的办法仍是经过查看某类查询时间消耗是否增大或者减小来判断。

如何配置和维护查询缓存
  • query_cache_type 是否打开查询缓存。能够设置成OFF、ON或DEMAND。DEMAND表示只有在查询语句中明确写明SQL_CACHE的语句才放入查询缓存。
  • query_cache_size 查询缓存使用的总内存空间,单位是字节。这个值必须是1024的整数倍。
  • query_cache_min_res_unit 在查询缓存中分配内存块时的最小单位。
  • query_cache_limit MySQL可以缓存的最大查询结果。
  • query_cache_wlock_invalidate若是某个数据表被其余的链接锁住,是否仍然从查询缓存中返回结果。

减小碎片 提升查询缓存的使用率

InnoDB和查询缓存

事务是否能够访问查询缓存取决于当前事务ID,以及对应的数据表上是否有锁。每个InnoDB表的内存数据字典都保存了一个事物ID号,若是当前事务ID小于该事务ID,则没法访问查询缓存。

若是表上有任何的锁,那么对这个表的任何查询语句都是没法被缓存的。

  • 全部大于该表计数器的事务才可使用查询缓存。
  • 该表的计数器并非直接更新为对该表进行加锁的事务ID,而是被更新成一个系统事务ID。
通用查询缓存优化
  • 用多个小表代替一个大表对查询缓存有好处。
  • 批量写入时只须要作一次缓存失效,因此相比单条写入效率更好。
  • 由于缓存空间太大,在过时操做的时候可能会致使服务器僵死。
  • 没法在数据库或者表级别控制查询缓存,可是能够经过SQL_CACHE和SQL_NO_CACHE来控制某个SELECT语句是否须要进行缓存。
  • 对于写密集型的应用来讲,直接禁用查询缓存可能会提升系统的性能。
  • 由于对互斥信号量的竞争,有时直接关闭查询缓存对读密集型的应用也会有好处。
查询缓存的替代方案

客户端的缓存能够很大程度上帮你分担MySQL服务器的压力。