在分析性能欠佳的查询时,应考虑:mysql
1) 应用程序是否正获取超过须要的数据,即访问了过多的行或列。程序员
2) Mysql服务器是否分析了超过须要的行。算法
若是发现访问的数据行数很大,而生成的结果中数据行不多,那么能够尝试修改,好比使用覆盖索引、更改架构或重写查询让优化器能够以优化的方式执行它。sql
优化最终集中在减小IO,下降CPU,提升查询速度。数据库
通常应用中数据库一般是IO密集型的,大部分数据库操做中超过90%的时间是由IO操做所占用,因此减小IO访问次数是SQL优化中首要考虑的因素。除了IO外,须要再考虑优化CPU的运算量。一般,ORDER BY、GROUP BY、DISTINCT和一些比较运算都是主要消耗CPU的地方。下降CPU计算也就成为优化的重要目标。缓存
能够经过 SHOW STATUS 提供的服务器状态信息,或使用 mysqladmin extende d-status 命令得到。 SHOW STATUS 能够根据须要显示 session 级别的统计结果和 global级别的统计结果。服务器
为了近似于实时知道服务器性能,能够周期性运行SHOW STATUS,而且和前一次的输出进行比较。如:网络
Mysqladmin extended –r –i 10session
由于输出不少,能够把结果导入到grep中,过滤掉本身不想看的变量,也可使用innotop或其余工具(如mysqlreport)来检查结果。一些值得监控的变量是:架构
Bytes_received和Bytes_send:和服务器之间来往的流量。
Com_* : 服务器正在执行的命令
Created_*: 在查询执行期间建立的临时表和文件
Handler_*: 存储引擎操做
Select_*: 不一样类型的联接执行计划
Sort_*: 几种排序信息
同时用这种方法还能监视MySQL的内部操做,例如:键访问的次数、为MyISAM从磁盘上进行的键读取、数据访问率、为InnoDB从磁盘上读取的数据,等等。这有助于定位系统中实际的和潜在的瓶颈,而无需调查每个查询。
如下几个参数是对 MyISAM 和 InnoDB 存储引擎的计数:
1. Com_select 执行 select 操做的次数,一次查询只累加 1 ;
2. Com_insert 执行 insert 操做的次数,对于批量插入的 insert 操做,只累加一次 ;
3. Com_update 执行 update 操做的次数;
4. Com_delete 执行 delete 操做的次数;
如下几个参数是针对 Innodb 存储引擎计数,累加的算法略有不一样:
1. Innodb_rows_read select 查询返回的行数;
2. Innodb_rows_inserted 执行 Insert 操做插入的行数;
3. Innodb_rows_updated 执行 update 操做更新的行数;
4. Innodb_rows_deleted 执行 delete 操做删除的行数;
经过以上几个参数,能够了解到当前数据库的应用是以插入更新为主还 是以查询操做为主,以及各类类型的 SQL大体的执行比例。注意对更新操做的计数是针对执行次数的计数,不论提交仍是回滚都会累加。
对于事务型的应用,经过 Com_commit 和 Com_rollback 能够了解事务提交和回 滚的状况,对于回滚操做很是频繁的数据库,可能意味着应用编写存在问题。此外,如下几个参数便于咱们了解数据库的基本状况:
1. Connections 试图链接 Mysql 服务器的次数
2. Uptime 服务器工做时间
3. Slow_queries 慢查询的次数
为找到耗费了最多时间的工做,能够将测试分析创建在任何适合的粒度上:能够总体分析服务器,或者检查单个查询或批查询。获得信息包括:
* MySQL访问得最多的数据。
* MySQL执行得最多的查询的种类。
* MySQL停留时间最长的状态
* MySQL用来执行查询的使用得最频繁的子系统
* MySQL查询过程当中访问的数据种类
* MySQL执行了多少种不一样类型的活动,好比索引扫描
一旦肯定查询只获取了所须要的数据,那么接下来不该检查在生成查询结果时是否检查了过多数据,测量指标主要:执行时间、检查行数、返回行数。
能够经过如下两种方式定位执行效率较低的 SQL 语句:
1). 能够经过慢查询日志定位那些执行效率较低的 sql 语句
用 --log-slow-queries[=file_name] 选项启动时, mysqld 写一个包含全部执行时间超过long_query_time 秒的 SQL 语句的日志文件。
测量的三个指标(执行时间、检查行数、返回行数)都被写入了慢查询日志,故慢查询日志是检索查找过多数据查询的最佳方式。
注意:执行时间在不一样负载下表现是不同的;在理想状况下,返回的行和检查的行应该是同样的,但其实是不可能的,例:使用联接查询时就需访问更多的行来产生一行输出。一般说来,检查的行和返回的行之间的比率一般较小,在1:1到10:1之间。
2). 使用 show processlist查看当前MySQL的线程
慢查询日志在查询结束之后才纪录,因此在应用反映执行效率出现问题的时候查询慢查询日志并不能有效定位问题,可使用 show processlist 命令查看当前 MySQL 在进行的线程,包括线程的状态,是否锁表等等,它不只能够显示哪一种查询正在执行,也能看到链接的状态。一些因素,好比大量链接处于锁定状态,是瓶颈的明显线索。。
经过以上步骤查询到效率低的 SQL 后,咱们能够经过 explain 或者 desc 获取MySQL 如何执行 SELECT 语句的信息,包括 select 语句执行过程表如何链接和链接 的次序。
经过EXPLAIN来探知访问类型,访问在扫描表、索引、范围和常量时的速度是不同的。若是没有获得好的访问类型,那么最好的解决办法是加一个索引。
一般说来,MySQL会在3种状况下使用where子句,从最好到最坏依次是:
* 对索引查找应用where子句来消除不匹配的行,这发生在存储引擎层;
* 使用覆盖索引来避免访问行(”Using Index’),而且从索引取得数据后过滤掉不匹配的行。这发生在服务器层
* 从表中检索出数据,而后过滤掉不匹配的行(‘Using Where’)。这发生在服务器端而且要求在过滤以前读取这些行。
索引用于快速找出在某个列中有一特定值的行。对相关列使用索引是提升SELECT 操做性能的最佳途径。
查询要使用索引最主要的条件是查询条件中须要使用索引关键字,若是是多列索引,那么只有查询条件使用了多列关键字最左边的前缀时(前缀索引),才可使用索引,不然将不能使用索引。
下列状况下, Mysql 不会使用已有的索引:
一、若是 mysql 估计使用索引比全表扫描更慢,则不使用索引。例如:若是 key_part 1均匀分布在 1 和 100 之间,下列查询中使用索引就不是很好:
SELECT * FROM table_name where key_part1 > 1 and key_part1 < 90
二、若是使用 heap 表而且 where 条件中不用=索引列,其余 > 、 < 、 >= 、 <= 均不使 用索引(MyISAM和innodb表使用索引);
三、查询条件里使用了函数(WHERE DAY(column) = …)或索引字段是表达式的一部分,则不会使用索引。
四、使用or分割的条件,若是or前的条件中的列有索引,后面的列中没有索引,那么涉及到的索引都不会使用。
五、若是建立复合索引,若是条件中使用的列不是索引列的第一部分;(不是前缀索引)
六、比较操做符LIKE和REGEXP的搜索模板的第一个字符是通配符,如 like 是以%开始时,不使用索引。
七、对 where 后边条件为字符串的必定要加引号,字符串若是为数字 mysql 会自动转为字符串,可是不使用索引。
经过下面几个参数来了解索引使用状况:
Handler_read_key 请求数字基于键读行。
Handler_read_next 请求读入基于一个键的一行的次数。
若是索引正在工做, Handler_read_key 的值将很高,这个值表明了一个行被索引值读的次数,很低的值代表增长索引获得的性能改善不高,由于索引并不常常使 用。
Handler_read_rnd_next 的值高则意味着查询运行低效,而且应该创建索引补救。这个值的含义是在数据文件中读下一行的请求数。若是你正进行大量的表扫描,该值较高。一般说明表索引不正确或写入的查询没有利用索引。
咱们能够经过FLUSH STATUS和SHOW SESSION STATUS相结合来分析查询或批处理查询。这是一种优化查询的好办法:
首先,运行FLUSH STATUS把会话状态变量设置为零,这样就能够知道MySQL执行查询时作了多少工做:mysql > FLUSH STATUS;
接下来运行查询。咱们添上了SQL_NO_CACHE,这样MySQL不会从查询缓存中取得查询结果。
首先,运行FLUSH STATUS把会话状态变量设置为零,这样就能够知道MySQL执行查询时作了多少工做:mysql > FLUSH STATUS;
接下来运行查询。咱们添上了SQL_NO_CACHE,这样MySQL不会从查询缓存中取得查询结果:
mysql> select sql_no_cache id, count(*) from tb inner join tb1 using(id) group by id order by count(*) desc;
…
4 rows in set (0.72 sec)
如今来分析这条语句的运行?先看看服务器选择的查询计划:
mysql> show session status like 'select%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| Select_full_join | 0 |
| Select_full_range_join | 0 |
| Select_range | 0 |
| Select_range_check | 0 |
| Select_scan | 2 |
+------------------------+-------+
5 rows in set (0.00 sec)
看上去MySQL进行了全表扫描。若是查询涉及了多张表,有几个变量的值就会大于零。例如,若是MySQL在后续表中进行了范围扫描,以寻找匹配行,select_full_range_join变会有值,甚至还能够查看查询执行的低层次存储引擎操做:
mysql> show session status like 'Handler%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 1 | --- 请求读入表中第一行的次数。
| Handler_read_key | 12 | ---请求数字基于键读行。
| Handler_read_next | 0 | ---请求读入基于一个键的一行的次数
| Handler_read_prev | 0 |
| Handler_read_rnd | 4 | ---请求读入基于一个固定位置的一行的次数
| Handler_read_rnd_next | 14 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 7 |
+----------------------------+-------+
15 rows in set (0.02 sec)
“读(read)”操做的值很高,意味着MySQL须要扫描多个表才能知足查询须要。一般,若是MySQL只对一个表使用了全表扫描,咱们就会看到Handler_read_rn_next的值较高,而且Handler_read_rnd是零。
在这个例子中,多个非零值意味着MySQL必须使用临时表来知足GROUP BY和ORDER BY子句,这是Handler_write和Handler_update不为零的缘由:MySQL假定写入临时表,扫描它并进行排序,而后再次进行扫描,输出排序后的结果。
再来看看MySQL为排序作了些什么:
mysql> show session status like 'Sort%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Sort_merge_passes | 0 |
| Sort_range | 0 |
| Sort_rows | 4 |
| Sort_scan | 1 |
+-------------------+-------+
4 rows in set (0.01 sec)
正如咱们猜想那样,MySQL经过扫描包含输出中全部行的临时表进行排序。若是值多于4行,咱们怀疑它在查询执行的过程当中在别的地方进行了排序。还能看到MySQL为查询建立了多少临时表:
mysql> show session status like 'created%';
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0 |
| Created_tmp_files | 0 |
| Created_tmp_tables | 2 |
+-------------------------+-------+
3 rows in set (0.00 sec)
Create_tmp_disk_tables为0表示不须要使用磁盘上的临时表。
咱们能够经过将命令运行两次,并将第2次结果减去第1次结果来计算开销,获得精确的结果。这样咱们就能够知晓MySQL在执行查询的过程当中作了多少工做。这将是咱们进行优化的基础。
在默认状况下,分析是关闭的,可是能够在会话的层面打开。打开它会让服务器收集用于执行查询的资源信息。在开始收集统计信息以前,须要把分析变量设置为1:
Mysql>set profiling = 1;
如今运行查询:
mysql> select sql_no_cache id, count(*) from tb inner join tb1 using(id) group by id order by count(*) desc;
+----+----------+
| id | count(*) |
+----+----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
+----+----------+
4 rows in set (0.00 sec)
查询的分析数据被保存在会话中。使用SHOW PROFILES查看已经被分析事后查询:
mysql> show profiles\G;
*************************** 1. row ***************************
Query_ID: 1
Duration: 0.00242825
Query: select sql_no_cache id, count(*) from tb inner join tb1 using(id) group by id order by count(*) desc
1 row in set (0.00 sec)
ERROR:
No query specified
可使用SHOW PROFILE命令取得被保存下来的分析数据。若是不加任何参数,它就会显示最近一个命令的状态值和运行时间:
mysql> show profile;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.001015 |
| Opening tables | 0.000042 |
| System lock | 0.000013 |
| Table lock | 0.000024 |
| init | 0.000097 |
| optimizing | 0.000027 |
| statistics | 0.000092 |
| preparing | 0.000036 |
| Creating tmp table | 0.000261 |
| executing | 0.000010 |
| Copying to tmp table | 0.000288 |
| Sorting result | 0.000093 |
| Sending data | 0.000048 |
| end | 0.000006 |
| removing tmp table | 0.000036 |
| end | 0.000008 |
| query end | 0.000010 |
| freeing items | 0.000202 |
| logging slow query | 0.000011 |
| logging slow query | 0.000098 |
| cleaning up | 0.000013 |
+----------------------+----------+
21 rows in set (0.00 sec)
每一行表明了进程的一种变化,以及在这种状态停留的时间。Status列和SHOW FULL PROCESSLIST输出的State列是对应的。它的值来自于thd->proc_info变量,所以能够直接看到MySQL内部的值。尽管它们的名字都很直观,也不难理解,可是这些变量仍是能够从MySQL的手册中找到。
能够从SHOW PROFILES的输出中获得特定的Query_ID,而且进行指定的分析,而且还能够定义输出的其他列。例如,为了解执行查询时用户的CPU使用率,可使用下面的命令:
Mysql> SHOW PROFILE CPU FOR QUERY 1;
SHOW PROFILE很好地揭示了服务器执行查询时所作的事情,而且有助于理解查询在操做上花费时间。它的局限是未实现的特性,不能查看和分析其余联接的查询,以及因为分析带来的开销。
1)关联子查询(Correlated Subqueries):MySQL有时把子查询优化得不好,最差的就是在Where子句中使用IN。优化语句须要考虑到执行顺序和缓存:从里向外执行查询是一种优化方式 ,缓存内部查询的结果是另一种方式。重写查询时应考虑兼顾这两方面。
但MySQL不会老是把关联子查询优化得不好,好比Exists在逻辑上表达“有一个匹配”概念,它不会产生任何重复的行,也可以避免使用GROUP BY和DISTINCT操做,有时子查询会比联接快得多。对待子查询不能有绝对的态度,应该用测试来证实。
2)联合的限制:MySQL有时不能把UNION外的一些条件“下推”到UNION的内部,而这些外部条件原本用于限制结果或者产生优化。具体应该把LIMIT子句或ORDER BY子句添加到UNION内部的每个子句上。
3)索引合并优化:索引合并算法能够在查询中对一个表使用多个索引。查询能够同时扫描多个索引,而且合并结果,这种算法有3种变体,分别是:对OR取并集、对AND取交集、对AND和OR的组合并集。但有时这种算法的缓冲、排序和合并操做使用了大量的CPU和内存资源,对于没有足够区分性的索引,并行扫描会返回大量须要合并操做的列,这种状况就更容易发生。
若是查询由于优化器的限制而运行得很慢,那么能够经过IGNORE INDEX命令禁止一些索引,或使用老的UNION策略。
4)相等传递:优化器经过把相应的列拷贝到相关的表里来共享列表。这一般是有用的,由于它让优化器和执行引擎有更多的机会选择执行IN操做的时机。可是若是这个列很是大,它就可能致使较慢的优化和执行。
5)并行执行:MySQL不能在多个CPU上并行执行一个查询。
6)哈希联接(Hash Join):MySQL还不能真正执行哈希联接。
7)松散索引扫描(Loose Index Scan):MySQL不支持松散索引扫描,即扫描不连续的索引。MySQL索引扫描一般都须要一个肯定的起点和终点,即便查询只须要其中一些不连续的行,MySQL也会扫描起点到终点范围内的全部行。
例:假设某表在列(a,b)上有索引,要执行查询:select … from tb1 where b between 2 and 3;由于索引是从a开始,可是Where子句中没有列a,MySQL将会全表扫描而且去掉不匹配的行。Explain中”Using Index for group-by”表示查询使用了松散索引扫描。
8)Min()和Max():MySQL不能很好地优化MIN()和MAX()。
9)对同一个表进行SELECT和UPDATE:MySQL不会让你在对一个表进行UPDATE的同时运行SELECT。一个变通的方式是衍生表(临时表),这样能够有效地处理两个查询:一是在子查询内部使用SELECT,二是使用表和子查询的结果进行联接,而后进行更新:
mysql> UPDATE tb1 INNER JOIN(
SELECT type, count(*) AS cnt FROM tb1 GROUP BY type) AS der USING(type)
SET tb1.cnt = der.cnt;
MySQL可以处理的一些优化类型是:
》对联接中的表从新排序
》对外联接转换成内联接
》代数等价法则:如5=5 AND a > 5等价于a>5
》优化COUNT()、MIN()和MAX()
》计算和减小常量表达式
》覆盖索引
》子查询优化
》尽早中止:一旦知足查询或某个步骤的条件,MySQL就会当即中止处理该查询或该步骤
》相等传递:
》比较IN()里面的数据:MySQL会对IN()里面的数据进行排序,而后用二分法查找某个值是否在列表中。这个算法效率是O(logn),而等同的OR 子句的效率是O(N),在列表很大的时候,OR子句会慢得多。
对查询进行优化,应尽可能避免全表扫描,首先应考虑在 where 及 order by 涉及的列上创建索引。相对于使用给定的索引,全表扫描将很是耗时。能够尝试下面的技巧以免优化器错选了表扫描:
* 使用ANALYZE TABLEtbl_name为扫描的表更新关键字分布。
* 对扫描的表使用FORCE INDEX。
SELECT * FROM t1, t2 FORCE INDEX (index_for_column) WHERE t1.col_name=t2.col_name;
* 用--max-seeks-for-key=1000选项启动MySQL或者使用SET max_seeks_for_key=1000告知优化器扫描不会超过1,000次关键字搜索。
虽然对表创建了索引,但表查询仍然可能会使用全表索引。
1) 应尽可能避免在 where 子句中对字段进行 null 值判断
任何在where子句中使用is null或is not null的语句优化器都是不容许使用索引,相反会进行全表扫描,如:
select fa from tb where fb is null
NULL对于大多数数据库都须要特殊处理,MySQL也不例外,它须要更多的代码,更多的检查和特殊的索引逻辑,只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。因此咱们在数据库设计时不要让字段的默认值为NULL。
2) 应尽可能避免在 where 子句中使用!=或<>操做符
一般MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。若是查询条件中存在!=或<>符号时,MySQL将没法使用索引而进行全表扫描。
对于LIKE语句,只有在不是以通配符(%或者_)开头的查询下才会使用索引。例如:
SELECT fa FROM tb WHERE fb LIKE 'Mich%'; # 这个查询将使用索引,
SELECT fa FROM tb WHERE fb LIKE '%ike'; #这个查询不会使用索引。
对于TEXT类型,若要提升效率,可考虑全文检索。
3) 应尽可能避免在 where 子句中使用 or 来链接条件
WHERE语句中使用OR,且没有使用覆盖索引,会进行全表扫描。如:
select id from t where num=10 or num=20
能够 使用UNION合并查询:
select id from t where num=10 union all select id from t where num=20
在某些状况下,or条件能够避免全表扫描的:必须全部的or条件都是独立索引。
4) 慎用in 和 not in,不然会致使全表扫描
MySQL索引能很好地匹配匹配范围值,但这也只会使用索引第一列,同时也不能跳过索引中的列。对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
使用BETWEEN语句时也需注意:MySQL不支持松散索引扫描,即扫描不连续的索引。MySQL索引扫描一般都须要一个肯定的起点和终点,即便查询只须要其中一些不连续的行,MySQL也会扫描起点到终点范围内的全部行。
例:假设某表在列(a,b)上有索引,要执行查询:select … from tb1 where b between 2 and 3;由于索引是从a开始,可是Where子句中没有列a,MySQL将会全表扫描而且去掉不匹配的行
5) 若是在 where 子句中使用参数,也会致使全表扫描。
由于SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,若是在编译时创建访问计划,变量的值仍是未知的,于是没法做为索引选择的输入项。以下面语句将进行全表扫描:
select id from t where num=@num
能够改成强制查询使用索引: select id from t with(index(索引名)) where num=@num
6) 应尽可能避免在 where 子句中对字段进行表达式操做。
这将致使MySQL放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
该查询不会使用索引。MySQL不会帮你求解方程,应该养成简化WHERE子句的习惯,这样就会把被索引的列单独放在比较运算符的一边,不要在 where 子句中的“=”左边进行函数、算术运算或其余表达式运算。上句可改成:
select id from t where num=100*2 或者selece id from t where num=200;
7) 应尽可能避免在where子句中对字段进行函数操做,
这一样会致使MySQL放弃使用索引而进行全表扫描。如:
SELECT … WHERE TO_DAYS(CURRENT_DATE) – TO_DAYS(date_col) <= 10;
这个查询将会查找date_col值离今天不超过10天的全部行,可是它不会使用索引,由于使用了TO_DAYS()函数。下面是一种较好的方式:
SELECT … WHERE date_col >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY) ;
使用CURRENT_DATE将会阻止查询缓存把结果缓存起来,能够用常量替换CURRENT_DATE的值对上条语句进行改进:
SELECT … WHERE date_col >=DATE_SUB(‘2008-01-17’, INTERVAL 10 DAY);
8) 索引字段不是复合索引的前缀索引
一般MySQL索引对于如下类型的查询有用(如下针对B-Tree树索引):
* 匹配全名: 全键值匹配指和索引中的全部列匹配。
* 匹配最左前缀:这仅仅适用了索引中的第一列。
* 匹配列前缀:能够匹配某列的值的开头部分
若是查找没有从索引列的最左边开始,它就没有什么用处。若是该索引是复合索引,那么必须使用到该索引中的第一个字段做为条件时才能保证系统使用该索引,不然该索引将不会被使用,而且应尽量的让字段顺序与索引顺序相一致。例如在表tb上存在索引(fa, fb, fc),这种索引不能帮助查询存在fb查询条件同时却没有定义fa条件的数据,即这种查询不能使用上索引(fa, fb, fc)。
一样查询条件不能跳过索引中的列。一样以上面表tb的索引(fa, fb, fc),索引也不能帮助查询指定fa、fc条件同时缺没有定义fb查询条件的数据。
另外,存储引擎也不能优化访问任何在第一个范围条件右边的列。例如表tb上的索引(fa,fb,fc),若是查询形如WHERE fa=’….’ AND fb LIKE ‘x%’ AND fc=’….’,那么访问就只能使用索引的头两列(fa, fb),由于LIKE是范围条件。
这些局限都和列顺序有关,因此列顺序极端重要了。对于高性能应用程序,也许要针对相同列以不一样顺序建立多个索引,以知足程序要求。
这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合B-Tree索引)。而(B,A)上的索引就不会是多余的,(B)上的索引也不是,由于列B不是列(A,B)的最左前缀。
1) 不要写一些没有意义的查询,
如生成一个空表结构:
select col1, col2 into #t from t where 1=0
上面这条语句不会返回任何结果集,可是会消耗系统资源的,应改用: create table #t(...)
2) 不少时候用 exists 代替 in 是一个好的选择:
EXISTS用于检查子查询是否至少会返回一行数据,用于检测行的存在,该子查询实际上并不返回任何数据,而是返回值True或False。EXISTS在逻辑上表达“有一个匹配”概念,它不会产生任何重复的行,也可以避免使用GROUP BY和DISTINCT操做:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
关于EXISTS与IN查询的效率,能够网上找‘mysql in和exists性能比较和使用’查看。具体以测试结果为准。
3) 并非全部索引对查询都有效,
SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即便在sex上建了索引也对查询效率起不了做用。一般认为在这样字段上不适合于创建索引。
4) 索引并非越多越好
索引当然能够提升相应的 select 的效率,但同时也下降了 insert 及 update 的效率,由于 insert 或 update 时有可能会重建索引,因此怎样建索引须要慎重考虑,视具体状况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
在任何可能的地方,首先应试着扩展索引,而不是新增索引。一般维护一个多列索引要比维护多个单列索引容易。
5) 应尽量的避免更新 clustered 索引数据列
由于 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将致使整个表记录的顺序的调整,会耗费至关大的资源。若应用系统须要频繁更新 clustered 索引数据列,那么须要考虑是否应将该索引建为 clustered 索引。
6) 尽可能使用数字型字段
若只含数值信息的字段尽可能不要设计为字符型,这会下降查询和链接的性能,并会增长存储开销。这是由于引擎在处理查询和链接时会逐个比较字符串中每个字符,而对于数字型而言只须要比较一次就够了。
特别是在做关键字段的类型选择上,要同时考虑存储类型和MySQL如何对它们进行计算和比较。
7) 尽量的使用 varchar/nvarchar 代替 char/nchar
由于首先变长字段存储空间小,能够节省存储空间,其次对于查询来讲,在一个相对较小的字段内搜索效率显然要高些。
8) 最好不要使用"*"返回全部: select * from t
若要利用覆盖索引,则应用具体的字段列表代替“*”,不要返回用不到的任何字段。
临时表通常都不多用,通常是程序中动态建立或者由MySQL内部根据SQL执行计划须要时建立。
内存表则大多数是当Cache用,随着memcache、NoSQL的流行,内存表也愈来愈少使用了。
1) 避免频繁建立和删除临时表,以减小系统表资源的消耗。
即使在删除临时表数据时,建议使用TRUNCATE TABLE 替代DELETE操做。
2) 在新建临时表时,若是一次性插入数据量很大,那么可使用 select into 代替 create table,避免形成大量 log ,以提升速度;若是数据量不大,为了缓和系统表的资源,应先create table,而后insert。
3) 若是使用到了临时表,在存储过程的最后务必将全部的临时表显式删除,先 truncate table ,而后 drop table ,这样能够避免系统表的较长时间锁定。
使用EXPLAIN分析查询语句时,extra列显示“using temporary”即便用了内部临时表。内部临时表的建立条件:
* group by 和 order by中的列不相同
* order by的列不是引用from 表列表中 的第一表
* group by的列不是引用from 表列表中 的第一表
* 使用了sql_small_result选项
* 含有distinct 的 order by语句
1) 尽可能避免使用游标,
由于游标的效率较差,若是游标操做的数据超过1万行,那么就应该考虑改写。 Mysql的游标不适合处理大一点的数据量,仅适合用于操做几百上千的小数据量。
2) 使用基于游标的方法或临时表方法以前,应先寻找基于集合的解决方案来解决问题,基于集合的方法一般更有效。
对小型数据集使用 FAST_FORWARD 游标一般要优于其余逐行处理方法,尤为是在必须引用几个表才能得到所需的数据时。在结果集中包括“合计”的例程一般要比使用游标执行的速度快。若是开发时间容许,基于游标的方法和基于集的方法均可以尝试一下,看哪种方法的效果更好。
1) 尽可能避免大事务操做,提升系统并发能力。
innodb在事务下能够锁定行也能够锁定表(MyISAM仅支持表锁),固然对于应用而言,锁定行是最佳性能,当锁定了表,其余进程对表进行write操做时只能队列等候事务的完成再继续。
1) 尽可能避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
一般避免向客户端返回大数据量的方法是采用分页系统,在分页系统中使用LIMIT和OFFSET很常见,它们一般也会和ORDER BY一块儿使用。但分页系统中(LIMIT和OFFSET)的一个常见问题是偏移量很大时,能够限制一个分页里访问的页面数目,或者让偏移量很大时查询效率更高。一个提升效率的简单技巧就是在覆盖索引上进行偏移,而不是对全行数据进行偏移。能够将从覆盖索引上提取出来的数据和全行数据进行联接,而后取得须要的列。有时能够把LIMIT转换为位置性查询(bwteen and子句),服务器能够以索引范围扫描的方式来执行。
COUNT有两种不一样的工做方式:统计值的数量和统计行的数量。若是在COUNT()的括号中定义了列名或其它表达式,COUNT就会统计这个表达式有值班的次数。COUNT(*)则统计结果中行的数量。
一般说来,使用了COUNT的查询很难优化,由于它们一般须要统计不少行。在MySQL内部优化它的惟一其余选择就是使用覆盖索引。若是这还不够,那么就须要更改应用程序的架构。能够考虑使用汇总表,还能够利用外部缓存系统,好比数据库缓存服务器(MemCached)。
1) InnoDB引擎在统计方面和MyISAM是不一样的,MyISAM内置了一个计数器,COUNT(*)在没有查询条件的状况下使用 select count(*) from table 的时候,MyISAM直接能够从计数器中取出数据,而InnoDB必须全表扫描一次方能获得总的数量。
2) 可是当有查询条件的时候,二者的查询效率一致。
对主键索引做COUNT的时候之因此慢,是由于:
* InnoDB引擎:
[1] 数据文件和索引文件存储在一个文件中,主键索引默认直接指向数据存储位置。
[2] 二级索引存储指定字段的索引,实际的指向位置是主键索引。当咱们经过二级索引统计数据的时候,无需扫描数据文件;而经过主键索引统计数据时,因为主键索引与数据文件存放在一块儿,因此每次都会扫描数据文件,因此主键索引统计没有二级索引效率高。
[3] 因为主键索引直接指向实际数据,因此当咱们经过主键id查询数据时要比经过二级索引查询数据要快。
* MyISAM引擎
[1] 该引擎把每一个表都分为几部分存储,好比用户表,包含user.frm,user.MYD和user.MYI。
[2] User.frm负责存储表结构
[3] User.MYD负责存储实际的数据记录,全部的用户记录都存储在这个文件中
[4] User.MYI负责存储用户表的全部索引,这里也包括主键索引。
对于JOIN查询,
• 首先确保ON或USING使用的列上有索引。这样,MySQL内部会启动为你优化Join的SQL语句的机制。
• 确保GROUP BY或ORDER BY只引用一个表中的列。这样MySQL能够尝试对这些操做使用索引。
• 确保被用来Join的字段,应该是相同的类型的。例如:若是你要把 DECIMAL 字段和一个 INT 字段Join在一块儿,MySQL就没法使用它们的索引。对于那些STRING类型,还须要有相同的字符集才行。(两个表的字符集有可能不同)
• 要谨慎升级MySQL。
对子查询重要的建议就是尽量使用联接。
基于索引的排序
MySQL的弱点之一是它的排序。虽然MySQL能够在1秒中查询大约15,000条记录,但因为MySQL在查询时最多只能使用一个索引。所以,若是WHERE条件已经占用了索引,那么在排序中就不使用索引了,这将大大下降查询的速度。咱们能够看看以下的SQL语句:
SELECT * FROM SALES WHERE NAME = “name” ORDER BY SALE_DATE DESC;
在以上的SQL的WHERE子句中已经使用了NAME字段上的索引,所以,在对SALE_DATE进行排序时将再也不使用索引。为了解决这个问题,咱们能够对SALES表创建复合索引:
ALTER TABLE SALES DROP INDEX NAME, ADD INDEX (NAME, SALE_DATE)
这样再使用上述的SELECT语句进行查询时速度就会大副提高。但要注意,在使用这个方法时,要确保WHERE子句中没有排序字段,在上例中就是不能用SALE_DATE进行查询,不然虽然排序快了,可是SALE_DATE字段上没有单独的索引,所以查询又会慢下来。
在某些状况中, 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 :
一般说来,索引也是优化它们的最重要手段。
当不能使用索引时,MySQL有两种优化GROUP BY的策略:使用临时表或文件排序进行分组。经过使用SQL_SMALL_RESULT强制MySQL选择临时表,或者使用SQL_BIG_RESULT强制它使用文件排序。
若是要对联接进行分组,经过对表的ID列进行分组会更加高效。经过配置SQL_MODE参数来禁止SELECT中使用未在GROUP BY中出现的列。
分组查询的一个变化就是要求MySQL在结果内部实现超级聚合(Super Aggregation)。能够在GROUP BY后面加上WITH ROLLUP来实现。但也许它没有被很好地优化。可使用解释器检查执行方法,确认分组是否已经经过文件排序或临时表完成,而后试着移除WITH ROLLUP,而且查看分组方法是否没有变化。
默认状况下, MySQL 排序全部 GROUP BY col1 , col2 , .... 。查询的方法如同在查询中指定 ORDER BY col1 , col2 , ... 。若是显式包括一个包含相同的列的 ORDER BY子句, MySQL 能够绝不减速地对它进行优化,尽管仍然进行排序。若是查询包括 GROUP BY 但你想要避免排序结果的消耗,你能够指定 ORDER BY NULL禁止排序。
在分页系统中使用LIMIT和OFFSET很常见,它们一般也会和ORDER BY一块儿使用。
一个常见问题是偏移量很大时,能够限制一个分页里访问的页面数目,或者让偏移量很大时查询效率更高。一个提升效率的简单技巧就是在覆盖索引上进行偏移,而不是对全行数据进行偏移。能够将从覆盖索引上提取出来的数据和全行数据进行联接,而后取得须要的列。有时能够把LIMIT转换为位置性查询(bwteen and子句),服务器能够以索引范围扫描的方式来执行。
若是确实须要优化分页系统,也许应该利用预先计算好的汇总数据。
MySQL老是经过建立并填充临时表方式来执行UNION,它不能对UNION进行太多的优化。重要的是始终要使用UNION ALL,除非须要服务器消除重复的行。若是忽略了ALL关键字,MySQL就会向临时表添加distinct选项,它会利用全部行来决定数据的惟一性,这种操做开销很大。
一般状况下,在OR子句中的字段都具备索引的状况下,用UNION子句来替换WHERE子句中的OR将会起到较好的效果。但若是存在没有索引的列,则不该使用UNION子句。对索引列使用OR将形成全表扫描。
另外,可使用IN子句来替代OR子句。经过给索引添加愈来愈多的列,而且使用IN()列表来覆盖那些不存WHERE子句的列。
对于OR子句,一般MyISAM存储索引可使用索引来扫描,而InnoDB则不能。为了利用索引,OR子句中的全部条件中的字段都具备单独的索引,不然利用不上索引。
若是不满意MySQL优化器选择的优化方案,可使用一些优化提示来控制优化器的行为。
• HIGH_PRIORITY和LOW_PRIORITY:决定访问同一个表的语句相对于其余语句的优先级。HIGH_PRIORITY告诉MySQL将一个SELECT语句放在其余语句的前面,以便它修改数据。LOW_PRIORITY则相反,若是有其余语句须要访问数据,它就将当前语句放到队列的最后。能够将这个选项用于SELECT、INSERT、UPDATE、REPLACE和DELETE。这两个选项只影响服务器对访问表的队列的处理,并非指在查询上分配较多或较少的资源。
• DELAYED:用于INSERT和UPDATE。应用这个提示的语句会当即返回并将待插入的列放入缓冲区中,在表空闲的时候再执行插入。
• STRAIGHT_JOIN:用于SELECT语句中SELECT关键字后,也可用于联接语句。它可强制MySQL按照查询中表出现的顺序来联接表,并当它出如今两个联接表中间时,强制这两个表按照顺序联接。
• SQL_SMALL_RESULT和SQL_BIG_RESULT:用于SELECT语句。告诉MySQL在GROUP BY或DISTINCT查询中如何并什么时候使用临时表。SQL_SMALL_RESULT告诉优化器结果集会比较小,能够放在索引过的临时表中,以免对分组后的数据排序。SQL_BIG_RESULT是结果集很大,最好使用磁盘上的临时表进行排序。
• SQL_BUFFER_RESULT:将结果放在临时表中,而且尽快释放掉表锁。
• SQL_CACHE和SQL_NO_CACHE:SQL_CACHE代表查询已经存在缓存中,而SQL_NO_CACHE正好相反。
• SQL_CALC_FOUND_ROWS:告诉MySQL在有LIMIT子句时计算完整的结果集。(见上述了解为何不要使用这个提示)
• FOR UPDATE和LOCK IN SHARE MODE:SELECT语句使用这两个提示来控制锁定,但只针对有行级锁的存会引擎。它可帮助预先锁定匹配的行。INSERT..SELECT查询不须要这两个提示。
• USE INDEX、IGNORE INDEX和FORCE INDEX:告诉优化器从表中寻找行时使用或忽略索引。
• Optimizer_search_depth:这个变量告诉优化器检查执行计划的深度。若是查询在“统计”的状态停留很长时间,就能够考虑减小这个变量的值。
• Optimizer_prune_level:让优化器根据检查的行的数量跳过某些查询计划
若是能够同时从同一客户插入不少行,使用多个值表的INSERT 语句。多个值表的 INSERT 语句 ,能够大大缩减客户端与数据库之间的链接、语法分析等消耗,使得效率比分开执行的单个 INSERT 语句快不少。
如批量插入:
INSERT INTO tb (fa, fb, fc) VALUES ('1', '12', '13'), ('2', '22', '23'), ('3', '32', '33'),
若是从不一样客户插入不少行,能够经过使用INSERT DELAYED 语句获得更高的速度。Delayed 的含义是让insert 语句立刻执行,其实数据都被放在内存的队列中,并无真正的写入磁盘;这比每条语句都分别插入要快的多;LOW_PRIORITY恰好相反,在全部其余用户对表的读写完成后才进行插入。
将索引文件和数据文件分在不一样的磁盘上存放(利用建表中的选项);
若是进行批量插入,能够增长bulk_insert_buffer_size 变量值的方法来提升速度,可是,这只能对myisam表使用
当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这一般比使用不少INSERT语句快20倍;
根据应用状况使用replace 语句代替insert;
根据应用状况使用ignore 关键字忽略重复记录。
1. 对于Myisam 类型的表,能够经过如下方式快速的导入大量的数据。
ALTER TABLE tblname DISABLE KEYS;
loading the data
ALTER TABLE tblname ENABLE KEYS;
这两个命令用来打开或者关闭MyISAM 表非惟一索引的更新。在导入大量的数据到一个非空的Myisam 表时,经过设置这两个命令,能够提升导入的效率。对于导入大量数据到一个空的Myisam 表,默认就是先导入数据而后才建立索引的,因此不用进行设置。
2. 而对于Innodb 类型的表,这种方式并不能提升导入数据的效率。对于Innodb 类型的表,咱们有如下几种方式能够提升导入的效率:
a. 由于Innodb 类型的表是按照主键的顺序保存的,因此将导入的数据按照主键的顺序排列,能够有效的提升导入数据的效率。若是Innodb 表没有主键,那么系统会默认建立一个内部列做为主键,因此若是能够给表建立一个主键,将能够利用这个优点提升导入数据的效率。
b. 在导入数据前执行SET UNIQUE_CHECKS=0,关闭惟一性校验,在导入结束后执行SETUNIQUE_CHECKS=1,恢复惟一性校验,能够提升导入的效率。
c. 若是应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行SET AUTOCOMMIT=1,打开自动提交,也能够提升导入的效率。
TRUNCATE TABLE `mytable`
注意:删除表中的全部记录,应使用TRUNCATE TABLE语句。
TRUNCATE TABLE 与没有 WHERE 子句的 DELETE 语句相似;可是,TRUNCATE TABLE 速度更快,使用的系统资源和事务日志资源更少。这也意味着TRUNCATE TABLE要比DELETE快得多。
DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一个项。TRUNCATE TABLE 经过释放用于存储表数据的数据页来删除数据,而且在事务日志中只记录页释放。
当使用行锁执行 DELETE 语句时,将锁定表中各行以便删除。TRUNCATE TABLE 始终锁定表和页,而不是锁定各行。
执行 DELETE 语句后,表仍会包含空页。例如,必须至少使用一个排他 (LCK_M_X) 表锁,才能释放堆中的空表。若是执行删除操做时没有使用表锁,表(堆)中将包含许多空页。对于索引,删除操做会留下一些空页,尽管这些页会经过后台清除进程迅速释放。
TRUNCATE TABLE 删除表中的全部行,但表结构及其列、约束、索引等保持不变。若要删除表定义及其数据,就需使用 DROP TABLE 语句。
须要注意的是,在下列状况下,不能对如下表使用 TRUNCATE TABLE:
* 由 FOREIGN KEY 约束引用的表。
* 参与索引视图的表。
* 经过使用事务复制或合并复制发布的表。
* 对于具备以上一个或多个特征的表,请使用 DELETE 语句。
* TRUNCATE TABLE 不能激活触发器,由于该操做不记录各个行删除。
若是你须要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你须要很是当心,要避免你的操做让你的整个网站中止相应。由于这两个操做是会锁表的,表一锁住了,别的操做都进不来了。
Apache 会有不少的子进程或线程。因此,其工做起来至关有效率,而咱们的服务器也不但愿有太多的子进程,线程和数据库连接,这是极大的占服务器资源的事情,尤为是内存。
若是你把你的表锁上一段时间,好比30秒钟,那么对于一个有很高访问量的站点来讲,这30秒所积累的访问进程/线程,数据库连接,打开的文件数,可能不只仅会让你泊WEB服务Crash,还可能会让你的整台服务器立刻掛了。
因此,若是你有一个大的处理,你定你必定把其拆分,使用 LIMIT 条件是一个好的方法。下面是一个示例:
while (1) {
//每次只作1000条
mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
if (mysql_affected_rows() == 0) {
// 没得可删了,退出!
break;
}
// 每次都要休息一下子
usleep(50000);
}
INSERT语句与DELETE语句和UPDATE语句有一点不一样,它一次只操做一个记录。然而,有一个方法可使INSERT 语句一次添加多个记录。要做到这一点,你须要把INSERT语句与SELECT语句结合起来,象这样:
INSERT mytable(first_column,second_column)
SELECT another_first,another_second FROM anothertable WHERE another_first='Copy Me!';
这个语句从anothertable拷贝记录到mytable.只有表anothertable中字段another_first的值为'Copy Me!'的记录才被拷贝。
当为一个表中的记录创建备份时,这种形式的INSERT语句是很是有用的。在删除一个表中的记录以前,你能够先用这种方法把它们拷贝到另外一个表中。
若是你须要拷贝整个表,你可使用SELECT INTO语句。例如,下面的语句建立了一个名为newtable的新表,该表包含表mytable的全部数据:
SELECT * INTO newtable FROM mytable;
你也能够指定只有特定的字段被用来建立这个新表。要作到这一点,只需在字段列表中指定你想要拷贝的字段。另外,你可使用WHERE子句来限制拷贝到新表中的记录。下面的例子只拷贝字段second_columnd的值等于'Copy Me!'的记录的first_column字段。
SELECT first_column INTO newtable FROM mytable
WHERE second_column='Copy Me!';
使用SQL修改已经创建的表是很困难的。例如,若是你向一个表中添加了一个字段,没有容易的办法来去除它。另外,若是你不当心把一个字段的数据类型给错了,你将没有办法改变它。可是,使用本节中讲述的SQL语句,你能够绕过这两个问题。
例如,假设你想从一个表中删除一个字段。使用SELECT INTO语句,你能够建立该表的一个拷贝,但不包含要删除的字段。这使你既删除了该字段,又保留了不想删除的数据。
若是你想改变一个字段的数据类型,你能够建立一个包含正确数据类型字段的新表。建立好该表后,你就能够结合使用UPDATE语句和SELECT语句,把原来表中的全部数据拷贝到新表中。经过这种方法,你既能够修改表的结构,又能保存原有的数据。
一个重要的查询设计问题就是是否能够把一个复杂查询分解成多个简单的查询。注意的是,在应用程序中若是可使用简单查询返回结果时仍然须要避免使用太多的查询。(从网络开销与服务器开销做平衡)。
1) 缩短查询(分治法):让查询每次执行一小部分,以减小受影响的行数。
例:将一次性大量数据的删除细化成屡次中等大小的数据删除,获得移除相同数据的目的。对一个高效的查询来讲,一次删除10000行数据的任务已经足够大了。
2)分解联接:把一个多表联接分解成多个单表查询,而后在应用程序端实现联接操做。
这种重构方式的优点:
* 缓存的效率更高:许多程序都直接缓存了表,这样可直接利用上缓存数据。
* 对MyISAM表来讲,每一个表一个查询能够有效地利用表锁。由于查询会在短期内锁住单个表,而不是把全部表长时间锁住。
* 查询自己会更高效。
* 能够减小多余的行访问。联接会反复访问同一行数据。
* 在某种意义上能够认为这种方式是手工执行了哈希联接,而不是MySQL内部执行联接操做时采用的嵌套循环算法。
注意:在何时应用程序端进行联接效率会更高:
* 能够缓存早期查询的大量数据。
* 使用了多个MyISAM表。
* 数据分布在不一样的服务器上。
* 对于大表使用IN()替换联接。
* 一个联接引用了同一个表不少次。
3)为查询缓存优化你的查询
大多数的MySQL服务器都开启了查询缓存。这是提升性最有效的方法之一,并且这是被MySQL的数据库引擎处理的。当有不少相同的查询被执行了屡次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操做表而直接访问缓存结果了。
这里最主要的问题是,对于程序员来讲,这个事情是很容易被忽略的。由于,咱们某些查询语句会让MySQL不使用缓存。请看下面的示例:
// 查询缓存不开启
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
// 开启查询缓存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
MySQL还容许改变语句调度的优先级,它可使来自多个客户端的查询更好地协做,这样单个客户端就不会因为锁定而等待很长时间。改变优先级还能够确保特定类型的查询被处理得更快。