分区表是一个独立的逻辑表,底层由多个物理子表构成。实现分区的代码其实是对一组底层表的句柄对象的封装。对分区表的请求,都会经过句柄对象转化成对存储引擎的接口调用。node
MySQL实现分区表的方式——对底层表的封装——意味着索引也是按照分区的子表定义的,而没有全局索引。mysql
MySQL在建立表的时使用PARTITION BY子句定义每一个分区存放的数据。在执行查询的时候,优化器会根据分区定义过滤那些没有咱们须要数据的分区,这样查询就无须扫描全部分区——只须要查找包含须要数据的分区就能够了。算法
分区的一个主要目的是将数据按照一个较粗的粒度分在不一样的表中。sql
使用场景:数据库
分区表自己也有一些限制:编程
分区表由多个相关的底层表实现,这些底层表也是由句柄对象表示,因此也能够直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表同样,分区表的索引只是在各个底层表上各自加上一个彻底相同的索引。从存储引擎的角度来看,底层表和一个普通表没有任何不一样,存储引擎也无需知道这是一个普通表仍是一个分区表的一部分。缓存
分区表上的操做按照下面的操做逻辑进行:安全
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须要打开并锁住全部的底层表,这是分区表的另外一个开销。这个操做在分区过滤以前发生,因此没法经过分区过滤下降此开销,而且改开销也和分区类型无关,会影响全部的查询。
维护分区的成本可能很高
引入分区给查询优化带来了一些新的思路。分区最大的优势就是优化器能够根据分区函数来过滤一些分区。根据粗粒度索引的优点,经过分区过滤一般可让查询扫描更少的数据。
对于访问分区表来讲,很重要的一点是要在WHERE条件中带入分区列,有时候即便看似多余的也要带上,这样就可让优化器可以过滤无须访问的分区。
这个查询访问全部的分区。
MySQL只能在使用分区函数的列自己进行比较时才能过滤分区,而不能根据表达式的值去过滤分区,即便这个表达式就是分区函数也不行。
这里写的WHERE条件中带入的是分区列,而不是基于分区列的表达式,因此优化器可以利用这个条件过滤部分分区。一个很重要的原则是:即使在建立分区时可使用表达式,但在查询时却只能根据列来过滤分区。
合并表是一种早期的、简单的分区实现,和分区表相比有一些不一样的限制,而且缺少优化。分区表严格来讲是一个逻辑上的概念,用户没法访问底层的各个分区,对用户来讲分区是透明的。
合并表至关于一个容器,里面包含了多个真实表。能够在CREATE TABLE中使用一种特别的UNION 语法来指定包含哪些真实表。
最后创建的合并表和前面的各个真实表字段彻底相同,在合并表中有的索引各个真实子表也有,这是建立合并表的前提条件。各个子表在对应列上都有主键限制,可是最终的合并表中仍然出现了重复值。
INSERT_METHOD=LAST告诉MySQL,将全部的INSERT语句都发送给最后一个表。指定FIRST或者LAST关键字是惟一能够控制行插入到合并表的哪个子表的方式。
INSERT语句的执行结果能够在最终的合并表中看到,也能够在对应的子表中看到:
删除一个合并表,它的子表不受任何影响,而若是直接删除其中一个子表则可能会有不一样的后果,这要视操做系统而定。
在使用CREATE语句建立一个合并表的时候,并不会检查各个子表的兼容性。若是子表的定义稍有不一样,那么MySQL就可能建立出一个后面没法使用的合并表。
根据合并表的特性,不难发现,在合并表上没法使用REPLACE语法,没法使用自增字段。
若是一个查询访问合并表,那么它须要访问全部子表。这会让根据键查找单行的查询速度变慢,若是可以只访问一个对应表,速度确定将更快。限制合并表中的子表数量特别重要,特别是当合并表是某个关联查询的一部分的时候,由于这时访问一个表的记录数可能会将比较操做传递到关联的其余表中,这时减小记录的访问就是减小整个关联操做。
合并表的各个子表能够直接被访问,它还具备一些MySQL5.5分区所不能提供的特性:
视图自己是一个虚拟表,不存听任何数据。在使用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容许经过触发器、存储过程、函数的形式来存储代码。从MySQL5.1开始,还能够在定时任务中存放代码,这个定时任务也被称为"事件"。存储过程和存储函数都被统称为"存储函数"。
不一样类型的存储代码的主要区别在于其执行的上下文——也就是输入和输出。存储过程和存储函数均可以接收参数而后返回值,可是触发器和事件不行。
存储代码的优势:
存储代码的缺点:
MySQL的架构自己和优化器的特性使得存储代码有一些自然限制,他的性能也必定程度受限于此。
咱们一般会但愿存储程序越小、越简单越好。但愿将更加复杂的处理逻辑交给上层的应用实现,一般这样会使代码更易读、易维护,也会更灵活。
存储过程要快不少,很大程度由于它无须网络通讯开销、解析开销和优化器开销等。
触发器可让你在执行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注入和攻击的风险。
对使用绑定变量的SQL,MySQL可以缓存其部分执行计划,若是某些执行计划须要根据传入的参数来计算时,MySQL就没法缓存这部分的执行计划。
在准备阶段 服务器解析SQL语句,移除不可能的条件,而且重写子查询。
在第一次执行的时候
若是可能的话,服务器先简化嵌套循环的关联,并将外关联转化成内关联。
在每次SQL语句执行时
服务器作如下事情
MySQL支持了SQL接口的绑定变量。不使用二进制传输协议也能够直接以SQL的方式使用绑定变量。
最主要的用途就是在存储过程当中使用。在MySQL5.0版本中,就能够在存储过程当中使用绑定变量,其语法和前面介绍的SQL接口和绑定变量相似。这意味着,能够在存储过程当中构建并执行"动态"的SQL语句,这里的"动态"是指能够经过灵活的拼接字符串等参数构建SQL语句。
编写存储过程时,SQL接口的绑定变量一般能够很大程度地帮助咱们调试绑定变量,若是不是在存储过程当中,SQL接口的绑定变量就不是那么有用了。由于SQL接口的绑定变量,它既没有使用二进制传输协议,也没有可以节省带宽,相反还老是须要增长至少一额外网络传输才能完成一次传输。
三种绑定变量的区别:
客户端的驱动程序接收一个带参数的SQL,在将指定的值带入其中,最后将完整的查询发送到服务器端。
客户端使用特殊的二进制协议将带参数的字符串发送到服务器端口,而后使用二进制协议将具体的参数值发送给服务器端并执行。
客户端先发送一个带参数的字符串到服务端,这相似于使用PREPARE的SQL语句,而后发送设置参数的SQL,最后使用EXECUTE来执行SQL。全部这些都使用普通的文本传输协议。
MySQL支持用户自定义函数(UDF)。存储过程只能使用SQL来编写,而UDF没有这个限制,你可使用支持C语言调用约定的任何编程语言来实现。
UDF必须事先编译好并动态连接到服务器上,这种平台相关性使得UDF在不少方面都很强大。UDF速度很是快,并且能够访问大量操做系统的功能,还可使用大量函数库。
能力越大,责任越大。因此在UDF中的一个错误极可能会让服务器直接崩溃,甚至扰乱服务器的内存或者数据,另外,全部C语言具备的潜在风险,UDF也都有。
字符集是指一种从二进制编码到某类字符符号的映射,能够参考如何使用一个字节表来表示英文字母。"校对"是指一组用于某个字符集的排序规则。MySQL4.1和以后的版本中,每一类编码字符都有其对应的字符集和校对规则。
#####MySQL如何使用字符集
每种字符集均可能有多种校对规则,而且都有一个默认的校对规则。每一个校对规则都是针对某个特定的字符集的,和其余的字符集没有关系。校对规则和字符集老是一块儿使用。
只有基于字符的值才真正的"有"字符集的概念。对于其余类型的值,字符集只是一个设置,指定用哪种字符集来作比较或者其余操做。
MySQL的设置能够分为两类:建立对象时的默认值、在服务器和客户端通讯时的设置。
建立对象时的默认设置
MySQL服务器有默认的字符集和校对规则,每一个数据库也有本身的默认值,每一个表也有本身的默认值。这是一个逐层继承的默认设置,最终最靠底层的默认设置将影响你建立的对象。
真正存放数据的是列,更高"阶梯"的设置只是指定默认值。一个表的默认字符集设置没法影响存储在这个表中某个列的值。只有当建立列而没为列指定字符集的时候,若是没有指定字符集,表的默认字符集才有做用。
服务器和客户端通讯时的设置
当服务器和客户端通讯的时候,它们可能使用不一样的字符集。服务器端将进行必要的翻译转换工做:
根据须要,可使用SET NAMES或者SET CHARACTER SET语句来改变上面的设置。不过在服务器上使用这个命令只能改变服务器端的设置。客户端程序和客户端的API也须要使用正确的字符集才能避免在通讯时出现问题。
MySQL如何比较两个字符串的大小
若是比较的两个字符串的字符集不一样,MySQL会先将其转成同一个字符集再进行比较。若是两个字符集不兼容的话,则会抛出错误。MySQL5.0和更新的版本常常会作这样的瘾式转换。
一些特殊状况
对于校对规则一般须要考虑的一个问题是,是否以大小写敏感的方式比较字符串,或者是以字符串编码的二进制值来比较大小。他们对应的校对规则的前缀分别是_cs、_ci和_bin,根据须要很容易选择。大小写敏感和二进制校对规则的不一样之处在于,二进制校对规则直接使用字符的字节进行比较,而大小写敏感的校对规则在多字节字符集时,有更复杂的比较规则。
某些字符集和校对规则可能会须要更多的CPU操做,可能会消耗更多的内存和存储空间,甚至还会影响索引的正常使用。
全文索引能够支持各类字符内容的搜索(包括CHAR、VARCHAR和TEXT类型),也支持天然语言搜索和布尔搜索。标准的MySQL中,只有MyISAM引擎支持全文索引。在MySQL5.6中,InnoDB已经实验性质的支持全文索引了。MyISAM对全文索引的支持有不少限制,例如表级别锁对性能的影响、数据文件的崩溃、崩溃后的恢复等。
MyISAM的全文索引是一类特殊的B-Tree索引,共有两层。第一层是全部关键字,而后对于每个关键字的第二层,包含的是一组相关的"文档指针"。
天然语言搜索引擎将计算每个文档对象和查询的相关度。这里,相关度是基于匹配的关键词个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。
全文索引的语法和普通查询略有不一样。能够根据WHERE子句中的MATCH AGAINST来区分查询是否使用全文索引。
函数MATCH()将返回关键词匹配的相关度,是一个浮点数字。在一个查询中使用两次MATCH()函数并不会有额外的消耗,MySQL会自动识别并只进行一次搜索。若是将MATCH()函数放到ORDER BY子句中,MySQL将会使用文件排序。
在MATCH()函数中指定的列必须和在全文索引中指定的列彻底相同,不然就没法使用全文索引。这是由于全文索引不会记录关键字是那一列的。
短语搜索的速度会比较慢。只使用全文索引是没法判断是否精确匹配短语的,一般还须要查询原文肯定记录中是否包含完整的短语。因为须要进行回表过滤,因此速度会很慢。
在MySQL5.1Z中引入了一些和全文索引相关的改进,包括一些性能上的提高和新增插件式的解析。
MySQL全文索引中只有一种判断相关性的方法:词频。索引也不会记录索引词在字符串中的位置,因此位置也就没法用在相关性上。
数据量大小也是一个问题。MySQL的全文索引只有所有在内存中的时候,性能才很是好。若是内存没法装载所有索引,那么搜索速度可能会很是慢。当你使用精确短语搜索时,想要好的性能,数据和索引都须要在内存中。相比其余的索引类型,当INSERT、UPDATE和DELETE操做进行时,全文索引的操做代价都很大:
全文索引还会影响查询优化器的工做。索引选择、WHERE子句、ORDER BY都有可能不是按照你所预想的方式来工做
假若有一百万个文档记录,在文档的做者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来创建全文索引。由于全文索引的更新是一个消耗很大的操做,因此上面的细节会帮你节省大量时间。
若是数据集特别大,则须要对数据进行手动分区,而后将数据分布到不一样的节点,再作并行的搜索。
存储引擎的事物特性可以保证在存储引擎级别实现ACID,而分布式事务则让存储引擎级别的ACID能够扩展到数据库层面,甚至能够扩展到多个数据库之间——这须要经过两阶段提交实现。MySQL5.0和更新版本的数据库已经开始支持XA事务了。
XA事务中须要一个事务协调器来保证全部的事务参与者都完成了准备工做。若是协调器收到全部的参与者都准备好的消息,就会告诉全部的事务能够提交了。
MySQL中有两种XA事务。一方面,MySQL能够参与到外部的分布式事务中;另外一方面,还能够经过XA事务来协调存储引擎和二进制日志。
MySQL自己的插件式架构致使在其内部须要使用XA事物。MySQL中各个存储引擎是彻底独立的,彼此不知道对方的存在,因此一个跨存储引擎的事物就须要一个外部的协调者。若是不使用XA协议,例如跨存储引擎的事务提交就只是顺序地要求每一个存储引擎各自提交。若是在某个存储提交过程当中发生系统崩溃,就会破坏事物的特性。
XA事务为MySQL带来巨大的性能降低。从MySQL5.0开始,它破坏了MySQL内部的"批量提交",使得MySQL不得不进行屡次额外的fsync()调用。
MySQL可以做为参与者完成一个外部的分布式事务。但它对XA协议支持并不完整。由于通讯延迟和参与者自己可能失败,因此外部XA事务比内部消耗会更大。若是在广域网中使用XA事务,一般会由于不可预测的网络性能致使事务失败。若是有太多不可控因素,则最好避免使用XA事务。任何可能让事务提交发生延迟的操做代价都很大,由于它影响的不只仅是本身自己,它还会让全部参与者都在等待。
MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存,MySQL会马上返回结果,跳过了解析、优化和执行阶段。
查询缓存系统会跟踪查询中涉及的每一个表,若是这些表发生变化,那么和这个表相关的全部的缓存数据都将失效。这种机制效率看起来比较低,由于数据表变化时颇有可能对应的查询结果并无更改,可是这种简单实现代价很小,而这点对于一个很是繁忙的系统来讲很是重要。
查询缓存对应用程序是彻底透明的。应用程序无须关系MySQL是经过查询缓存返回的结果仍是实际执行返回的结果。
随着如今的通用服务器愈来愈强大,查询缓存被发现是一个影响服务器扩展性的因素。它可能成为整个服务器的资源竞争单点,在多核服务器上还可能致使服务器僵死。
MySQL判断缓存命中的方法很简单:缓存存放在一个引用表中,经过一个哈系值引用,这个哈希值包括了以下因素,即查询自己、当前要查询的数据库、客户端协议的版本等一些其余可能会影响返回结果的信息。
当判断缓存是否命中时,MySQL不会解析、"正视化"或者参数化查询语句,而是直接使用SQL语句和客户端发送过来的其余原始信息。任何字符上的不一样都会致使缓存的不命中。
当查询语句中有一些不肯定的数据时,则不会被缓存。若是查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql库中的系统表,或者任何包含列级别权限的表,都不会被缓存。
在检查查询缓存的时候,尚未解析SQL语句,因此MySQL并不知道查询语句中是否包含这类函数。在检查查询缓存以前,MySQL只作一件事情,就是经过一个大小写不敏感的检查看看SQL语句是否是以SEL开头。
若是查询语句中包含任何的不肯定函数,那么在查询缓存中是不可能找到缓存结果的。
打开查询缓存对读和写操做都会带来额外的消耗:
若是查询缓存使用了大量的内存,缓存失效操做就可能成为一个很是严重的问题瓶颈。若是缓存中存放了大量的查询结果,那么缓存失效操做时整个系统均可能会僵死一下子。由于这个操做是靠一个全局锁操做保护的,全部须要作该操做的查询都要等待这个锁,并且不管是检测是否命中缓存、仍是缓存失效检测都须要等待这个全局锁。
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。
一般能够经过观察查询缓存内存的实际使用状况,来肯定是否须要缩小或者扩大查询缓存。若是查询缓存空间长时间都有剩余,那么建议缩小;若是常常因为空间不足而致使查询缓存失效,那么则须要增大查询缓存。另外还须要和系统的其余缓存一块儿考虑。
最好的判断查询缓存是否有效的办法仍是经过查看某类查询时间消耗是否增大或者减小来判断。
减小碎片 提升查询缓存的使用率
事务是否能够访问查询缓存取决于当前事务ID,以及对应的数据表上是否有锁。每个InnoDB表的内存数据字典都保存了一个事物ID号,若是当前事务ID小于该事务ID,则没法访问查询缓存。
若是表上有任何的锁,那么对这个表的任何查询语句都是没法被缓存的。
客户端的缓存能够很大程度上帮你分担MySQL服务器的压力。