分享
 
 
 

MySQL查询优化系列讲座之查询优化器

王朝mysql·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

当你提交一个查询的时候,MySQL会分析它,看是否可以做一些优化使处理该查询的速度更快。这一部分将介绍查询优化器是如何工作的。如果你想知道MySQL采用的优化手段,可以查看MySQL参考手册。

当然,MySQL查询优化器也利用了索引,但是它也使用了其它一些信息。例如,如果你提交如下所示的查询,那么无论数据表有多大,MySQL执行它的速度都会非常快:

SELECT * FROM tbl_name WHERE 0;

在这个例子中,MySQL查看WHERE子句,认识到没有符合查询条件的数据行,因此根本就不考虑搜索数据表。你可以通过提供一个EXPLAIN语句看到这种情况,这个语句让MySQL显示自己执行的但实际上没有真正地执行的SELECT查询的一些信息。如果要使用EXPLAIN,只需要在EXPLAIN单词放在SELECT语句的前面:

mysql> EXPLAIN SELECT * FROM tbl_name WHERE 0\G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: NULL

type: NULL

possible_keys: NULL

key: NULL

key_len: NULL

ref: NULL

rows: NULL

Extra: Impossible WHERE

通常情况下,EXPLAIN返回的信息比上面的信息要多一些,还包括用于扫描数据表的索引、使用的联结类型、每张数据表中估计需要检查的数据行数量等非空(NULL)信息。

优化器是如何工作的

MySQL查询优化器有几个目标,但是其中最主要的目标是尽可能地使用索引,并且使用最严格的索引来消除尽可能多的数据行。你的最终目标是提交SELECT语句查找数据行,而不是排除数据行。优化器试图排除数据行的原因在于它排除数据行的速度越快,那么找到与条件匹配的数据行也就越快。如果能够首先进行最严格的测试,查询就可以执行地更快。假设你的查询检验了两个数据列,每个列上都有索引:

SELECT col3 FROM mytable

WHERE col1 = ’some value’ AND col2 = ’some other value’;

假设col1上的测试匹配了900个数据行,col2上的测试匹配了300个数据行,而同时进行的测试只得到了30个数据行。先测试Col1会有900个数据行,需要检查它们找到其中的30个与col2中的值匹配记录,其中就有870次是失败了。先测试col2会有300个数据行,需要检查它们找到其中的30个与col1中的值匹配的记录,只有270次是失败的,因此需要的计算和磁盘I/O更少。其结果是,优化器会先测试col2,因为这样做开销更小。

你可以通过下面一个指导帮助优化器更好地利用索引:

尽量比较数据类型相同的数据列。当你在比较操作中使用索引数据列的时候,请使用数据类型相同的列。相同的数据类型比不同类型的性能要高一些。例如,INT与BIGINT是不同的。CHAR(10)被认为是CHAR(10)或VARCHAR(10),但是与CHAR(12)或VARCHAR(12)不同。如果你所比较的数据列的类型不同,那么可以使用ALTER TABLE来修改其中一个,使它们的类型相匹配。

尽可能地让索引列在比较表达式中独立。如果你在函数调用或者更复杂的算术表达式条件中使用了某个数据列,MySQL就不会使用索引,因为它必须计算出每个数据行的表达式值。有时候这种情况无法避免,但是很多情况下你可以重新编写一个查询让索引列独立地出现。

下面的WHERE子句显示了这种情况。它们的功能相同,但是对于优化目标来说就有很大差异了:

WHERE mycol < 4 / 2

WHERE mycol * 2 < 4

对于第一行,优化器把表达式4/2简化为2,接着使用mycol上的索引来快速地查找小于2的值。对于第二个表达式,MySQL必须检索出每个数据行的mycol值,乘以2,接着把结果与4进行比较。在这种情况下,不会使用索引。数据列中的每个值都必须被检索到,这样才能计算出比较表达式左边的值。

我们看另外一个例子。假设你对date_col列进行了索引。如果你提交一条如下所示的查询,就不会使用这个索引:

SELECT * FROM mytbl WHERE YEAR(date_col) < 1990;

这个表达式不会把1990与索引列进行比较;它会把1990与该数据列计算出来的值比较,而每个数据行都必须计算出这个值。其结果是,没有使用date_col上的索引,因为执行这样的查询需要全表扫描。怎么解决这个问题呢?只需要使用文本日期,接着就可以使用date_col上的索引来查找列中匹配的值了:

WHERE date_col < ’1990-01-01’

但是,假设你没有特定的日期。你可能希望找到一些与今天相隔固定的几天的日期的记录。表达这种类型的比较有很多种方法--它们的效率并不同。下面就有三种:

WHERE TO_DAYS(date_col) - TO_DAYS(CURDATE()) < cutoff

WHERE TO_DAYS(date_col) < cutoff + TO_DAYS(CURDATE())

WHERE date_col < DATE_ADD(CURDATE(), INTERVAL cutoff DAY)

对于第一行,不会用到索引,因为每个数据行都必须检索以计算出TO_DAYS(date_col)的值。第二行要好一些。Cutoff和TO_DAYS(CURDATE())都是常量,因此在处理查询之前,比较表达式的右边可以被优化器一次性计算出来,而不需要每个数据行都计算一次。但是date_col列仍然出现在函数调用中,它阻止了索引的使用。第三行是这几个中最好的。同样,在执行查询之前,比较表达式的右边可以作为常量一次性计算出来,但是现在它的值是一个日期。这个值可以直接与date_col值进行比较,再也不需要转换成天数了。在这种情况下,会使用索引。

在LIKE模式的开头不要使用通配符。有些字符串搜索使用如下所示的WHERE子句:

WHERE col_name LIKE ’%string%’

如果你希望找到那些出现在数据列的任何位置的字符串,这个语句就是对的。但是不要因为习惯而简单地把"%"放在字符串的两边。如果你在查找出现在数据列开头的字符串,就删掉前面的"%"。假设你要查找那些类似MacGregor或MacDougall等以"Mac"开头的名字。在这种情况下,WHERE子句如下所示:

WHERE last_name LIKE ’Mac%’

优化器查看该模式中词首的文本,并使用索引找到那些与下面的表达式匹配的数据行。下面的表达式是使用last_name索引的另一种形式:

WHERE last_name >= ’Mac’ AND last_name < ’Mad’

这种优化不能应用于使用了REGEXP操作符的模式匹配。REGEXP表达式永远不会被优化。

帮助优化器更好的判断索引的效率。在默认情况下,当你把索引列的值与常量进行比较的时候,优化器会假设键值在索引内部是均匀分布的。在决定进行常量比较是否使用索引的时候,优化器会快速地检查索引,估计出会用到多少个实体(entry)。对应MyISAM、InnoDB和BDB数据表来说,你可以使用ANALYZE TABLE让服务器执行对键值的分析。它会为优化器提供更好的信息。

使用EXPLAIN验证优化器的操作。EXPLAIN语句可以告诉你是否使用了索引。当你试图用另外的方式编写语句或检查添加索引是否会提高查询执行效率的时候,这些信息对你是有帮助的。

在必要的时候给优化器一些提示。正常情况下,MySQL优化器自由地决定扫描数据表的次序来最快地检索数据行。在有些场合中优化器没有作出最佳选择。如果你察觉这种现象发生了,就可以使用STRAIGHT_JOIN关键字来重载优化器的选择。带有STRAIGHT_JOIN的联结类似于交叉联结,但是强迫数据表按照FROM子句中指定的次序来联结。

在SELECT语句中有两个地方可以指定STRAIGHT_JOIN。你可以在SELECT关键字和选择列表之间的位置指定,这样会对语句中所有的交叉联结产生影响;你也可以在FROM子句中指定。下面的两个语句功能相同:

SELECT STRAIGHT_JOIN ... FROM t1, t2, t3 ... ;

SELECT ... FROM t1 STRAIGHT_JOIN t2 STRAIGHT_JOIN t3 ... ;

分别在带有STRAIGHT_JOIN和不带STRAIGHT_JOIN的情况下运行这个查询;MySQL可能因为什么原因没有按照你认为最好的次序使用索引(你可以使用EXPLAIN来检查MySQL处理每个语句的执行计划)。

你还可以使用FORCE INDEX、USE INDEX或IGNORE INDEX来指导服务器如何使用索引。

利用优化器更加完善的区域。MySQL可以执行联结和子查询,但是子查询是最近才支持的,是在MySQL 4.1中添加的。因而在很多情况下,优化器对联结操作的调整比对子查询的调整要好一些。当你的子查询执行地很慢的时候,这就是一条实际的提示。有一些子查询可以使用逻辑上相等的联结来重新表达。在可行的情况下,你可以把子查询重新改写为联结,看是否执行地快一些。

测试查询的备用形式,多次运行。当你测试查询的备用形式的时候(例如,子查询与等同的联结操作对比),每种方式都应该多次运行。如果两种形式都只运行了一次,那么你通常会发现第二个查询比第一个快,这是因为第一个查询得到的信息仍然保留在缓存中,以至于第二个查询没有真正地从磁盘上读取数据。你还应该在系统负载相对平稳的时候运行查询,以避免系统中其它的事务影响结果。

避免过度地使用MySQL自动类型转换。MySQL会执行自动的类型转换,但是如果你能够避免这种转换操作,你得到的性能就更好了。例如,如果num_col是整型数据列,那么下面这些查询将返回相同的结果:

SELECT * FROM mytbl WHERE num_col = 4;

SELECT * FROM mytbl WHERE num_col = ’4’;

但是第二个查询涉及到了类型转换。转换操作本身为了把整型和字符串型转换为双精度型进行比较,使性能恶化了。更严重的情况是,如果num_col是索引的,那么涉及到类型转换的比较操作不会使用索引。

相反类型的比较操作(把字符串列与数值比较)也会阻止索引的使用。假设你编写了如下所示的查询:

SELECT * FROM mytbl WHERE str_col = 4;

在这个例子中,不会使用str_col上的索引,因为在把str_col中的字符串值转换成数值的时候,可能有很多值等于4(例如’4’、’4.0’和’4th’)。分辨哪些值符合要求的唯一办法是读取每个数据行并执行比较操作。

使用EXPLAIN来检查优化器的操作

EXPLAIN对于了解优化器生成的、用于处理语句的执行计划的内部信息是很有帮助的。在这一部分中,我们将解释EXPLAIN的两种用途:

· 查看采用不同的方式编写的查询是否影响了索引的使用。

· 查看向数据表添加索引对优化器生成高效率执行计划的能力的影响。

这一部分只讨论与示例相关的EXPLAIN输入字段。

前面,在"优化器是如何工作的"部分中我们得出的观点是,你编写表达式的方式将决定优化器是否能使用可用的索引。特别是上面的讨论使用了下面三个逻辑相等的WHERE子句的例子,只有第三个允许使用索引:

WHERE TO_DAYS(date_col) - TO_DAYS(CURDATE()) < cutoff

WHERE TO_DAYS(date_col) < cutoff + TO_DAYS(CURDATE())

WHERE date_col < DATE_ADD(CURDATE(), INTERVAL cutoff DAY)

EXPLAIN允许你查看编写表达式的某种方式是否比另外的方式好一些。为了看到结果,让我们分别用这三个WHERE子句搜索成员表中过期的数据列值,把cutoff值设为30天。为了看到索引的使用和表达式编写方式之间的关系,我们首先对expiration列进行索引:

mysql> ALTER TABLE member ADD INDEX (expiration);

接着在每个表达式形式上使用EXPLAIN,看优化器生成了什么样的执行计划:

mysql> EXPLAIN SELECT * FROM MEMBER

-> WHERE TO_DAYS(expiration) - TO_DAYS(CURDATE()) < 30\G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: MEMBER

type: ALL

possible_keys: NULL

key: NULL

key_len: NULL

ref: NULL

rows: 102

Extra: Using where

mysql> EXPLAIN SELECT * FROM MEMBER

-> WHERE TO_DAYS(expiration) < 30 + TO_DAYS(CURDATE())\G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: MEMBER

type: ALL

possible_keys: NULL

key: NULL

key_len: NULL

ref: NULL

rows: 102

Extra: Using where

mysql> EXPLAIN SELECT * FROM MEMBER

-> WHERE expiration < DATE_ADD(CURDATE(), INTERVAL 30 DAY)\G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: MEMBER

type: range

possible_keys: expiration

key: expiration

key_len: 4

ref: NULL

rows: 6

Extra: Using where

上面的结果显示,前面两个语句没有使用索引。类型(type)值表明了将如何从数据表中读取信息。ALL意味着"将检查所有的记录"。也就是说,它会执行全表扫描,没有利用索引。每个与键相关的列都是NULL也表明没有使用索引。

与此形成对比的是,第三个语句的结果显示,采用这种方式编写的WHERE子句,优化器可以使用expiration列上的索引:

· 类型(type)值表明它可以使用索引来搜索特定范围的值(小于右边表达式给定的值)。

· 可能键(possible_keys)和键(key)值显示expiration上的索引已经被考虑作为备选索引,并且它也是真正使用的索引。

· 行数(rows)值显示优化器估计自己需要检查6个数据行来处理该查询。这比前面两个执行计划的102小很多。

EXPLAIN的第二种用途是查看添加索引是否能帮助优化器更高效率地执行语句。我将使用两个未被索引的数据表。它足够显示建立索引的效率。相同的规则可以应用于涉及多表的更加复杂的联结操作。

假设我们有两个数据表t1和t2,每个有1000行,包含的值从1到1000。下面的查询查找出两个表中值相同的数据行:

mysql> SELECT t1.i1, t2.i2 FROM t1, t2 WHERE t1.i1 = t2.i2;

+------+------+

| i1 | i2 |

+------+------+

| 1 | 1 |

| 2 | 2 |

| 3 | 3 |

| 4 | 4 |

| 5 | 5 |

...

两个表都没有索引的时候,EXPLAIN产生下面的结果:

mysql>

[1] [2] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有