参考:使你的 SQL 语句完全优化--Oracle

mysql 执行顺序 SQL语句执行顺序分析


首先,SELECT语句的基本语法如下: 


 

  SQL Select语句完整的执行顺序【从DBMS使用者角度】: 

SELECT selection_list # What columns toselect 

   FROM table_list # Which tables to select rowsfrom 

   WHERE primary_constraint # What conditions rows mustsatisfy 

   GROUP BY grouping_columns # How to groupresults 

   HAVING secondary_constraint # Secondary conditions rows mustsatisfy 

   ORDER BY sorting_columns # How to sortresults 

   LIMIT from, count; # Limiting row count on results对于MySQL,除了第一行,其他部分都是可选的,有的DBMS要求FROM从句也是必须的。


  1、from子句组装来自不同数据源的数据; 


  2、where子句基于指定的条件对记录行进行筛选; 


  3、group by子句将数据划分为多个分组; 


  4、使用聚集函数进行计算; 


  5、使用having子句筛选分组; 


  6、计算所有的表达式; 


  7、使用order by对结果集进行排序。 


  SQLSelect语句的执行步骤【从DBMS实现者角度,这个对我们用户意义不大】: 


  1)语法分析,分析语句的语法是否符合规范,衡量语句中各表达式的意义。 


  2) 语义分析,检查语句中涉及的所有数据库对象是否存在,且用户有相应的权限。 


  3)视图转换,将涉及视图的查询语句转换为相应的对基表查询语句。 


  4)表达式转换, 将复杂的 SQL 表达式转换为较简单的等效连接表达式。 


  5)选择优化器,不同的优化器一般产生不同的“执行计划” 


  6)选择连接方式, ORACLE 有三种连接方式,对多表连接 ORACLE可选择适当的连接方式。 


  7)选择连接顺序, 对多表连接 ORACLE选择哪一对表先连接,选择这两表中哪个表做为源数据表。 


  8)选择数据的搜索路径,根据以上条件选择合适的数据搜索路径,如是选用全表搜索还是利用索引或是其他的方式。 


  9)运行“执行计划”。



分析问题

查询计划是用户所提交的SQL语句的集合,查询规划是经过优化处理之后所产生的语句集合。

DBMS处理查询计划的过程是这样的:

  1. 在做完查询语句的词法、语法检查之后,
  2. 将语句提交给DBMS的查询优化器,优化器做完代数优化和存取路径的优化之后,
  3. 由预编译模块对语句进行处理并生成查询规划,
  4. 然后在合适的时间提交给系统处理执行,
  5. 最后将执行结果返回给用户。
  6. 在实际的数据库产品(如Oracle、Sybase等)的高版本中都是采用基于代价的优化方法,这种优化能根据从系统字典表所得到的信息来估计不同的查询规划的代价,然后选择一个较优的规划。虽然现在的数据库产品在查询优化方面已经做得越来越好,但由用户提交的SQL语句是系统优化的基础,很难设想一个原本糟糕的查询计划经过系统的优化之后会变得高效,因此用户所写语句的优劣至关重要。系统所做查询优化我们暂不讨论,下面重点说明改善用户查询计划的解决方案。


  1. SELECT * FROM table1 USE INDEX WHERE col1=1 AND col2=2 AND
  2. sql查询一次查询,只能使用一个索引,不是多个【参考:使用use index优化
  3. 一条sql语句里面关联的表每个表只能用一个索引[5.0之前版本],5.0之后如果查询条件里面有多个索引字段,mysql会尝试进行 index merge,同时使用几个索引的合集。
  4. 使用哪个索引是由where语名决定的(条件顺序,索引搜索类型 range或者...),如果where语句写的不好,可能就不会走索引。
  5. 索引适合从大数量里面取少量数据的情况
  6. 如果MYSQL的查询引擎认为查询条件[201102016,201103027]基本等于kithe的所有取值。那么它不会使用index去做一个range(使用B+索引)的查询操作,反而使用table scan去做这件事情,这样可以节省B+索引的检索时间。 如果你的[201102016,201102027]可能kithe的一个部分。那么查询引擎会使用range进行查询操作。 主要是查询引擎在计算那条路可以更快得出结果的时候的取舍操作了。这属于DB查询优化的问题了。 
  7. 使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作:


 

SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008

  虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:


  

SELECT * FROM orders WHERE customer_num=104 AND order_num>1001 
   UNION 
   SELECT * FROM orders WHERE order_num=1008
  1.   这样就能利用索引路径处理查询
  2. mysql limit查询优化(数据量大的时候很优)
  3. select*from yanxue8_visit limit 10000,10
    多次运行,时间保持在0.0187左右
    Select*From yanxue8_visit Where vid>=(

    Select vid From yanxue8_visit Order By vid limit 10000,1

    ) limit 10

    多次运行,时间保持在0.0061左右,只有前者的1/3。可以预计offset越大,后者越优。
    以后要注意改正自己的limit语句,优化一下mysql了


索引误区:

  1. 不使用索引
  2. 考虑在列上进行的比较类型。索引可用于“ <”、“ < = ”、“ = ”、“ > =”、“ > ”和BETWEEN 运算。在模式具有一个直接量前缀时,索引也用于LIKE 运算 like 'dapeng%'。如果只将某个列用于其他类型的运算时(如STRCMP( )),对其进行索引没有价值.不使用索引, date(clo)='2010-11-11'
  3. IN、OR子句常会使用工作表,使索引失效。如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。


关于建立索引的几个准则:



1、合理的建立索引能够加速数据读取效率,不合理的建立索引反而会拖慢数据库的响应速度。


2、索引越多,更新数据的速度越慢。


3、尽量在采用MyIsam作为引擎的时候使用索引(因为MySQL以BTree存储索引),而不是InnoDB。但MyISAM不支持Transcation。【MYSQL配置得当INNODB引擎的综合新能优于MYISAM】


4、当你的程序和数据库结构/SQL语句已经优化到无法优化的程度,而程序瓶颈并不能顺利解决,那就是应该考虑使用诸如memcached这样的分布式缓存系统的时候了。【增加业务逻辑、维护、异常处理的复杂度】


5、习惯和强迫自己用EXPLAIN来分析你SQL语句的性能。


一个很容易犯的错误:


不要在选择的栏位上放置索引,这是无意义的。应该在条件选择的语句上合理的放置索引,比如where,order by。


例子:


SELECT id,title,content,cat_id FROM article WHERE cat_id = 1;


上面这个语句,你在id/title/content上放置索引是毫无意义的,对这个语句没有任何优化作用。但是如果你在外键cat_id上放置一个索引,那作用就相当大了。


几个常用ORDER BY语句的MySQL优化:


1、ORDER BY + LIMIT组合的索引优化。如果一个SQL语句形如:


SELECT [column1],[column2],.... FROM [TABLE] ORDER BY [sort] LIMIT [offset],[LIMIT];



这个SQL语句优化比较简单在[sort]这个栏位上建立索引即可。


2、WHERE + ORDER BY + LIMIT组合的索引优化,形如:


SELECT [column1],[column2],.... FROM [TABLE] WHERE [columnX] = [value] ORDER BY [sort] LIMIT[offset],[LIMIT];



这个语句,如果你仍然采用第一个例子中建立索引的方法,虽然可以用到索引,但是效率不高。更高效的方法是建立一个联合索引(columnX,sort)


3、WHERE + IN + ORDER BY + LIMIT组合的索引优化,形如:


SELECT [column1],[column2],.... FROM [TABLE] WHERE [columnX] IN ([value1],[value2],...) ORDER BY[sort] LIMIT [offset],[LIMIT];



这个语句如果你采用第二个例子中建立索引的方法,会得不到预期的效果(仅在[sort]上是using index,WHERE那里是using where;using filesort),理由是这里对应columnX的值对应多个。


这个语句怎么优化呢?我暂时没有想到什么好的办法,看到网上有便宜提供的办法,那就是将这个语句用UNION分拆,然后建立第二个例子中的索引:


SELECT [column1],[column2],.... FROM [TABLE] WHERE [columnX]=[value1] ORDER BY [sort] LIMIT[offset],[LIMIT]

UNION

SELECT [column1],[column2],.... FROM [TABLE] WHERE [columnX]=[value2] ORDER BY [sort] LIMIT[offset],[LIMIT]

UNION

……



但经验证,这个方法根本行不通,in 该 UNION 效率反而更低,需要去重(union all未验证),测试时对于大部分应用强制指定使用排序索引效果更好点


4、不要再WHERE和ORDER BY的栏位上应用表达式(函数),比如:


SELECT * FROM [table] ORDER BY YEAR(date) LIMIT 0,30;



5、WHERE+ORDER BY多个栏位+LIMIT,比如


SELECT * FROM [table] WHERE uid=1 ORDER x,y LIMIT 0,10;




对于这个语句,大家可能是加一个这样的索引(x,y,uid)。但实际上更好的效果是(uid,x,y)。这是由MySQL处理排序的机制造成的。【数据索引集合】[X,XY,XYUID]

利用最左前缀。在创建一个n 列的索引时,实际是创建了MySQL可利用的n 个索引。多列索引可起几个索引的作用,因为可利用索引中最左边的列集来匹配行。这样的列集称为最左前缀。(这与索引一个列的前缀不同,索引一个列的前缀是 利用该的前n 个字符作为索引值。)



多列索引:


一个索引可以包括15个列。对于某些列类型,可以索引列的前缀。


多列索引可以视为包含通过连接索引列的值而创建的值的排序的数组。


MySQL按这样的方式使用多列索引:当你在WHERE子句中为索引的第1个列指定已知的数量时,查询很快,即使你没有指定其它列的值。


假定表具有下面的结构:


CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name,first_name));



name索引是一个对last_name和first_name的索引。索引可以用于为last_name,或者为last_name和first_name在已知范围内指定值的查询。因此,name索引用于下面的查询:


SELECT * FROM test WHERE last_name='Widenius'; SELECT * FROM test WHERE last_name='Widenius' AND first_name='Michael'; SELECT * FROM test WHERE last_name='Widenius' AND (first_name='Michael' OR first_name='Monty'); SELECT * FROM test WHERE last_name='Widenius' AND first_name >='M' AND first_name < 'N';


然而,name索引 用于下面的查询: 【 以联合索引的第一个列做排列组合得索引数】


SELECT * FROM test WHERE first_name='Michael'; SELECT * FROM test WHERE last_name='Widenius' OR first_name='Michael';





以上例子你在实际项目中应用的时候,不要忘记在添加索引后,用EXPLAIN看看效果。


索引(index)设计

InnoDB表会包含一个聚集索引
一般是按照下面的规则来设定聚集索引的:
1,假如表包含PRIMARY KEY,InnoDB使用它作为聚集索引
2,假如表没有定义PRIMARY KEY,InnoDB将第一个只包含NOT NULL属性列的UNIQUE index作为主键并且将它设置为聚集索引
3,前两者都不满足的时候,mysql就增加一个隐藏的autocreament

HASH (仅仅memory和 ndb引擎支持)
full-text (仅仅myisam支持,并且只支持 char varchar text三种数据类型)
r-tree

2)索引用途:
# 提高数据表检索效率
# 降低数据排序成本(排序主要消耗cpu和内存, 对于分组操作同样是先排序后分组)

2)如何判定 是否建立索引:
# 只有在操作频繁的字段建立索引,绝不建不必要或者想当然的索引,这个在设计表的时候要能大致估计SQL要怎么写。

# 唯一性太差的字段不建立索引,基本上当一条QUERY返回的数据占15%以上就不适合建立索引(通常的像像性别这样的字段绝对不建立索引)

# 更新频繁的字段不建立索引。更新表数据的时候同时要更新索引数据,导致IO访问增大以及影响整个存储系统的消耗。(如果查询更新都较多的情况下,则要比较查询与更新的比例,当比例较大的时候,更新附带的成本是可以接受的)

3) 单键索引和联合索引
# 当where语句的过滤条件比较多的时候,考虑几个字段同时出现的频繁度,对频繁度出现较高的字段集建立联合索引。

# 联合索引的缺点是多个字段同时存在,更新可能性更高,索引存储长度也越大。但就查询角度来讲这个因为它过滤掉更多的数据,所以效率更高。同时比在多个字段都建立单键索引效果好(因为mysql query optimizer需要将多个索引 index_merge 成本更高,有时候还会放弃其他索引)

# 联合索引左前缀原则
使用最左(leftmost)前缀。建立多列复合索引的时候,你实际上建立了MySQL可以使用的多个索引。复合索引可以作为多个索引使用,因为索引中 最左边的列集合都可以用于匹配数据行。这种列集合被称为”最左前缀”(它与索引某个列的前缀不同,那种索引把某个列的前面几个字符作为索引值)。

假设你在表的state、city和zip数据列上建立了复合索引。索引中的数据行按照state/city/zip次序排列,因此它们也会自动地按照 state/city和state次序排列。这意味着,即使你在查询中只指定了state值,或者指定state和city值,MySQL也可以使用这个 索引。因此,这个索引可以被用于搜索如下所示的数据列组合:
state, city, zip
state, city
state

MySQL不能利用这个索引来搜索没有包含在最左前缀的内容。例如,如果你按照city 或zip来搜索,就不会使用到这个索引。如果你搜索给定的 state和具体的ZIP代码(索引的1和3列),该索引也是不能用于这种组合值的,尽管MySQL可以利用索引来查找匹配的state从而缩小搜索的范 围。

# 前缀索引(只使用某个字段前面部分内容作为索引键索引该字段)
4) MYSQL索引限制
# 索引键长度总和不超过1000字节
# BLOB TEXT类型列只创建前缀索引
# 不支持函数索引
# 使用(!= <> )的时候, mysql不支持索引
# 过滤字段使用了函数运算(如abs() ) 等,不支持索引
# join语句中JOIN字段类型不一致,不支持索引
# LIKE语句中以(‘%abc’)开始,不支持索引
# 非等值查询时,不支持HASH索引

5) JOIN语句优化
# 永远用小结果集驱动大结果集(资源消耗存在较大区别外),比如A B联查,
A过滤后 10rows
B过滤后 20rows
A作为驱动表,JOIN过滤则为10次
B作为驱动表,JOIN过滤则为20次
所以选择结果集小的作驱动表

# join字段优化,保证每次查询节省资源

6)ORDER BY、 GROUP BY、 DISTINCT优化
原理都需要进行排序,除对字段索引外,需要去掉不必要的返回字段,节省内存(排序的原则)

5. QUERY语句优化
优化10原则:
1)优化更需要的优化
说明:执行对系统影响更大的QUERY,一般指的是高并发,执行更加频繁的SQL)

2)定位优化对象性能瓶颈
3)明确优化目标
4)explain
5)profule
6)小结果集驱动大结果集
7)尽可能在索引中完成排序
8)只取自己需要的字段
9)仅仅使用最有效的过滤条件
10)尽可能避免复杂的JOIN和子查询

6. 实例分析

Demo1:过度弱化query造成性能消耗
 Table1: users表(user_id, user_name, last_feed_time)
 Table2: feeds表(feed_id, user_id, feed_data, dateline)
 显然users表与feeds表是一对多的关系,现在要查询最近有动作的10个用户,同时在列表页要显示该用户最近24小时的动作。

 解决方法1:
 // 得到10个最近有动作的用户
 $sql = ‘SELECT user_id, user_name
 FROM users ORDER BY last_feed_time DESC limit 10’;

 // PHP获得10条数据 $rs

 // 循环query查询对应ID的最近三个动作
 foreach($rs as $k=>$v) {
 $sql = “SELECT feed_data
 FROM feeds WHERE user_id = ‘{$v[‘user_id’]}’ AND dateline <’’ ORDER BY dateline DESC “;
 }

 解决方法2:
 // 同第一步

 // 查询10个用户最近24小时动作
 $sql = “SELECT user_id, feed_data
 FROM feeds WHERE user_id IN () AND dateline < ‘’ ORDER BY dateline DESC ”:

 // 数组组装



总结:我们在开发中经常碰到一些二级栏目的列表页,但数据来自不同的数据表,通常的做法是循环里面执行query,殊不知这样增加了QUERY的次数,而每次QUERY都需要MYSQL进行解析,在这种情况下,宁愿QUERY复杂点或者在程序端复杂点,保证性能。而从另一个理论来讲,这种情况属于弱化了QUERY造成性能问题。

Demo2:过度依赖query造成性能消耗
 Table1: users表(user_id, user_name)
 Table2: user_profile表(user_id, profile_data)
 Table3: users_group表(id, group_id, user_id, level)
 现在要查询群组ID为1的群组成员信息以及群主的详细信息。
 解决方法1:
 // 一次查询所有信息
 SELECT u.user_name, up.profile_data
 FROM users_group ug LEFT JOIN users u ON ug.user_id = u.user_id LEFT JOIN users_profile up ON u.user_id = up.user_id
 WHERE ug.group_id = ‘1’

 解决方法2:
 // 先查询群组下用户的基本信息
 SELECT u.user_id, u.user_name,
 FROM users_group ug LEFT JOIN users u ON ug.user_id = u.user_id
 WHERE ug.group_id = ‘1’
 // 查群主信息
 SELECT *
 FROM users_profile WHERE user_id = ‘

’;

总结:与上例相反,这个DEMO操作是增加一个query减少不必要的访问(只需要群主的详细信息,而profile存储的是数据类型比较大的数据,这样操作减少的是IO的访问)

Demo3:小结果集驱动大结果集

Table1: users表(user_id, user_name, sex)
 Table3: users_group表(id, group_id, user_id, level, join_time)
 现在要查询某个群组下面(id = 1)用户的名称和性别,按加入时间倒序取100-120条的记录

 解决方法1:
 SELECT u.user_id, u.username, u.sex
 FROM users_group ug LEFT JOIN users u ON ug.user_id = u.user_id
 WHERE ug.group_id = ‘1’ ORDER BY ug.join_time DESC LIMIT 100,120;

 解决方法2:
 SELECT u.user_id, u.username, u.sex
 FROM (
 SELECT user_id
 FROM user_group
 WHERE user_group.group_id = 1
 ORDER BY join_time DESC
 LIMIT 100,20) ug, user
 WHERE ug.user_id = user.user_id;



总结:方法1参与join操作的是全部user_group中group_id = 1的数据、而方法2 参与join操作的数据仅仅是过滤过的20条数据。所以SQL优化永远记住小结果集驱动大结果集,节省的是CPU和IO的消耗。