对于第一行,不会用到索引,因为每个数据行都必须检索以计算出TO_DAYS(date_col)的值。
第二行要好一些。
Cutoff和TO_DAYS(CURDATE())都是常量,因此在处理查询之前,比较表达式的右边可以被优化器一次性计算出来,而不需要每个数据行都计算一次。
但是date_col列仍然出现在函数调用中,它阻止了索引的使用。
第三行是这几个中最好的。
同样,在执行查询之前,比较表达式的右边可以作为常量一次性计算出来,但是现在它的值是一个日期。
这个值可以直接与date_col值进行比较,再也不需要转换成天数了。
在这种情况下,会使用索引。
在LIKE模式的开头不要使用通配符。
有些字符串搜索使用如下所示的WHERE子句:
WHEREcol_nameLIKE’%string%’
如果你希望找到那些出现在数据列的任何位置的字符串,这个语句就是对的。
但是不要因为习惯而简单地把"%"放在字符串的两边。
如果你在查找出现在数据列开头的字符串,就删掉前面的"%"。
假设你要查找那些类似MacGregor或MacDougall等以"Mac"开头的名字。
在这种情况下,WHERE子句如下所示:
WHERElast_nameLIKE’Mac%’
优化器查看该模式中词首的文本,并使用索引找到那些与下面的表达式匹配的数据行。
下面的表达式是使用last_name索引的另一种形式:
WHERElast_name>=’Mac’ANDlast_name<’Mad’
这种优化不能应用于使用了REGEXP操作符的模式匹配。
REGEXP表达式永远不会被优化。
帮助优化器更好的判断索引的效率。
在默认情况下,当你把索引列的值与常量进行比较的时候,优化器会假设键值在索引内部是均匀分布的。
在决定进行常量比较是否使用索引的时候,优化器会快速地检查索引,估计出会用到多少个实体(entry)。
对应MyISAM、InnoDB和BDB数据表来说,你可以使用ANALYZETABLE让服务器执行对键值的分析。
它会为优化器提供更好的信息。
使用EXPLAIN验证优化器的操作。
EXPLAIN语句可以告诉你是否使用了索引。
当你试图用另外的方式编写语句或检查添加索引是否会提高查询执行效率的时候,这些信息对你是有帮助的。
在必要的时候给优化器一些提示。
正常情况下,MySQL优化器自由地决定扫描数据表的次序来最快地检索数据行。
在有些场合中优化器没有作出最佳选择。
如果你察觉这种现象发生了,就可以使用STRAIGHT_JOIN关键字来重载优化器的选择。
带有STRAIGHT_JOIN的联结类似于交叉联结,但是强迫数据表按照FROM子句中指定的次序来联结。
在SELECT语句中有两个地方可以指定STRAIGHT_JOIN。
你可以在SELECT关键字和选择列表之间的位置指定,这样会对语句中所有的交叉联结产生影响;你也可以在FROM子句中指定。
下面的两个语句功能相同:
SELECTSTRAIGHT_JOIN...FROMt1,t2,t3...;
SELECT...FROMt1STRAIGHT_JOINt2STRAIGHT_JOINt3...;
分别在带有STRAIGHT_JOIN和不带STRAIGHT_JOIN的情况下运行这个查询;MySQL可能因为什么原因没有按照你认为最好的次序使用索引(你可以使用EXPLAIN来检查MySQL处理每个语句的执行计划)。
你还可以使用FORCEINDEX、USEINDEX或IGNOREINDEX来指导服务器如何使用索引。
利用优化器更加完善的区域。
MySQL可以执行联结和子查询,但是子查询是最近才支持的,是在MySQL4.1中添加的。
因而在很多情况下,优化器对联结操作的调整比对子查询的调整要好一些。
当你的子查询执行地很慢的时候,这就是一条实际的提示。
有一些子查询可以使用逻辑上相等的联结来重新表达。
在可行的情况下,你可以把子查询重新改写为联结,看是否执行地快一些。
测试查询的备用形式,多次运行。
当你测试查询的备用形式的时候(例如,子查询与等同的联结操作对比),每种方式都应该多次运行。
如果两种形式都只运行了一次,那么你通常会发现第二个查询比第一个快,这是因为第一个查询得到的信息仍然保留在缓存中,以至于第二个查询没有真正地从磁盘上读取数据。
你还应该在系统负载相对平稳的时候运行查询,以避免系统中其它的事务影响结果。
避免过度地使用MySQL自动类型转换。
MySQL会执行自动的类型转换,但是如果你能够避免这种转换操作,你得到的性能就更好了。
例如,如果num_col是整型数据列,那么下面这些查询将返回相同的结果:
SELECT*FROMmytblWHEREnum_col=4;
SELECT*FROMmytblWHEREnum_col=’4’;
但是第二个查询涉及到了类型转换。
转换操作本身为了把整型和字符串型转换为双精度型进行比较,使性能恶化了。
更严重的情况是,如果num_col是索引的,那么涉及到类型转换的比较操作不会使用索引。
相反类型的比较操作(把字符串列与数值比较)也会阻止索引的使用。
假设你编写了如下所示的查询:
SELECT*FROMmytblWHEREstr_col=4;
在这个例子中,不会使用str_col上的索引,因为在把str_col中的字符串值转换成数值的时候,可能有很多值等于4(例如’4’、’4.0’和’4th’)。
分辨哪些值符合要求的唯一办法是读取每个数据行并执行比较操作。
使用EXPLAIN来检查优化器的操作
EXPLAIN对于了解优化器生成的、用于处理语句的执行计划的内部信息是很有帮助的。
在这一部分中,我们将解释EXPLAIN的两种用途:
·查看采用不同的方式编写的查询是否影响了索引的使用。
·查看向数据表添加索引对优化器生成高效率执行计划的能力的影响。
这一部分只讨论与示例相关的EXPLAIN输入字段。
前面,在"优化器是如何工作的"部分中我们得出的观点是,你编写表达式的方式将决定优化器是否能使用可用的索引。
特别是上面的讨论使用了下面三个逻辑相等的WHERE子句的例子,只有第三个允许使用索引:
WHERETO_DAYS(date_col)-TO_DAYS(CURDATE())WHERETO_DAYS(date_col)WHEREdate_col EXPLAIN允许你查看编写表达式的某种方式是否比另外的方式好一些。
为了看到结果,让我们分别用这三个WHERE子句搜索成员表中过期的数据列值,把cutoff值设为30天。
为了看到索引的使用和表达式编写方式之间的关系,我们首先对expiration列进行索引:
mysql>ALTERTABLEmemberADDINDEX(expiration);
接着在每个表达式形式上使用EXPLAIN,看优化器生成了什么样的执行计划:
mysql>EXPLAINSELECT*FROMMEMBER
->WHERETO_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:
Usingwhere
mysql>EXPLAINSELECT*FROMMEMBER
->WHERETO_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:
Usingwhere
mysql>EXPLAINSELECT*FROMMEMBER
->WHEREexpiration***************************1.row***************************
id:
1
select_type:
SIMPLE
table:
MEMBER
type:
range
possible_keys:
expiration
key:
expiration
key_len:
4
ref:
NULL
rows:
6
Extra:
Usingwhere
上面的结果显示,前面两个语句没有使用索引。
类型(type)值表明了将如何从数据表中读取信息。
ALL意味着"将检查所有的记录"。
也就是说,它会执行全表扫描,没有利用索引。
每个与键相关的列都是NULL也表明没有使用索引。
与此形成对比的是,第三个语句的结果显示,采用这种方式编写的WHERE子句,优化器可以使用expiration列上的索引:
·类型(type)值表明它可以使用索引来搜索特定范围的值(小于右边表达式给定的值)。
·可能键(possible_keys)和键(key)值显示expiration上的索引已经被考虑作为备选索引,并且它也是真正使用的索引。
·行数(rows)值显示优化器估计自己需要检查6个数据行来处理该查询。
这比前面两个执行计划的102小很多。
EXPLAIN的第二种用途是查看添加索引是否能帮助优化器更高效率地执行语句。
我将使用两个未被索引的数据表。
它足够显示建立索引的效率。
相同的规则可以应用于涉及多表的更加复杂的联结操作。
假设我们有两个数据表t1和t2,每个有1000行,包含的值从1到1000。
下面的查询查找出两个表中值相同的数据行:
mysql>SELECTt1.i1,t2.i2FROMt1,t2WHEREt1.i1=t2.i2;
+------+------+
|i1|i2|
+------+------+
|1|1|
|2|2|
|3|3|
|4|4|
|5|5|
...
两个表都没有索引的时候,EXPLAIN产生下面的结果:
mysql>EXPLAINSELECTt1.i1,t2.i2FROMt1,t2WHEREt1.i1=t2.i2\G
***************************1.row***************************
id:
1
select_type:
SIMPLE
table:
t1
type:
ALL
possible_keys:
NULL
key:
NULL
key_len:
NULL
ref:
NULL
rows:
1000
Extra:
***************************2.row***************************
id:
1
select_type:
SIMPLE
table:
t2
type:
ALL
possible_keys:
NULL
key:
NULL
key_len:
NULL
ref:
NULL
rows:
1000
Extra:
Usingwhere
类型列中的ALL表明要进行检查所有数据行的全表扫描。
可能键列中的NULL表明没有找到用于提高查询速度的备选索引(键、键长度和参考列都是NULL也是因为缺少合适的索引)。
Usingwhere表明使用WHERE子句中的信息来识别合格的数据行。
这段信息告诉我们,优化器没有为提高执行查询的效率找到任何有用的信息:
·它将对t1表进行全表扫描。
·对于t1中的每一行,它将执行t2的全表扫描,使用WHERE子句中的信息识别出合格的行。
行数值显示了优化器估计的每个阶段查询需要检查的行数。
T1的估计值是1000,因为1000可以完成全表扫描。
相似地,t2的估计值也是1000,但是这个值是对于t1的每一行的。
换句话说,优化器所估计的处理该查询所需要检查的数据行组合的数量是1000×1000,也就是一百万。
这会造成很大的浪费,因为实际上只有1000个组合符合WHERE子句的条件。
为了使这个查询的效率更高,给其中一个联结列添加索引并重新执行EXPLAIN语句:
mysql>ALTERTABLEt2ADDINDEX(i2);
mysql>EXPLAINSELECTt1.i1,t2.i2FROMt1,t2WHEREt1.i1=t2.i2\G
***************************1.row***************************
id:
1
select_type:
SIMPLE
table:
t1
type:
ALL
possible_keys:
NULL
key:
NULL
key_len:
NULL
ref:
NULL
rows:
1000
Extra:
***************************2.row***************************
id:
1
select_type:
SIMPLE
table:
t2
type:
ref
possible_keys:
i2
key:
i2
key_len:
5
ref:
sampdb.t1.i1
rows:
10
Extra:
Usingwhere;Usingindex
我们可以看到性能提高了。
T1的输出没有改变(表明还是需要进行全表扫描),但是优化器处理t2的方式就有所不同了:
·类型从ALL改变为ref,意味着可以使用参考值(来自t1的值)来执行索引查找,定位t2中合格的数据行。
·参考值在参考(ref)字段中给出了:
sampdb.t1.i1。
·行数值从1000降低到了10,显示出优化器相信对于t1中的每一行,它只需要检查t2中的10行(这是一个悲观的估计值。
实际上,在t2中只有一行与t1中数据行匹配。
我们在后面会看到如何帮助优化器改善这个估计值)。
数据行组合的全部估计值使1000×10=10000。
它比前面的没有索引的时候估计出来的一百万好多了。
对t1进行索引有价值吗?
实际上,对于这个特定的联结操作,扫描一张表是必要的,因此没有必要对t1建立索引。
如果你想看到效果,可以索引t1.i1并再次运行EXPLAIN:
mysql>ALTERTABLEt1ADDINDEX(i1);
mysql>EXPLAINSELECTt1.i1,t2.i2FROMt1,t2WHEREt1.i1=t2.i2\G
***************************1.row***************************
id:
1
select_type:
SIMPLE
table:
t1
type:
index
possible_keys:
i1
key:
i1
key_len:
5
ref:
NULL
rows:
1000
Extra:
Usingindex
***************************2.row***************************
id:
1
select_type:
SIMPLE
table:
t2
type:
ref
possible_keys:
i2
key:
i2
key_len:
5
ref:
sampdb.t1.i1
rows:
10
Extra:
Usingwhere;Usingindex
上面的输出与前面的EXPLAIN的输出相似,但是添加索引对t1的输出有一些改变。
类型从NULL改成了index,附加(Extra)从空的改成了Usingindex。
这些改变表明,尽管对索引的值仍然需要执行全表扫描,但是优化器还是可以直接从索引文件中读取值,根据不需要使用数据文件。
你可以从MyISAM表中看到这类结果,在这种情况下,优化器知道自己只询问索引文件就能够得到所有需要的信息。
对于InnoDB和BDB表也有这样的结果,在这种情况下优化器可以单独使用索引中的信息而不用搜索数据行。
我们可以运行ANALYZETABLE使优化器进一步优化估计值。
这会引起服务器生成键值的静态分布。
分析上面的表并再次运行EXPLAIN得到了更好的估计值:
mysql>ANALYZETABLEt1,t2;
mysql>EXPLAINSELECTt1.i1,t2.i2FROMt1,t2WHEREt1.i1=t2.i2\G
***************************1.row***************************
id:
1
select_type:
SIMPLE
table:
t1
type:
index
p