MySQL高级特性
1. 分区表:分区表是一种粗粒度的、简易的索引策略,适用于大数据量的过滤场景。最适合的场景是,在没有合适的索引时,对几个分区进行全表扫描,或者是只有一个分区和索引是热点,而且这个分区和索引
能够在内存中;限制单表分区数不要超过150个,并且注意某些导致无法分区过滤的细节,分区表对单条记录的查询并没有什么优势,需要注意这类查询的性能。
1). 对于用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成。实现分区的代码实际上是对一组底层表的句柄对象(Handler)的封装。
2). MySQL实现分区的方式--对底层表的封装--意味着索引也是按照分区的子表定义的,而没有全局索引。
3). MySQL在创建表的时候使用PARTITION BY子句定义每个分区存放的数据。在执行查询的时候,优化器会根据分区过滤那些没有我们需要的数据的分区,这样查询就无法扫描所有分区--只要查找包含
需要数据的分区就可以了。
4). 下面场景中分区可以起到很大的作用:
a. 表非常大以至于无法全部放到内存中,或者只在表的最后部分有热点数据,其他均是历史数据。
b. 分区表的数据更容易维护。
c. 分区表的数据可以分布在不同的物理设备上,从而高效的利用多个硬件设备。
e. 可以使用分区表来避免某些特殊的瓶颈,例如InnoDB的单个索引的互斥访问、ext3文件系统的inode锁竞争。
f. 如果需要,还可以备份和恢复独立的分区,这在非常大的数据集的场景下效果非常好。
5). 分区本身也有一些限制,下面是其中比较重要的几点:
a. 一个表最多只有1024个分区
b. 在MySQL5.1中,分区表达式必须是整数或者返回整数表达式。在MySQL5.5中,某些场景可以直接使用列进行分区。
c. 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来。
d. 分区中无法使用外键约束。
6). 为了保证大数据量的可扩展性,一般有下面两个策略:
a. 全量扫描数据,不要任何索引:可以使用简单的分区方式存放表,不要任何索引,根据分区的规则大致定位需要的数据位置。只要能够使用WHERE条件,将需要的数据限制在少数分区中,则效率是很高的。
b. 索引数据,并分离热点:如果数据有明显的"热点",而除了这部分数据,其他数据都很少被访问到,那么可以将这部分热点数据单独放在一个分区中,让这个分区的数据能够有机会都缓存在内存中。这样查询
就可以访问一个很小的分区表,能够使用索引,也能够有效地使用缓存。
7). 分区可能遇到的问题:
a. NULL值会使分区过滤无效:
b. 分区列和索引列不匹配:如果定义的索引索引列和分区列不匹配,会导致查询无法进行分区过滤。
c. 选择分区的成本可能很高:可以通过限制分区数量来缓解此问题,根据经验,对大多数系统来首,100个所有的分区是没有问题的。
d. 打开并锁住所有底层表的成本可能很高:这个操作在分区过滤之前发生。
e. 维护分区的成本可能很高:例如重组分区。
8). 查询优化:
a. 分区最大的优点就是优化器可以根据分区函数来过滤一些分区。根据粗粒度索引的优势,通过分区过滤通常可以让查询扫描更少的数据(在某些场景下)。所以,对于访问分区表来说,很重要的一点是要在WHERE
条件带入到分区列中。有时候即使看似多余的也要带上,这样就可以让优化器过滤掉无需访问的分区。如果没有这些条件,MySQL就需要让对应存储引擎访问这个表的所有分区,如果表非常大,可能会非常慢。
b. MySQL只能在使用分区函数的列本身进行比较时才能过滤分区,而不能根据表达式的值去过滤分区,即使这个表达式就是分区函数也不行。
c. 一个很重要的原则是:即便在创建分区时可以使用表达式,但在查询时却只能根据列来过滤分区。
2. 视图:对于好几个表的复杂查询,使用视图有时候会大大简化问题。当视图使用临时表时,无法将WHERE条件下推到各个具体的表中,也不能使用任何索引,需要特别注意这列查询的性能。如果为了便利,使用视图是很
合适的。
1). 视图本身是一个虚拟表,不存放任何数据。在使用SQL语句访问视图的时候,它返回的数据是MySQL从其他表中生成的。
2). 实现视图最简单的方法是将SELECT语句的结果存放到临时表中。当需要访问视图的时候,直接访问这个临时表就可以了。
CREATE VIEW Oceania AS SELECT * FROM Country WHERE Continent='Oceania' WITH CHECK OPTION;
3). 处理视图有两种算法,称为合并算法和临时表算法,如果可能,会尽可能地使用合并算法。MySQL甚至可以嵌套地定义视图,也就是在一个视图上再定义另一个视图。
4). 如果视图中包含GROUP BY、DISTINCT、任何聚合函数、UNION、子查询等,只要无法在原表记录和视图记录中建立一一映射的场景中,MySQL都将使用临时表算法来实现视图。
2.1 可更新视图:可更新视图是指可以通过更新这个视图来更新视图涉及的相关表。只要指定了合适的条件,就可以更新、删除甚至向视图中写入数据。临时表视图不能被更新。
2.2 视图对性能的影响:多数人认为视图不能提升性能,实际上,在MySQL中某些情况下视图也可以帮助提升性能。而且视图还可以和其他提升性能的方式叠加使用。例如:在重构schema的时候可以使用视图,使得在
修改视图底层表结构的时候,应用代码还能继续不报错的运行。
1). 可以使用视图实现基于列的权限控制,却不需要真正的在系统中创建列权限,因此没有额外的开销。
2). 使用临时表算法实现的视图,在某些时候性能会很糟糕。MySQL以递归的方式执行这类视图,外层查询的WHERE条件无法"下推"到构建视图的临时表查询中,临时表也无法建立索引。
3). MySQL的视图还不是那么成熟。
2.3 视图的限制:在其他的关系数据库中,你可能使用过物化视图,MySQL还不支持物化视图(物化视图是指将视图结果数据存放在一个可以查看的表中,并定期从原始表中刷新数据到这个表中).MySQL也不支持在视图中
创建索引。不过,可以通过使用构建缓存表或者汇总表的办法来模拟物化视图和索引。
3. 外键:外键限制会将约束放到MySQL中,这对于必须维护外键的场景,性能会更高。不过这也会带来额外的复杂性和额外的索引消耗,还会增加多表之间的交互,会导致系统中更多的锁和竞争。外键可以被看
作是一个确保系统完整性的额外的特性,但是如果设计的是一个高性能的系统,那么外键就显得很臃肿了。很多人在更在意系统的性能的时候都不会使用外键,而是通过应用程序来维护的。
1). InnoDB是目前MySQL中唯一支持外键的内置存储引擎。
2). 使用外键是有成本的。比如外键通常都要求每次在修改数据时都要在另一张表中多执行一次查询操作。虽然InnoDB强制外键约束使用索引,还是无法消除这种约束检查的开销。如果外键列的选择性很低,则会导致一个
非常大且选择性很低的索引。
3). 在某些场合下,外键会提升一些性能。如果想确保两个相关表始终有一致的数据,那么使用外键比下应用程序中检查一致性的性能要高得多。此外,外键在相关数据的删除和更新上,也比在应用中维护要更高效,
不过,外键维护操作是逐行进行的,所以这样的更新会比批量删除和更新要慢些。
4). 外键约束使得查询需要额外访问一些表,这也意味着需要额外的锁。
5). 有时可以使用触发器来代替外键。对于相关数据的同时更新外键更合适,但是如果外键只是用作数值约束,那么触发器或者显式地限制取值会更好一些(这里可以直接使用ENUM类型)。
6). 如果只是使用外键做约束,那通常在应用程序里实现该约束会更好。外键会带来很大的额外消耗。
4. 存储过程:MySQL本身实现了存储过程、触发器、存储函数和事件,老实说,这些特性并没什么特别的。而且对于基于语句的复制还有很多问题。通常,使用这些特性可以帮你节省很多网络开销--很多情况下,
减少网路开销可以大大提升系统的性能。在某些经典的场景下,你可以使用这些特性(例如中心化业务逻辑、绕过全线系统等),但需要注意在MySQL中,这些特性并没有别的数据库系统那么成熟和全面。
1). MySQL允许通过触发器、存储过程、函数的形式来存储代码。从MySQL 5.1开始,还可以在定时任务中存放代码,这个定时任务也被成为"事件"。存储过程和存储函数都被统称为"存储程序"。
2). 一般来说,存储代码是一种很高的共享和复用代码的方法。
3). 存储代码的优点:
a. 在服务器内部运行,理数据最近,另外在服务器上执行还可以节省带宽和网络延迟。
b. 代码重用。
c. 可以简化代码的维护和版本更新。
d. 可以帮助提升安全,例如提供更细粒度的权限控制。如银行资金转移。
f. 服务器可以换成存储过程的执行计划。
g. 因为是在服务器端部署的,所以备份、维护都可以在服务器端完成。
h. 它可以在应用程序和数据库开发人员之间更好的分工。
存储代码的缺点:
a. MySQL本身没有提供好用的开发和调试工具。
b. 较之应用程序的代码,存储代码效率要稍微差些。 例如:函数有限,无法编写复杂字符串维护功能。
c. 存储代码可能会给应用程序代码的部署带来额外的复杂性。
d. 可能有安全隐患,最好加密。
e. 存储过程会给数据库服务器增加额外的压力,而数据库服务器的扩展性相比应用服务器要差很多。
f. MySQL没有什么选项可以控制存储过程的资源消耗,所以在存储过程中的一个小错误,可能直接把服务器拖死。
g. 调试MySQL的存储过程是一件很困难的事情。
4.1 存储过程和函数:我们通常会希望程序越小、越简单越好。希望将更复杂的处理逻辑交给上层的应用实现,通常这样会使代码更易读、易维护,也会更灵活。这样做也会让你拥有更多的计算资源,潜在的
还会让你拥有更多的缓存资源。 不过,对于某些操作,存储过程比其他的实现要快的多--特别是一个存储过程调用可以代替很多小查询的时候。如果查询很小,相比这个查询执行的成本,解析和网络开销
就变得非常明显。
4.2 触发器:触发器可以让你在执行INSERT、UPDATE或者DELETE的时候,执行一些特定的操作。可以在MySQL中指定是在SQL语句执行前触发还是在执行后触发。触发器本身没有返回值,不过它们可以读取
或者改变触发SQL语句所影响的数据。所以,可以使用触发器实现一些强制限制,或者而某些业务逻辑,否则,就需要在应用程序中实现这些逻辑。
1). 因为使用触发器可以减少客户端和服务器之间的通信,所以触发器可以简化应用逻辑,还可以提高性能。另外,还可以用于自动更新反范式化数据或者汇总表数据。
2). MySQL触发器实现非常简单,所以功能也有限。特别需要注意一下几点:
a. 对应每一个表的每一个事件,最多只能定义一个触发器(换句话说,不能在AFTER INSERT上定义两个触发器)
b. MySQL只支持"基于行的触发" -- 也就是说,触发器始终是针对一条记录的,而不是针对整个SQL语句的。如果变更的数据集非常大的话,效率会很低。
触发器本身的限制:
a. 触发器可以掩盖服务器背后的工作。例如SQL影响的记录数翻一倍。
b. 触发器的问题很难排除
c. 触发器可能导致死锁或所等待。
3). 在InnoDB表上的触发器是在同一个事务中完成的,所以它们执行的操作是原子的,原子操作和触发器操作会同时失败或者成功。不过,如果在InnoDB表上的触发器去检查数据的一致性,需要特别小心
MVCC,稍不小心,可能会获得错误的结果。
4). 可以使用触发器记录数据变更日志。
4.3 MySQL 5.1 引入,类似于Linux的定时任务,不过是完全在MySQL内部实现的。你可以创建事件,执行MySQL在某个时候执行一段SQL代码,或者每隔一段时间执行一段SQL代码。通常,我们会把复杂
的SQL都封装到一个存储过程中,这样事件在执行的时候只需要做一个简单的CALL调用。
1). 如果一个定时事件执行需要很长的时间,那么有可能会出现这样的情况,即前面一个事件还未执行完成,下一个时间点的事件又开始了。MySQL本身不会防止这种并发,所以需要用户在自己编写这种
情况下防并发代码。你可以使用函数GET_LOCK()来确保当前总是有一个事件在被执行。
2). 虽然事件的执行是和连接无关的,但是它仍然是线程级别的。MySQL中有一个事件调度线程,必须在MySQL配置文件中设置,或者使用下面的命令来设置。SET GLOBAL event_scheduler :=1
4.4 在存储过程中保留注释:/*! xxxxxxxxxxxxxxxxxxxx */
4.5 游标:MySQL在服务器端提供只读的、单向的游标,而且只能在存储过程或者更底层的客户端API中使用。因为MySQL游标中指向的对象都是存储在临时表中而不是实际查询到的数据,所以MySQL游标总
是只读的。它可以逐行指向查询结果,然后让程序做进一步处理。
5. 绑定变量:当查询语句的解析和执行计划生成消耗了主要时间,那么绑定变量可以在一定程度上解决问题。因为只需要解析一次,对于大量重复类型的查询语句,性能会有很大的提高。另外,执行计划的缓存
和传输使用的二进制协议,这都使得绑定变量的方式比普通SQL语句执行的方式要更快。
1). 当创建一个绑定变量SQL时,客户端向服务器发送一个SQL语句地原型。服务器端收到这个SQL语句框架后,解析并存储这个SQL语句的部分执行计划,返回给客户端一个SQL语句处理句柄。以后每次
执行这类查询,客户端都指定使用这个句柄。
2). 绑定变量的SQL,使用问好标记可以接受参数的位置,当真正需要执行具体的查询的时候,则使用具体值代替这些问号。
3). 因为如下原因,MySQL在使用绑定变量的时候可以更高效地执行大量重复语句:
a. 在服务器端只需要解析一次SQL语句。
b. 在服务器端某些优化器的工作只需要执行一次,因为它会缓存一部分的执行计划。
c. 使用参数的方式只发送参数和句柄,比起每次都发送ASCII码文本效率更高。
d. 仅仅是参数 -- 而不是整个查询语句 -- 需要发送到服务器端,所以网络开销会更小。
e. MySQL在存储参数的时候,直接将其放到缓存中,不再需要在内存中多次复制
4). 绑定变量相对也更安全。无需在应用程序中处理转义,一则更简单,二则也大大减少了SQL注入和攻击的风险。
5). 绑定变量的限制:
a. 绑定变量是会话级别的,所以连接之间不能共用绑定变量句柄。同样地,一旦连接断开,则原理的句柄也不能再使用。(连接池和持久化连接可以在一定程度上缓解这个问题。)
b. 在MySQL 5.1版本之前,绑定变量的SQL是不能使用查询缓存的。
c. 并不是所有时候的绑定变量都能获得更好的性能。如果只执行一次SQL,会带来额外的消耗。
d. 当前版本下,还不能在存储函数中使用绑定变量(但是存储过程中可以使用)
e. 如果总是忘记释放绑定变量资源,则在服务器端很容易发生资源"泄露"。绑定变量SQL总数的限制是一个全局限制,所以某一个地方的错误可能会对其他的线程都产生影响。
f. 有些操作,如BEGIN,无法在绑定变量中完成。
6. 插件:使用C或者C++编写的插件可以让你最大程度的扩展MySQL功能。插件功能非常强大。
7. 字符集:字符集是一种字节到字符之间的映射,而校对规则是一个字符集的排序方法。很多人使用Latin1(默认字符集,对英语和某些欧洲语言有效)或者UTF-8。如果使用的是UTF-8,那么在使用临时表和缓冲区
的时候需要注意:MySQL会按照每个字符三个字节的最大占用空间来分配存储空间,这可能消耗更多的内存或者磁盘空间。
注意让字符集和MySQL字符集配置相符,否则可能会由于字符集转换让某些索引无法使用。
7.1 MySQL如何使用字符集:只有基于字符的值才真正的"有"字符集的概念。
MySQL的设置可以分为两类:创建对象时的默认值、在服务器和客户端通信时的设置
1). 创建对象时的默认设置:
MySQL服务器有默认的字符集和校对规则,每个数据库也有自己的默认值,每个表也有自己的默认值。这是一个逐层继承的默认设置,最终要靠底层的默认值设置将影响你创建的对象。
这些默认值,至上而下地告诉MySQL应该使用什么样的字符集来存储某个列。
在这个"阶梯"的每一层,你都可以指定一个特殊的字符集或者让服务器使用它的默认值:
a. 创建数据库的时候,将根据服务器上的character_set_server设置来设定该数据库的默认字符集。
b. 创建表的时候,将根据数据库的字符集设置指定这个表的字符集设置。
c. 创建列的时候,将根据表的设置指定列的字符集设置。
需要记住的是,真正放数据的是列,所以更高"阶梯"的设置只是指定默认值。一个表的默认字符集设置无法影响存储在这个表中某个列的值。只有当创建列而没有为该列指定字符集的时候,如果没有指定字符集,
表的默认字符集才有用。
2). 服务器和客户端通信时的设置
当服务器端和客户端通信时的时候,它们可能使用不同的字符集。这时,服务器端将进行必要的翻译转换工作:
a. 服务器端总是假设客户端是按照character_set_client设置的字符来传输数据和SQL语句的。
b. 当服务器端收到客户端的SQL语句时,它先将其转换成字符集character_set_connection。它还使用这个设置来决定如何将数据转换成字符串。
c. 当服务器返回数据或者错误信息给客户端时,它会将其转换为character_set_result.
3). MySQL如何比较两个字符串的大小:如果两个字符串的字符集不同,MySQL会先将其转换成同一个字符集再进行比较,如果两个字符集不兼容的话,则会抛出错误。
7.2 选择字符集和校对规则:
1). 正确的做法是,最要好先为服务器(或者数据库) 选择一个合理的字符集,然后根据不同的实际情况,让某些列选择合适的字符集。
2). 对于校验规则通常需要考虑的一个问题是,是否以大小写敏感的方式比较字符串,或者是以字符串编码的二进制值来比较大小。它们对应的校验规则的前缀分别是_cs、_ci和_bin,根据需要很容易选择。大小写
敏感和二进制校对规则的不同之处在于,二进制校对规则直接使用字符的字节进行比较,而大小写敏感的校对规则在多字节字符集时,如果有德语,有更复杂的比较规则。
7.3 字符集和校对规则如何影响查询:某些字符集和校对规则可能会需要更多的CPU操作,可能会消耗更多的内存和存储空间,甚至还会影响索引的正常使用。所以在选择字符集的时候,也有一些需要注意的地方。
1). 不同的字符集和校对规则之间的转换可能会带来额外的系统开销。
2). 只有排序查询的字符集与服务器数据的字符集相同的时候,才能使用索引进行排序。
3). 为了能够适应各种字符集,包括客户端字符集、在查询中显式指定的字符集,MySQL会在需要的时候进行字符集转换。如果你不确定是否进行了转换,可以在EXPLAIN EXTENDED后使用SHOW WARNINGS
来查看MySQL是如何处理的。
4). 在多字节字符集中,一个字符不再是一个字节。所以,MySQL中有两个函数LENGTH()和CHAR_LENGTH()来计算字符串的长度。
8. 全文索引:在5.6版本之前只有MyISAM支持全文索引,不过据说5.6开始,InnoDB也将支持全文索引。MyISAM因为在锁粒度和崩溃恢复上的缺点,使得在大型全文索引场景中基本无法使用。这时,我们通常
帮助客户构建和使用Sphinx来解决全文索引问题。
9. XA(分布式)事务:很少有人使用MySQL的XA事务特性。除非你真正明白参数innodb_support_xa的意义,否则不要修改这个参数的值,并不是只有显式使用XA事务时才需要设置这个参数。InnoDB和二进制日志也是
需要使用XA事务来做协调的,从而确保在系统崩溃的时候,数据能够一致地恢复。
1). 存储引擎的事务特征能够保证事务在存储引擎级别实现ACID,而分布式事务则让存储引擎级别的ACID可以扩展到数据库层面,甚至可以扩展到多个数据库之间。这需要两个阶段提交实现。XA事务中需要一个事务
协调器来保证所有的事务参与者都完成了准备工作(第一阶段)。如果协调器收到所有的参与者都准备好的消息,就会告诉所有的事务可以提交了,这是第二阶段。MySQL在这个XA事务过程中扮演了一个参与者的
角色,而不是协调者。
2). 实际上,在MySQL中有两种XA事务。一方面,MySQL可以参与到外部的分布式事务中;另一方面,还可以通过XA事务来协调存储引擎和二进制日志。
3). 内部XA事务:如果将MySQL记录二进制日志操作看作一个独立的"存储引擎",就不难理解为什么即使是一个存储引擎参与的事务仍然需要XA事务了。在存储引擎提交的同时,需要将"提交"的信息写入二进制日志,
这就是一个分布式事务,只不过二进制日志的参与者是MySQL本身。XA事务会为MySQL带来巨大的性能下降,唯一避免这个问题的办法是关闭二进制日志,但这样设置非常不安全。
4). 外部XA事务:MySQL能够作为一个参与者完成一个外部的分布式事务。但它对XA协议支持并不完整。
a. 因为通信延迟或者参与者本身可能失败,所以外部XA事务比内部消耗更大。
b. 通常,还可以使用别的方式实现高性能的分布式事务。例如,可以在本地写入数据,并将其放入队列,然后在一个更小、更快的事务中自动分发。还可以使用MySQL本身的复制机制来发送数据。完全可以避免
使用分布式事务。
c. 也就是说,XA事务是一种在多个服务器之间同步数据的方法。如果由于某些原因不能使用MySQL本身的复制,或者性能并不是瓶颈的时候,可以尝试使用。
10. 查询缓存:完全相同的查询在重复执行的时候,查询缓存可用立即返回结果,而无须在数据库重新执行一次。根据我们的经验,在高并发压力环境中查询缓存会导致系统性能的下降,甚至僵死。如果你一定要使用
查询缓存,那么不要设置太大的内存,而且只有在明确收益的时候才使用。那该如何判断是否应该使用查询缓存呢?建议使用Percona Server,观察更细致的日志,并做一些简单的计算。还可以查看缓存命中率(
并不是总是有用)、"INSERTS和SELECT比率"(这个参数也并不直观)或者"命中和写入比率"(这个参考意义较大)。查询缓存时一个非常方便的缓存,可对应用程序完全透明,无需任何额外的编码,但是,如果希望
有更高的缓存效率,我们建议使用memcached或者其他类似的解决方案。
1). 很多数据库产品都能够缓存查询的执行计划,对于相同类型的SQL就可以跳过SQL解析和执行计划生成阶段。MySQL在某些场景下也可以实现,但是MySQL还另外一种不同的缓存类型:缓存完整的SELECT查询结果,
也就是"查询缓存"。
2). MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存,MySQL会立刻返回结果,跳过了解析、优化和执行阶段。
3). 查询缓存系统会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有缓存数据都将消失。
4). 查询缓存对应用程序是透明的。
5). 随着现在通用服务器越来越大,查询缓存被发现是一个影响服务器扩展性的因素。
10.1 MySQL如何判断缓存命中:MySQL判断缓存命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括如下因素,即查询本身、当前要查询的数据库、客户端协议的版本等。
1). 任何字符的不同,例如:空格、注释,都会导致缓存不命中。
2). 当查询语句中有一些不确定的数据时,则不会被缓存。例如包括函数:NOW()或者CURRENT DATE()。
3). MySQL的查询缓存在很多时候可以提升查询性能,在使用的时候。有一些问题需要特别注意。首先打开查询缓存对读和写操作会带来额外的消耗:
a. 读查询开始之前必须先检查是否命中缓存。
b. 将查询结果存入到查询缓存,这会带来额外的系统消耗。
c. 对写入操作也有影响,如果向某个表写入数据时,必须将对应的表的查询缓存设置为失效。
d. 如果查询缓存使用了大量的内存,缓存失效操作就可能成为一个非常严重的问题瓶颈。
10.2 查询缓存如何使用内存:查询缓存完全存储在内存中,除了查询结果之外,需要缓存的还有很多别的维护相关的数据。查询缓存的内存被分为很多小块,供每个查询缓存使用。
10.3 什么情况下查询缓存能发挥作用:
1). 对于那些需要消耗大量资源的查询通常都是非常适合缓存的。例如一些汇总计算查询,具体的如COUNT()等。总的来说,对于复杂的SELECT语句都可以使用查询缓存。
2). 需要进行很多次相同查询的
3). 推荐另外一个指标:"命中和写入"的比率,即Qcache_hits和Qcache_inserts的比值。根据经验来看,当这个比值大于3:1时通常查询缓存时有效的,不过这个比率最好能够达到10:1。
10.4 如何配置和维护查询缓存:
1). 可供配置的参数:
a. query_cache_type:是否打开查询缓存。可以设置成OFF、ON或DEMAND。DEMAND表示只有在查询语句中明确写明SQL_CACHE的语句才放入查询缓存。这个变量可以是会话级别的,也可以是全局级别的。
b. query_cache_size:查询缓存使用的总内存空间,但为时字节。这是值必须是1024的整数倍,否则MySQL实际分配的数据和你指定的略有不同。
c. query_cache_min_res_unit:在查询缓存中分配内存时的最小单位。
d. query_cache_limit:能够缓存的最大查询结果。如果你事先知道有很多这样的情况发生,那么建议在查询语句中加入SQL_NO_CACHE来避免查询缓存带来的额外消耗。
e. query_cache_wlock_oinvalidate:如果某个数据表被其他的连接锁住,是否仍然从查询缓存中返回结果。一般无需注意。
2). 减少碎片:
1). 没有什么方法能够完全避免碎片,但是选择合适的query_cache_min_res_unit可以帮你减少由碎片导致的内存空间浪费。设置合适的值可以平衡每个数据块的大小和每次存储结果时内存块申请的次数。
这个参数的大小和应用程序的查询结果的平均值大小直接相关。
2). 可以使用命令FLUSH_QUEYR_CACHE完成碎片整理。这个命令会将所有的查询缓存重新排序,并将所有的空闲空间都聚集到查询缓存的一块区域上。不过需要注意,这个命令并不会将查询缓存清空,
清空查询缓存由命令RESET_QUERY_CACHE完成。
3). 提高查询缓存的使用率:找到命中率很低的原因,是否是因为内存太小?是否是因为查询缓存无效(可以通过将query_cache_size设置为0,来关闭查询缓存)?
10.5 InnoDB和查询缓存:因为InnoDB有自己的MVCC机制,所以相比其他存储引擎,InnoDB的查询缓存更复杂。InnoDB会控制在一个事务中是否可以使用查询缓存,InooDB会同时控制查询缓存的读和写操作。
事务是否可以访问查询缓存取决于当前事务ID,以及对应数据表上是否有锁。每个InnoDB表的内存数据字典都保存了一个事务ID号,如果当前事务ID小于该事务ID,则无法访问查询缓存。
10.6 通用查询缓存优化:
1). 除了前文介绍的,还有以下几点:
a. 用多个小表代替一个大表对查询缓存有好处。
b. 批量写入时只需要一次缓存失效,所以相比单条写入效率更好。
c. 为防止缓存空间太大,导致服务器僵死,可以控制缓存空间的大小
d. 无法在数据库或者表级别控制查询缓存,但是可以通过SQL_CACHE和SQL_NO_CACHE来控制SELECT语句是否进行缓存。
e. 对于写密集型应用来说,直接禁用查询缓存可能会提高系统性能。
f. 因为对互斥信号量的竞争,有时直接关闭查询缓存对读密集型的应用也会有好处。如果你希望提高系统并发,那么最好做一个相关的测试,对比打开和关闭查询缓存时候的性能差异。
2). 如果不想所有的查询都进入查询缓存,但是又希望某些查询走查询缓存,那么可以将query_cache_type设置成DEMAND,然后在希望缓存的查询中加上SQL_CACHE。
10.7 查询缓存的替代方案:客户端缓存(分布式缓存?)