7.1.5. 使用自己的基准
一定要测试应用程序和数据库,以发现瓶颈在哪儿。通过修正它(或通过用一个“哑模块”代替瓶颈),可以很容易地确定下一个瓶颈。即使你的应用程序的整体性能目前可以接受,至少应该对每个瓶颈做一个计划,如果某天确实需要更好的性能,应知道如何解决它。
关于一些可移植的基准程序的例子,参见MySQL基准套件。请参见7.1.4节,“MySQL基准套件”。可以利用这个套件的任何程序并且根据你的需要修改它。通过这样做,可以尝试不同的问题的解决方案并测试哪一个是最好的解决方案。 另一个免费基准套件是开放源码数据库基准套件,参见http://osdb.sourceforge.net/。
在系统负载繁重时出现一些问题是很普遍的,并且很多客户已经与我们联系了,他们在生产系统中有一个(测试)系统并且有负载问题。大多数情况下,性能问题经证明是与基本数据库设计有关的问题(例如,表扫描在高负载时表现不好)或操作系统或库问题。如果系统已经不在生产系统中,它们大多数将很容易修正。 为了避免这样的问题,应该把工作重点放在在可能最坏的负载下测试你的整个应用程序。你可以使用Super Smack。该工具可以从
http://jeremy.zawodny.com/mysql/super-smack/获得。正如它的名字所建议,它可以根据你的需要提供合理的系统,因此确保只用于你的开发系统。
7.2. 优化SELECT语句和其它查询
7.2.1. EXPLAIN语法(获取SELECT相关信息) 7.2.2. 估计查询性能
7.2.3. SELECT查询的速度
7.2.4. MySQL怎样优化WHERE子句 7.2.5. 范围优化 7.2.6. 索引合并优化
7.2.7. MySQL如何优化IS NULL 7.2.8. MySQL如何优化DISTINCT
7.2.9. MySQL如何优化LEFT JOIN和RIGHT JOIN 7.2.10. MySQL如何优化嵌套Join 7.2.11. MySQL如何简化外部联合 7.2.12. MySQL如何优化ORDER BY 7.2.13. MySQL如何优化GROUP BY 7.2.14. MySQL如何优化LIMIT 7.2.15. 如何避免表扫描 7.2.16. INSERT语句的速度 7.2.17. UPDATE语句的速度 7.2.18. DELETE语句的速度 7.2.19. 其它优化技巧 首先,影响所有语句的一个因素是:你的许可设置得越复杂,所需要的开销越多。
执行GRANT语句时使用简单的许可,当客户执行语句时,可以使MySQL降低许可检查开销。例如,如果未授予任何表级或列级权限,服务器不需要检查tables_priv和columns_priv表的内容。同样地,如果不对任何 账户进行限制,服务器不需要对资源进行统计。如果查询量很高,可以花一些时间使用简化的授权结构来降低许可检查开销。
如果你的问题是与具体MySQL表达式或函数有关,可以使用mysql客户程序所带的BENCHMARK()函数执行定时测试。其语法为BENCHMARK(loop_count,expression)。例如: mysql> SELECT BENCHMARK(1000000,1+1);
+------------------------+ | BENCHMARK(1000000,1+1) | +------------------------+ | 0 | +------------------------+ 1 row in set (0.32 sec)
上面结果在PentiumII 400MHz系统上获得。它显示MySQL在该系统上在0.32秒内可以执行1,000,000个简单的+表达式运算。
所有MySQL函数应该被高度优化,但是总有可能有一些例外。BENCHMARK()是一个找出是否查询有问题的优秀的工具。
7.2.1. EXPLAIN语法(获取SELECT相关信息)
EXPLAIN tbl_name
或:
EXPLAIN [EXTENDED] SELECT select_options
EXPLAIN语句可以用作DESCRIBE的一个同义词,或获得关于MySQL如何执行SELECT
语句的信息:
· EXPLAIN tbl_name是DESCRIBE tbl_name或SHOW COLUMNS FROM tbl_name的一个同义词。
· 如果在SELECT语句前放上关键词EXPLAIN,MySQL将解释它如何处理SELECT,提供有关表如何联接和联接的次序。 该节解释EXPLAIN的第2个用法。
借助于EXPLAIN,可以知道什么时候必须为表加入索引以得到一个使用索引来寻找记录的更快的SELECT。
如果由于使用不正确的索引出现了问题,应运行ANALYZE TABLE更新表的统计(例如关键字集的势),这样会影响优化器进行的选择。参见13.5.2.1节,“ANALYZE TABLE语法”。
还可以知道优化器是否以一个最佳次序联接表。为了强制优化器让一个SELECT语句按照表命名顺序的联接次序,语句应以STRAIGHT_JOIN而不只是SELECT开头。
EXPLAIN为用于SELECT语句中的每个表返回一行信息。表以它们在处理查询过程中将被MySQL读入的顺序被列出。MySQL用一遍扫描多次联接(single-sweep multi-join)的方式解决所有联接。这意味着MySQL从第一个表中读一行,然后
找到在第二个表中的一个匹配行,然后在第3个表中等等。当所有的表处理完后,它输出选中的列并且返回表清单直到找到一个有更多的匹配行的表。从该表读入下一行并继续处理下一个表。
当使用EXTENDED关键字时,EXPLAIN产生附加信息,可以用SHOW WARNINGS浏览。该信息显示优化器限定SELECT语句中的表和列名,重写并且执行优化规则后SELECT语句是什么样子,并且还可能包括优化过程的其它注解。
EXPLAIN的每个输出行提供一个表的相关信息,并且每个行包括下面的列:
· id
SELECT识别符。这是SELECT的查询序列号。 · select_type
SELECT类型,可以为以下任何一种:
o SIMPLE
简单SELECT(不使用UNION或子查询) o PRIMARY
最外面的SELECT o UNION
UNION中的第二个或后面的SELECT语句 o DEPENDENT UNION
UNION中的第二个或后面的SELECT语句,取决于外面的查询 o UNION RESULT
UNION的结果。 o SUBQUERY
子查询中的第一个SELECT o DEPENDENT SUBQUERY
子查询中的第一个SELECT,取决于外面的查询 o DERIVED
导出表的SELECT(FROM子句的子查询)
· table
输出的行所引用的表。 · type 联接类型。下面给出各种联接类型,按照从最佳类型到最坏类型进行排序:
o system
表仅有一行(=系统表)。这是const联接类型的一个特例。 o const
表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!
const用于用常数值比较PRIMARY KEY或UNIQUE索引的所有部分时。在下面的查询中,tbl_name可以用于const表:
SELECT * from tbl_name WHERE primary_key=1;
SELECT * from tbl_name
WHERE primary_key_part1=1和 primary_key_part2=2; o eq_ref
对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY。
eq_ref可以用于使用= 操作符比较的带索引的列。比较值可以为常量或一个使用在该表前面所读取的表的列的表达式。
在下面的例子中,MySQL可以使用eq_ref联接来处理ref_tables:
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1; o ref
对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY(换句话说,如果联接不能基于关键字选择单个行的话),则使用ref。如果使用的键仅仅匹配少量行,该联接类型是不错的。
ref可以用于使用=或<=>操作符的带索引的列。
在下面的例子中,MySQL可以使用ref联接来处理ref_tables:
SELECT * FROM ref_table WHERE key_column=expr;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1; o ref_or_null
该联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行。在解决子查询中经常使用该联接类型的优化。 在下面的例子中,MySQL可以使用ref_or_null联接来处理ref_tables:
SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL;
参见7.2.7节,“MySQL如何优化IS NULL”。 o index_merge
该联接类型表示使用了索引合并优化方法。在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。详细信息参见7.2.6节,“索引合并优化”。 o unique_subquery
该类型替换了下面形式的IN子查询的ref:
value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery是一个索引查找函数,可以完全替换子查询,效率
更高。
o index_subquery
该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:
value IN (SELECT key_column FROM single_table WHERE some_expr)
o range
只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。
当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range:
SELECT * FROM tbl_name WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1= 10 AND key_part2 IN (10,20,30); o index
该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。
当查询只使用作为单索引一部分的列时,MySQL可以使用该联接类型。
o ALL
对于每个来自于先前的表的行组合,进行完整的表扫描。如果表是第一个没标记const的表,这通常不好,并且通常在它情况下很差。通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常数值或列值被检索出。
· possible_keys
possible_keys列指出MySQL能使用哪个索引在该表中找到行。注意,该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。 如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询。参见13.1.2节,“ALTER TABLE语法”。
为了看清一张表有什么索引,使用SHOW INDEX FROM tbl_name。 · key