可以通过以下两种方式定位执行效率较低的SQL语句。 通过慢查询日志定位那些执行效率较低的SQL语句,用-log-slow-queries[=file_name]选 项启动时,mysqld写一个包含所有执行时间超过long_query_time秒的SQL语句的日志 文件。
慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢 询日志并不能定位问题,可以使用show processlist命令查看当前MySQL在进行的线程, 包括线程的状态、是否锁表等,可以实时地查看SQL的执行情况,同时对一些锁表操 作进行优化。
select_type:表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接
或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或
者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等。
table:输出结果集的表。
type:表示表的连接类型,性能由好到差的连接类型为 system(表中仅有一行,即
常量表)、const(单表中最多有一个匹配行,例如 primary key 或者 unique index)、 eq_ref(对于前面的每一行,在此表中只查询一条记录,简单来说,就是多表连接 中使用primarykey或者uniqueindex)、re(f 与eq_ref类似,区别在于不是使用primary key 或者 unique index,而是使用普通的索引)、ref_or_null(与 ref 类似,区别在于 条件中包含对 NULL 的查询)、index_merge(索引合并优化)、unique_subquery(in 的后面是一个查询主键字段的子查询)、index_subquery(与 unique_subquery 类似, 区别在于 in 的后面是查询非唯一索引字段的子查询)、range(单表中的范围查询)、 index(对于前面的每一行,都通过查询索引来得到数据)、all(对于前面的每一行,都通过全表扫描来得到数据)。
possible_keys:表示查询时,可能使用的索引。
key:表示实际使用的索引。
key_len:索引字段的长度。
rows:扫描行的数量。
Extra:执行情况的说明和描述。
MyISAM 存储引擎的表的数据和索引是自动分开存储的,各自是独立的一个文件;InnoDB 存储引擎的表的数据和索引是存储在同一个表空间里面,但可以有多个文件组成。
MySQL 中索引的存储类型目前只有两种(BTREE 和 HASH),具体和表的存储引擎相关: MyISAM 和 InnoDB 存储引擎都只支持 BTREE 索引;MEMORY/HEAP 存储引擎可以支持 HASH 和 BTREE 索引。
MySQL 目前不支持函数索引,但是能对列的前面某一部分进索引,例如 name 字段,可 以只取 name 的前 4 个字符进行索引,这个特性可以大大缩小索引文件的大小,用户在设计 表结构的时候也可以对文本列根据此特性进行灵活设计。
索引用于快速找出在某个列中有一特定值的行。对相关列使用索引是提高 SELECT 操作 性能的最佳途径。
查询要使用索引最主要的条件是查询条件中需要使用索引关键字,如果是多列索引,那 么只有查询条件使用了多列关键字最左边的前缀时,才可以使用索引,否则将不能使用索引。
在 MySQL 中,下列几种情况下有可能使用到索引。
(1)对于创建的多列索引,只要查询的条件中用到了最左边的列,索引一般就会被使用, 举例说明如下。
首先按 company_id,moneys 的顺序创建一个复合索引,具体如下:
然后按 company_id 进行表查询,具体如下:
可以发现即便 where 条件中不是用的 company_id 与 moneys 的组合条件,索引仍然能 用到,这就是索引的前缀特性。但是如果只按 moneys 条件查询表,那么索引就不会 被用到,具体如下:
(2)对于使用 like 的查询,后面如果是常量并且只有%号不在第一个字符,索引才可能会 被使用,来看下面两个执行计划:
可以发现第一个例子没有使用索引,而第二例子就能够使用索引,区别就在于“%”的位置 不同,前者把“%”放到第一位就不能用到索引,而后者没有放到第一位就使用了索引。 另外,如果如果 like 后面跟的是一个列的名字,那么索引也不会被使用。
(3)如果对大的文本进行搜索,使用全文索引而不用使用 like ‘%…%’。
(4)如果列名是索引,使用column_name is null将使用索引。如下例中查询name为null 的记录就用到了索引:
在下列情况下,虽然存在索引,但是 MySQL 并不会使用相应的索引。
(1) 如果 MySQL 估计使用索引比全表扫描更慢,则不使用索引。例如如果列
key_part1 均匀分布在 1 和 100 之间,下列查询中使用索引就不是很好:
SELECT * FROM table_name where key_part1 > 1 and key_part1 < 90;
(2) 如果使用 MEMORY/HEAP 表并且 where 条件中不使用“=”进行索引列,那么不会用到索引。heap 表只有在“=”的条件下才会使用索引。
(3) 用 or 分割开的条件,如果 or 前的条件中的列有索引,而后面的列中没有索引, 那么涉及到的索引都不会被用到,例如:
从上面可以发现只有 year 列上面有索引,来看如下的执行计划:
可见虽然在 year 这个列上存在索引 ind_sales_year,但是这个 SQL 语句并没有用到这个索引, 原因就是 or 中有一个条件中的列没有索引。
(4) 如果不是索引列的第一部分,如下例子:
可见虽然在 money 上面建有复合索引,但是由于 money 不是索引的第一列,那么在查询中 这个索引也不会被 MySQL 采用。
5) 如果 like 是以%开始,例如:
可见虽然在 name 上建有索引,但是由于 where 条件中 like 的值的“%”在第一位了,那么 MySQL 也不会采用这个索引。
(6) 如果列类型是字符串,那么一定记得在 where 条件中把字符常量值用引号引 起来,否则的话即便这个列上有索引,MySQL 也不会用到的,因为,MySQL 默认把输入的 常量值进行转换以后才进行检索。如下面的例子中 company2 表中的 name 字段是字符型的, 但是 SQL 语句中的条件值 294 是一个数值型值,因此即便在 name 上有索引,MySQL 也不能 正确地用上索引,而是继续进行全表扫描。
从上面的例子中可以看到,第一个 SQL 语句中把一个数值型常量赋值给了一个字符型的列 name,那么虽然在 name 列上有索引,但是也没有用到;而第二个 SQL 语句就可以正确使 用索引。
如果索引正在工作,Handler_read_key 的值将很高,这个值代表了一个行被索引值读的 次数,很低的值表明增加索引得到的性能改善不高,因为索引并不经常使用。
Handler_read_rnd_next 的值高则意味着查询运行低效,并且应该建立索引补救。这个值 的含义是在数据文件中读下一行的请求数。如果正进行大量的表扫描, Handler_read_rnd_next 的值较高,则通常说明表索引不正确或写入的查询没有利用索引,具体如下。
从上面的例子中可以看出,目前使用的 MySQL 数据库的索引情况并不理想。
对于大多数开发人员来说,可能只希望掌握一些简单实用的优化方法,对于更多更复杂的优 化,更倾向于交给专业 DBA 来做。
分析表的语法如下:
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
本语句用于分析和存储表的关键字分布,分析的结果将可以使得系统得到准确的统计信 息,使得 SQL 能够生成正确的执行计划。如果用户感觉实际执行计划并不是预期的执行计 划,执行一次分析表可能会解决问题。在分析期间,使用一个读取锁定对表进行锁定。这对 于 MyISAM, BDB 和 InnoDB 表有作用。对于 MyISAM 表,本语句与使用 myisamchk -a 相当,下例中对表 msgs 做了表分析:
检查表的语法如下:
CHECK TABLE tbl_name [, tbl_name] ... [option] ... option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}
检查表的作用是检查一个或多个表是否有错误。CHECK TABLE 对 MyISAM 和 InnoDB 表有作用。 对于 MyISAM 表,关键字统计数据被更新,例如:
CHECK TABLE 也可以检查视图是否有错误,比如在视图定义中被引用的表已不存在,举例如 下。
(1)首先我们创建一个视图。
(2)然后 CHECK 一下该视图,发现没有问题。
(3)现在删除掉视图依赖的表。
(4)再来 CHECK 一下刚才的视图,发现报错了。
优化表的语法如下:
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
如果已经删除了表的一大部分,或者如果已经对含有可变长度行的表(含有 VARCHAR、 BLOB 或 TEXT 列的表)进行了很多更改,则应使用 OPTIMIZE TABLE 命令来进行表优化。这个 命令可以将表中的空间碎片进行合并,并且可以消除由于删除或者更新造成的空间浪费,但 OPTIMIZE TABLE 命令只对 MyISAM、BDB 和 InnoDB 表起作用。
以下例子显示了优化表 sales 的过程:
前面我们介绍了 MySQL 中怎么样通过索引来优化查询。日常开发中,除了使用查询外,我 们还会使用一些其他的常用 SQL,比如 INSERT、GROUP BY 等。
当用 load 命令导入数据的时候,适当的设置可以提高导入的速度。
对于 MyISAM 存储引擎的表,可以通过以下方式快速的导入大量的数据。
DISABLE KEYS 和 ENABLE KEYS 用来打开或者关闭 MyISAM 表非唯一索引的更新。在导入 大量的数据到一个非空的 MyISAM 表时,通过设置这两个命令,可以提高导入的效率。对于 导入大量数据到一个空的 MyISAM 表,默认就是先导入数据然后才创建索引的,所以不用进 行设置。
下面例子中,用 LOAD 语句导入数据耗时 115.12 秒:
而用 alter table tbl_name disable keys 方式总耗时 6.34 + 12.25 = 18.59 秒,提高了 6 倍多。
上面是对MyISAM表进行数据导入时的优化措施,对于InnoDB类型的表,这种方式并不 能提高导入数据的效率,可以有以下几种方式提高InnoDB表的导入效率。
(1)因为 InnoDB 类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺 序排列,可以有效地提高导入数据的效率。
例如,下面文本film_test3.txt是按表film_test4的主键存储的,那么导入的时候共耗时 27.92秒。
而下面的 film_test4.txt 是没有任何顺序的文本,那么导入的时候共耗时 31.16 秒。
从上面例子可以看出当被导入的文件按表主键顺序存储的时候比不按主键顺序存储的时候 快 1.12 倍。
(2)在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行 SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。
例如,当 UNIQUE_CHECKS=1 时:
当 SET UNIQUE_CHECKS=0 时:
可见比 UNIQUE_CHECKS=0 的时候比 SET UNIQUE_CHECKS=1 的时候要快一些。
(3)如果应用使用自动提交的方式,建议在导入前执行 SET AUTOCOMMIT=0,关闭自
动提交,导入结束后再执行 SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。 例如,当 AUTOCOMMIT=1 时:
当 AUTOCOMMIT=0 时:
对比一下可以知道,当 AUTOCOMMIT=0 时比 AUTOCOMMIT=1 时导入数据要快一些。
当进行数据 INSERT 的时候,可以考虑采用以下几种优化方式。
insert into test values(1,2),(1,3),(1,4)...
如果从不同客户插入很多行,能通过使用 INSERT DELAYED 语句得到更高的速度。 DELAYED 的含义是让 INSERT 语句马上执行,其实数据都被放在内存的队列中,并没有 真正写入磁盘,这比每条语句分别插入要快的多;LOW_PRIORITY 刚好相反,在所有其 他用户对表的读写完后才进行插入;
将索引文件和数据文件分在不同的磁盘上存放(利用建表中的选项);
如果进行批量插入,可以增加 bulk_insert_buffer_size 变量值的方法来提高速度,但是,这只能对 MyISAM 表使用;
当从一个文本文件装载一个表时,使用 LOAD DATA INFILE。这通常比使用很多 INSERT 语句快 20 倍。
默认情况下,MySQL 对所有 GROUP BY col1,col2….的字段进行排序。这与在查询中指定 ORDER BY col1,col2…类似。因此,如果显式包括一个包含相同的列的 ORDER BY 子句,则 对 MySQL 的实际执行性能没有什么影响。
如果查询包括 GROUP BY 但用户想要避免排序结果的消耗,则可以指定 ORDER BY NULL 禁止排序,如下面的例子:
从上面的例子可以看出第一个 SQL 语句需要进行“filesort”,而第二个 SQL 由于 ORDER BY NULL 不需要进行“filesort”,而 filesort 往往非常耗费时间。
在某些情况中,MySQL 可以使用一个索引来满足 ORDER BY 子句,而不需要额外的排序。 WHERE 条件和 ORDER BY 使用相同的索引,并且 ORDER BY 的顺序和索引顺序相同,并且 ORDER BY 的字段都是升序或者都是降序。
但是在以下几种情况下则不使用索引:
MySQL 4.1 开始支持 SQL 的子查询。这个技术可以使用 SELECT 语句来创建一个单列的查 询结果,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性地完成很 多逻辑上需要多个步骤才能完成的 SQL 操作,同时也可以避免事务或者表锁死,并且写起 来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)替代。
在下面的例子中,要从 sales2 表中找到那些在 company2 表中不存在的所有公司的信息:
果使用连接(JOIN)来完成这个查询工作,速度将会快很多。尤其是当 company2 表 中对 id 建有索引的话,性能将会更好,具体查询如下:
从执行计划中可以明显看出查询扫描的记录范围和使用索引的情况都有了很大的改善。
连接(JOIN)之所以更有效率一些,是因为 MySQL 不需要在内存中创建临时表来完成这 个逻辑上的需要两个步骤的查询工作。
对于含有 OR 的查询子句,如果要利用索引,则 OR 之间的每个条件列都必须用到索引; 如果没有索引,则应该考虑增加索引。
例如,首先使用 show index 命令查看表 sales2 的索引,可知它有 3 个索引,在 id、year 两个字段上分别有 1 个独立的索引,在 company_id 和 year 字段上有 1 个复合索引。
然后在两个独立索引上面做 OR 操作,具体如下:
可以发现查询正确的用到了索引,并且从执行计划的描述中,发现 MySQL 在处理含有 OR 字句的查询时,实际是对 OR 的各个字段分别查询后的结果进行了 UNION。 但是当在建有复合索引的列 company_id 和 moneys 上面做 OR 操作的时候,却不能用到索引, 具体结果如下:
SQL 提示(SQL HINT)是优化数据库的一个重要手段,简单来说就是在 SQL 语句中加入一些 人为的提示来达到优化操作的目的。
下面是一个使用 SQL 提示的例子:
SELECT SQL_BUFFER_RESULTS * FROM...
这个语句将强制 MySQL 生成一个临时结果集。只要临时结果集生成后,所有表上的锁 定均被释放。这能在遇到表锁定问题时或要花很长时间将结果传给客户端时有所帮助,因为 可以尽快释放锁资源。
下面是一些在 MySQL 中常用的 SQL 提示。
在查询语句中表名的后面,添加 USE INDEX 来提供希望 MySQL 去参考的索引列表,就可 以让 MySQL 不再考虑其他可用的索引。
如果用户只是单纯地想让 MySQL 忽略一个或者多个索引,则可以使用 IGNORE INDEX 作
为 HINT。同样是上面的例子,这次来看一下查询过程忽略索引 ind_sales2_id 的情况:
从执行计划可以看出,系统忽略了指定的索引,而使用了全表扫描。
为强制 MySQL 使用一个特定的索引,可在查询中使用 FORCE INDEX 作为 HINT。例如, 当不强制使用索引的时候,因为 id 的值都是大于 0 的,因此 MySQL 会默认进行全表扫描, 而不使用索引,如下所示:
但是,当使用 FORCE INDEX 进行提示时,即便使用索引的效率不是最高,MySQL 还是选择使 用了索引,这是 MySQL 留给用户的一个自行选择执行计划的权力。加入 FORCE INDEX 提示 后再次执行上面的 SQL:
果然,执行计划中使用了 FORCE INDEX 后的索引。
SQL优化调试在日常的开发工作中很重要,快速定位到问题不仅需要一定的技巧,更需要的是经验,熟练使用explain关键字和慢查询日志。