一、优化SQL语句的一般步骤
1. 通过show status 命令了解各种SQL的执行频率,具体命令如下:
show [session|global] status like 'Com_%'; 默认是session
1.1 命令显示了当前session 中所有统计参数的值,Com_xxx 表示每个xxx 语句执行的次数,我们通常比较关心的是以下几个统计参数。
Com_select:执行select 操作的次数,一次查询只累加1。
Com_insert:执行INSERT 操作的次数,对于批量插入的INSERT 操作,只累加一次。
Com_update:执行UPDATE 操作的次数。
Com_delete:执行DELETE 操作的次数。
1.2 上面这些参数对于所有存储引擎的表操作都会进行累计。下面这几个参数只是针对
InnoDB 存储引擎的,累加的算法也略有不同。
Innodb_rows_read:select 查询返回的行数。
Innodb_rows_inserted:执行INSERT 操作插入的行数。
Innodb_rows_updated:执行UPDATE 操作更新的行数。
Innodb_rows_deleted:执行DELETE 操作删除的行数。
1.3 此外,以下几个参数便于用户了解数据库的基本情况。
Connections:试图连接MySQL 服务器的次数。
Uptime:服务器工作时间。
Slow_queries:慢查询的次数。
2. 定位执行效率较低的SQL 语句
2.1 利用慢查询日志,具体测试过程可点开下面的链接
http://orangeholic.iteye.com/blog/1700509
2.2 慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用show processlist 命令查看当前MySQL 在进行的线程,包括线程的状态、是否锁表等,可以实时地查看SQL 的执行情况,同时对一些锁表操作进行优化。
3. 通过explain或desc分析低效SQL 的执行计划
3.1 通过以上步骤查询到效率低的SQL 语句后,可以通过EXPLAIN 或者DESC 命令获取MySQL如何执行SELECT 语句的信息,包括在SELECT 语句执行过程中表如何连接和连接的顺序。
每个列的简单解释如下:
select_type:表示SELECT 的类型,常见的取值有SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个SELECT)等。
table:输出结果集的表。
type:表示表的连接类型,性能由好到差的连接类型为:
system(表中仅有一行,即常量表)。
const(单表中最多有一个匹配行,例如primary key 或者unique index)。
eq_ref(对于前面的每一行,在此表中只查询一条记录,简单来说,就是多表连接中使用primary key或者 unique index)。
ref (与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:执行情况的说明和描述。
二、优化SQL之索引问题
在一些情况下,在某些字段上加上了索引(注意不要在有大量重复数据的字段上加索引),sql进行查询时却没有发挥应有的作用,进而导致引擎放弃使用索引而进行全表扫描。下面就来梳理一下这些情况:
如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因),注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。
对于多列索引,不是使用的第一部分,则不会使用索引,这是因为索引是取最左前缀的。
like查询是以%开头,这也是因为索引是取最左前缀的。
如果列类型是字符串,那一定要在条件中将数据使用单引号引用起来,否则不使用索引。所以不管是什么类型,都加上单引号。
查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等) 。
# 错误的例子
select * from test where id-1 = 9;
# 正确的例子
select * from test where id = 10;
在 where 子句中使用 != 或 <> 操作符。
在 where 子句中使用 IN 或 NOT IN,用exists 或 not exists来代替。
查询出来的表上的数据行超出表总记录数30%,变成全表扫描。
如果mysql估计使用全表扫描要比使用索引快,则不使用索引。
此外,查看索引的使用情况
show status like ‘Handler_read%';
大家可以注意:
handler_read_key:这个值越高越好,越高表示使用索引查询到的次数;
handler_read_rnd_next:这个值越高,说明查询低效。
三、两个简单实用的优化SQL方法
3.1 定期分析表和检查表
分析表用于分析和存储表的关键字分布,分析的结果将可以使得系统得到准确的统计信息,使得SQL 能够生成正确的执行计划。
检查表的作用是检查一个或多个表是否有错误。CHECK TABLE 对MyISAM 和InnoDB 表有作用。
# 分析表的语法如下:
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
# 检查表的语法如下:
CHECK TABLE tbl_name [, tbl_name] ... [option] ...
option = {QUICK | FAST | MEDIUM | EXTENDED| CHANGED}
3.2 定期优化表
# 优化表的语法如下:
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
四、常用SQL的优化
4.1. 大批量插入数据
当用load 命令导入数据的时候,适当的设置可以提高导入的速度。
MyISAM
对于MyISAM 存储引擎的表,可以通过以下方式快速的导入大量的数据。
ALTER TABLE tbl_name DISABLE KEYS;
load data infile '/home/mysql/film_test.txt' into table film_test2;
ALTER TABLE tbl_name ENABLE KEYS;
DISABLE KEYS 和ENABLE KEYS 用来打开或者关闭MyISAM 表非唯一索引的更新。在导入
大量的数据到一个非空的MyISAM 表时,通过设置这两个命令,可以提高导入的效率。对于
导入大量数据到一个空的MyISAM 表,默认就是先导入数据然后才创建索引的,所以不用进
行设置。
InnoDB
上面是对MyISAM表进行数据导入时的优化措施,对于InnoDB类型的表,这种方式并不
能提高导入数据的效率,可以有以下几种方式提高InnoDB表的导入效率。
(1)因为InnoDB 类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺
序排列,可以有效地提高导入数据的效率。
(2)在导入数据前执行SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行
SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。
(3)如果应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自
动提交,导入结束后再执行SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。
4.2. 优化INSERT语句
当进行数据INSERT 的时候,可以考虑采用以下几种优化方式。
如果同时从同一客户插入很多行,尽量使用多个值表的INSERT 语句,这种方式将大大
缩减客户端与数据库之间的连接、关闭等消耗,使得效率比分开执行的单个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 倍。
4.3. 优化GROUP BY 语句
默认情况下,MySQL 对所有GROUP BY col1,col2....的字段进行排序。这与在查询中指定
ORDER BY col1,col2...类似。因此,如果显式包括一个包含相同的列的ORDER BY 子句,则
对MySQL 的实际执行性能没有什么影响。
如果查询包括GROUP BY 但用户想要避免排序结果的消耗,则可以指定ORDER BY NULL
禁止排序。禁止后用explain查看的Extra不会显示Using filesort。
4.4. 优化ORDER BY 语句
在某些情况中,MySQL 可以使用一个索引来满足ORDER BY 子句,而不需要额外的排序。WHERE 条件和ORDER BY 使用相同的索引,并且ORDER BY 的顺序和索引顺序相同,并且ORDER BY 的字段都是升序或者都是降序。
例如,下列SQL 可以使用索引。
SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC;
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
但是在以下几种情况下则不使用索引:
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
# order by 的字段混合ASC 和DESC
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
# 用于查询行的关键字与ORDER BY 中所使用的不相同
SELECT * FROM t1 ORDER BY key1, key2;
# 对不同的关键字使用ORDER BY:
4.5. 优化嵌套查询
MySQL4.1开始支持SQL 的子查询。这个技术可以使用SELECT 语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性地完成很多逻辑上需要多个步骤才能完成的SQL 操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)替代。
4.6. MySQL如何优化OR 条件
对于含有OR 的查询子句,如果要利用索引,则OR 之间的每个条件列都必须用到索引;如果没有索引,则应该考虑增加索引。这点在索引优化中有说到。
4.7. 使用SQL 提示
SQL 提示(SQL HINT)是优化数据库的一个重要手段,简单来说就是在SQL 语句中加入一些人为的提示来达到优化操作的目的。
下面是一个使用SQL 提示的例子:
SELECT SQL_BUFFER_RESULTS * FROM...
这个语句将强制MySQL 生成一个临时结果集。只要临时结果集生成后,所有表上的锁
定均被释放。这能在遇到表锁定问题时或要花很长时间将结果传给客户端时有所帮助,因为
可以尽快释放锁资源。
下面是一些在MySQL 中常用的SQL 提示,以下命令加在表名的后面。
1.USE INDEX(索引名)
在查询语句中表名的后面,添加USE INDEX 来提供希望MySQL 去参考的索引列表,就可
以让MySQL 不再考虑其他可用的索引,也就是只使用这一个索引。
2.IGNORE INDEX(索引名)
如果用户只是单纯地想让MySQL 忽略一个或者多个索引,则可以使用IGNORE INDEX 作
为HINT(提示)。
3.FORCE INDEX(索引名)
为强制MySQL 使用一个特定的索引,可在查询中使用FORCE INDEX 作为HINT。例如,
当不强制使用索引的时候,因为id 的值都是大于0 的,因此MySQL 会默认进行全表扫描,
而不使用索引,但是,当使用FORCE INDEX 进行提示时,即便使用索引的效率不是最高,MySQL 还是选择使用了索引。
【注】:本文的编写主要参考《深入浅出MySQL》一书!
