02 SQL 执行
2014-04-10 16:27
281 查看
sql被保存在sharepool后,开始解析,解析包括语句的语法,检验及对象,以确认该用于是否有该对象的权限,如果这些都通过了,接下来就要看这个语句之前是否被执行过,如果是,oracle将取回之前解析的信息并重用,这就是软解析,如果没有被执行过,那么oracle就将执行所有的工作来为当前语句生成执行计划,并将它存在于缓存中以便将来使用,这就是硬解析.
硬解析需要做很多工作,如果想看硬解析都干了什么,最简单的办法是打开扩展SQL追踪,执行一个语句然后查询追踪数据.
可以参考sharepool工作原理
可以看出最好是每个sql语句都用软解析,查询v$sql区域查看执行过的sql语句,例如
[code]SELECT*FROMEMPLOYEESWHEREDEPARTMENT_ID=60;
[/code]
上述3条语句,执行结果完全相同,但是他们却是不相同的SQL语句,不能实现软解析.因为在执行一条语句时,oracle会首先将字符串转换成散列值,当执行其他语句时,其他语句的散列值与当前的散列值进行比较.而散列值当输入的字母不一样,它的值都会不同,这也是为什么我们推荐使用绑定变量,而不是常量,因为一旦你使用常量,那么常量的值不同时,散列值也就不同,也就不能利用软解析.
查看SQLarea
[code]fromv$sql
[/code]
另外,这里要介绍一下锁存器,锁存器是oracle为了读取存放在dictionarycache或者其他内存结构中信息时获得的一种锁,锁存器可以保护dictionarycache同时被两个会话修改,或者一个会话正要读取的信息被另一个会话修改而导致的损坏,在读取dictionarycache中的任何信息之前,oracle都会获得一个锁存器,其他会话必须等待,直到该锁存器被释放它们才能获得锁存器完成工作.锁存器与典型的锁是不同的,它并不是一个队列,换句话说,如果oracle为了检查你要执行的SQL语句是否已经存在而要在库高速缓存中获取一个锁存器,它将会检查锁存器是否空闲,如果锁存器是空闲的,它将获取该锁存器做它需要的工作,然后释放锁存器,但是,如果锁存器已经被使用了,oracle将会做一件被称为自旋(spinning)的事情,想象一下,这就好像一个小朋友坐在车上一直问"我们到了吗?"如果在一段时间里的自旋之后锁存器仍然不可用(oracle会不断的询问,知道达到指定的次数(_spin_count)默认是2000次),该请求就会被暂时停止,你的会话也不得不排到别的需要使用CPU的会话后面去.它需要再次等待轮到它使用CPU的时间来检查锁存器是否可用.这个迭代过程将会不断重复直到锁存器可用.注意锁存器是串行的,oracle需要获得锁存器的频率越高(硬解析),就越有可能发生争夺,你也就不得不等待更长的时间,这对性能和可扩展性的影响是巨大的,因此,正确的编写你的代码使其较少的使用锁存器(也就是硬解析)是非常关键的.
块是oracle进行操作的最小单位.
如果我们要读取的数据块在内存中,那么叫做逻辑读取,如果我们要读取的块在磁盘中,我们需要先将其移动带内存中,再读取,这叫做物理读取.
我们可以想象,如果我们的操作都是软解析+逻辑读取,那速度肯定是很快,反过来,硬解析+物理读取,就慢很多.
硬解析+物理读取,软解析+物理读取,软解析+逻辑读取
[code]altersystemsetevents'immediatetracenameflush_cache';--清空buffercache
[/code]
在你的查询通过了语法和权限检查之后,查询就进入了转换为一系列查询块的转换阶段.查询块是通过select关键字定义的,如select*fromemployeeswheredepartment_id=60这个查询只有一个查询块,select*fromemployeeswheredepartment_idin(selectdepartment_idfromdepartments)这个就有两个查询块.各个查询块要么嵌套在一起,要么以某种方式相联结.查询书写的方式决定了查询块之间的关系,查询转换的主要目的就是确定如果改变查询的写法会不会提供更好的查询计划.所以,查询转换能够并且可能会重写你的查询.你所写的并不一定就是最终确定执行计划的语句.但是这种转换有可能不是你想要的结果,尤其是你想要的语句的一定部分执行顺序,所以需要了解查询转换的内容,以便写出可以正确得到你想要的行为的SQL语句.
当然查询转换不会改变你的语句的结果集,例如:
[code]select*fromemployeeswheredepartment_idin(selectdepartment_idfromdepartments)
[/code]
通过查看执行计划可以了解是否发生了查询转换,以下几种情况基本会发生查询转换:
视图合并视图合并是一种能将内嵌或存储式视图展开为能够独立分析或者与查询剩余部分合并成总体执行计划的独立查询块的转换.改写后的语句基本上不包含视图
子查询解嵌套
谓语前推
使用物化视图进行查询重写
视图合并(基本上可以认为是oracle自动进行就可以了)
例如:
[code]fromorderso,
[/code]
--进行视图合并
--不进行视图合并
注意,在不进行视图合并中,该计划是通过VIEW关键字来表明视图是保持”原样”的.通过单独处理视图,在于外部的orders表联结之前就要对orders表进行全表扫描,然而使用视图合并的版本中,计划运算合并为一个计划而不是让内嵌视图保持独立.
合并视图是oracle自动进行的,我们之所以得到了没有合并的执行计划,是因为我们在执行sql语句时,增加了NO_MERGE提示符,这样看起来:
[code]fromorderso,
[/code]
还有其他一些情况也会阻止视图合并的发生.如果一个查询块包含解析函数或聚合函数,集合运算(例如union,intersect,minus),orderby子句或者使用了rownum,视图合并将会被禁止或限制.即使出现了上面的默些情形,你也可以通过使用MERGE提示来强制执行视图合并.不过你要确定视图合并不影响查询结果.例如:
[code]fromemployeese1,
[/code]
视图合并行为时通过一个隐藏参数_complex_view_merging来控制,从oracle10g版开始,转换后的查询将会由优化器进行复查,视图合并以及不合并的查询计划所需成本都会被评估,然后优化器就会选择成本最低的执行计划.
子查询解嵌套(基本上可以认为oracle自动进行就可以了)
基本与视图合并相似,不同就是位置不一样,这个是位于where子句.
不相关子查询
上边例子,就会通过转化为表联结来合并到主查询中,转换成类似如下例子
[code]fromemployeese,departmentsd
[/code]
通过NO_UNNEST提示,可以强制该查询按照书写的方式进行优选,也就意味着将会为子查询单独生成一个子执行计划
[code]fromemployees
[/code]
谓语前退
主要目标,将不需要的数据行尽可能早的过滤掉,要一直这样想,早点儿进行筛选.例如:
[code]fromemployeese1,
[/code]
另外,使用rownum条件时一定要小心,它会阻止oracle自动进行合并视图等等.
使用物化视图进行查询重写
查询重写是一种发生在当一个查询或查询的一部分已经被保存为一个物化视图,转换器重写该查询以使用预先计算好的物化视图数据而不需要执行当前查询的转换.
物化视图与普通视图的区别:查询已经被执行并将结果集存入了一张表中.这样做的好处就是预先计算了查询的结果并且在特定查询执行的时候可以直接调去该结果.也就是说,所有的确定执行计划,执行查询以及收集所有数据的工作都已经做完了.
例如:
[code]selectp.prod_id,p.prod_name,t.time_id,t.week_ending_day,
[/code]
[code]select/*+rewrite(sales_time_product_mv)*/
[/code]
可以看到使用了物化视图后,执行计划大幅缩减(这是当然)
可能你所做的事情都是正确的,但是统计信息本身是错误的或不够准确的,所以你只关注你做的事情,可能几天你都不能发现问题,因为它本身是正确的.
另外,要注意,你写的sql语句有可能优化器不能很好的利用统计信息,例如:
我们假设每种车仅由一家公司生产,也就是说只有福特会有一款车叫福克斯
统计信息是:(优化器要利用统计信息)
table的总行数:1,000,000
不同厂商:4
不同车品牌:1000
查询中有两个不同条件(或称为谓语),首先计算出它们的选择比,生产商的选择比是1/4,所谓选择比就是你要查的生产商值,这里是‘Ford’与全部的生产商的值的比,品牌的选择比是1/1000,由于谓语是通过and连接,这个条件组合的两个选择比将会通过各自选择比相乘得出.因此最后的结果是0.00025,这就是说优化器会确定该查询将会返回250行(0.00025*1000000一共有1000000行)问题出在什么地方呢?
事实上,我们知道‘Focus’这款车肯定出子’Ford’,那么谓词(条件)wheremanufacturer=‘Ford’包含在查询中使得总的选择比降低了25%,在这个例子中,真正的选择比仅仅是车型这一列的选择比,如果仅有这一个谓词,那么选择比是1/1000,优化器所计算出的需要返回的数据行数应该是1000行而不是250行.
每次调用时客户端和数据库之间的网络往返回路将会影响语句总的响应时间.除了FETCH以外,其他的数据库调用类型在一次查询过程中都只会发生一次,oracle需要执行足够次数的FETCH调用来获取并返回满足查询所需要的所有结果.一次FETCH调用将会访问buffercache中的一个或多个块.每次访问一个数据块的时候,oracle都会从该块中取出数据行然后在一次回路中返回给客户端.一次回路传输的数据行大小,是在application级别定义的,Arraysize,在SQL*PLUS默认大小是15,你可以通过setARRARSIZEn来改变,jdbc默认值是10,可以用((OracleConnection)conn).setDefaultRowPrefetch(n)来更改.具有较大的数组大小的好处:减少FETCH调用的次数以减少网络往返.例如:
[code]
[/code]
[code]
[/code]
可以看到逻辑读次数从52->22,网络往返次数46–>16
总结:优化器位于所有你所写的SQL语句的核心位置.在写SQL语句时候时刻考虑优化器将会使你获得超出想象的帮助.
这里又再一次推荐了,Cost-BasedOracleFundamentals这本书,lewis写的,就是那个说oracledbaFF那个人.
硬解析需要做很多工作,如果想看硬解析都干了什么,最简单的办法是打开扩展SQL追踪,执行一个语句然后查询追踪数据.
可以参考sharepool工作原理
可以看出最好是每个sql语句都用软解析,查询v$sql区域查看执行过的sql语句,例如
select*fromemployeeswheredepartment_id=60;
[code]SELECT*FROMEMPLOYEESWHEREDEPARTMENT_ID=60;
select/*a_comment*/*fromemployeeswheredepartment_id=60;
[/code]
上述3条语句,执行结果完全相同,但是他们却是不相同的SQL语句,不能实现软解析.因为在执行一条语句时,oracle会首先将字符串转换成散列值,当执行其他语句时,其他语句的散列值与当前的散列值进行比较.而散列值当输入的字母不一样,它的值都会不同,这也是为什么我们推荐使用绑定变量,而不是常量,因为一旦你使用常量,那么常量的值不同时,散列值也就不同,也就不能利用软解析.
select*fromemployeeswheredepartment_id=:v_dept;
查看SQLarea
selectsql_text,sql_id,child_number,hash_value,address,executions
[code]fromv$sql
wheresql_textlike'%v_dept';
[/code]
另外,这里要介绍一下锁存器,锁存器是oracle为了读取存放在dictionarycache或者其他内存结构中信息时获得的一种锁,锁存器可以保护dictionarycache同时被两个会话修改,或者一个会话正要读取的信息被另一个会话修改而导致的损坏,在读取dictionarycache中的任何信息之前,oracle都会获得一个锁存器,其他会话必须等待,直到该锁存器被释放它们才能获得锁存器完成工作.锁存器与典型的锁是不同的,它并不是一个队列,换句话说,如果oracle为了检查你要执行的SQL语句是否已经存在而要在库高速缓存中获取一个锁存器,它将会检查锁存器是否空闲,如果锁存器是空闲的,它将获取该锁存器做它需要的工作,然后释放锁存器,但是,如果锁存器已经被使用了,oracle将会做一件被称为自旋(spinning)的事情,想象一下,这就好像一个小朋友坐在车上一直问"我们到了吗?"如果在一段时间里的自旋之后锁存器仍然不可用(oracle会不断的询问,知道达到指定的次数(_spin_count)默认是2000次),该请求就会被暂时停止,你的会话也不得不排到别的需要使用CPU的会话后面去.它需要再次等待轮到它使用CPU的时间来检查锁存器是否可用.这个迭代过程将会不断重复直到锁存器可用.注意锁存器是串行的,oracle需要获得锁存器的频率越高(硬解析),就越有可能发生争夺,你也就不得不等待更长的时间,这对性能和可扩展性的影响是巨大的,因此,正确的编写你的代码使其较少的使用锁存器(也就是硬解析)是非常关键的.
块是oracle进行操作的最小单位.
如果我们要读取的数据块在内存中,那么叫做逻辑读取,如果我们要读取的块在磁盘中,我们需要先将其移动带内存中,再读取,这叫做物理读取.
我们可以想象,如果我们的操作都是软解析+逻辑读取,那速度肯定是很快,反过来,硬解析+物理读取,就慢很多.
硬解析+物理读取,软解析+物理读取,软解析+逻辑读取
--实验
[code]altersystemsetevents'immediatetracenameflush_cache';--清空buffercache
--altersystemflushbuffer_cache;--10g新特性,清空buffercache
altersystemflushshared_pool;--清空sharepool
setautotracetraceonlystatistics--只看sql语句的统计信息,也可以setautotraceon即看统计信息又看执行计划
setautotraceoff--关闭统计信息
[/code]
查询转换
在生成执行计划之前,会有一步查询转换,该步骤发证在一个查询完成了语法和权限的检查,优化器为了决定最终的执行计划而为不同的计划计算成本预估之前,换句话说,转换和优选是两种不同的任务.在你的查询通过了语法和权限检查之后,查询就进入了转换为一系列查询块的转换阶段.查询块是通过select关键字定义的,如select*fromemployeeswheredepartment_id=60这个查询只有一个查询块,select*fromemployeeswheredepartment_idin(selectdepartment_idfromdepartments)这个就有两个查询块.各个查询块要么嵌套在一起,要么以某种方式相联结.查询书写的方式决定了查询块之间的关系,查询转换的主要目的就是确定如果改变查询的写法会不会提供更好的查询计划.所以,查询转换能够并且可能会重写你的查询.你所写的并不一定就是最终确定执行计划的语句.但是这种转换有可能不是你想要的结果,尤其是你想要的语句的一定部分执行顺序,所以需要了解查询转换的内容,以便写出可以正确得到你想要的行为的SQL语句.
当然查询转换不会改变你的语句的结果集,例如:
--查询转换
[code]select*fromemployeeswheredepartment_idin(selectdepartment_idfromdepartments)
--上面sql,可能转换为
selecte.*fromemployeese,departmentsdwheree.department_id=d.department_id
[/code]
通过查看执行计划可以了解是否发生了查询转换,以下几种情况基本会发生查询转换:
视图合并视图合并是一种能将内嵌或存储式视图展开为能够独立分析或者与查询剩余部分合并成总体执行计划的独立查询块的转换.改写后的语句基本上不包含视图
子查询解嵌套
谓语前推
使用物化视图进行查询重写
视图合并(基本上可以认为是oracle自动进行就可以了)
例如:
select*
[code]fromorderso,
(selectsales_rep_id
fromorders
)o_view
whereo.sales_rep_id=o_view.sales_rep_id(+)
ando.order_total>100000;
[/code]
--进行视图合并
--不进行视图合并
注意,在不进行视图合并中,该计划是通过VIEW关键字来表明视图是保持”原样”的.通过单独处理视图,在于外部的orders表联结之前就要对orders表进行全表扫描,然而使用视图合并的版本中,计划运算合并为一个计划而不是让内嵌视图保持独立.
合并视图是oracle自动进行的,我们之所以得到了没有合并的执行计划,是因为我们在执行sql语句时,增加了NO_MERGE提示符,这样看起来:
select*
[code]fromorderso,
(select/*+NO_MERGE*/sales_rep_id
fromorders
)o_view
whereo.sales_rep_id=o_view.sales_rep_id(+)
ando.order_total>100000;
[/code]
还有其他一些情况也会阻止视图合并的发生.如果一个查询块包含解析函数或聚合函数,集合运算(例如union,intersect,minus),orderby子句或者使用了rownum,视图合并将会被禁止或限制.即使出现了上面的默些情形,你也可以通过使用MERGE提示来强制执行视图合并.不过你要确定视图合并不影响查询结果.例如:
selecte1.last_name,e1.salary,v.avg_salary
[code]fromemployeese1,
(selectdepartment_id,avg(salary)avg_salary
fromemployeese2
groupbydepartment_id)v
wheree1.department_id=v.department_id
ande1.salary>v.avg_salary;
[/code]
视图合并行为时通过一个隐藏参数_complex_view_merging来控制,从oracle10g版开始,转换后的查询将会由优化器进行复查,视图合并以及不合并的查询计划所需成本都会被评估,然后优化器就会选择成本最低的执行计划.
子查询解嵌套(基本上可以认为oracle自动进行就可以了)
基本与视图合并相似,不同就是位置不一样,这个是位于where子句.
不相关子查询
select*fromemployeeswheredepartment_idin(selectdepartment_idfromdepartments);
上边例子,就会通过转化为表联结来合并到主查询中,转换成类似如下例子
selecte.*
[code]fromemployeese,departmentsd
wheree.department_id=d.department_id
[/code]
通过NO_UNNEST提示,可以强制该查询按照书写的方式进行优选,也就意味着将会为子查询单独生成一个子执行计划
selectemployee_id,last_name,salary,department_id
[code]fromemployees
wheredepartment_idin
(select/*+NO_UNNEST*/department_id
fromdepartmentswherelocation_id>1700);
[/code]
谓语前退
主要目标,将不需要的数据行尽可能早的过滤掉,要一直这样想,早点儿进行筛选.例如:
selecte1.last_name,e1.salary,v.avg_salary
[code]fromemployeese1,
(selectdepartment_id,avg(salary)avg_salary
fromemployeese2
groupbydepartment_id)v
wheree1.department_id=v.department_id
ande1.salary>v.avg_salary
ande1.department_id=60;
[/code]
另外,使用rownum条件时一定要小心,它会阻止oracle自动进行合并视图等等.
使用物化视图进行查询重写
查询重写是一种发生在当一个查询或查询的一部分已经被保存为一个物化视图,转换器重写该查询以使用预先计算好的物化视图数据而不需要执行当前查询的转换.
物化视图与普通视图的区别:查询已经被执行并将结果集存入了一张表中.这样做的好处就是预先计算了查询的结果并且在特定查询执行的时候可以直接调去该结果.也就是说,所有的确定执行计划,执行查询以及收集所有数据的工作都已经做完了.
例如:
--没有使用物化视图
[code]selectp.prod_id,p.prod_name,t.time_id,t.week_ending_day,
s.channel_id,s.promo_id,s.cust_id,s.amount_sold
fromsaless,productsp,timest
wheres.time_id=t.time_id
ands.prod_id=p.prod_id;
--创建物化视图
creatematerializedviewsales_time_product_mv
enablequeryrewriteas
selectp.prod_id,p.prod_name,t.time_id,t.week_ending_day,
s.channel_id,s.promo_id,s.cust_id,s.amount_sold
fromsaless,productsp,timest
wheres.time_id=t.time_id
ands.prod_id=p.prod_id;
--还是普通查询,执行计划跟上边的普通查询一样
selectp.prod_id,p.prod_name,t.time_id,t.week_ending_day,
s.channel_id,s.promo_id,s.cust_id,s.amount_sold
fromsaless,productsp,timest
wheres.time_id=t.time_id
ands.prod_id=p.prod_id;
[/code]
--物化视图进行查询
[code]select/*+rewrite(sales_time_product_mv)*/
p.prod_id,p.prod_name,t.time_id,t.week_ending_day,
s.channel_id,s.promo_id,s.cust_id,s.amount_sold
fromsaless,productsp,timest
wheres.time_id=t.time_id
ands.prod_id=p.prod_id;
[/code]
可以看到使用了物化视图后,执行计划大幅缩减(这是当然)
确定执行计划
当发生硬解析的时候,oracle将会确定哪个执行计划对于该查询是最优的.简而言之,一个执行计划就是oracle访问你的查询所使用的对象并返回相应结果数据将会采用的一系列步骤.为了确定执行计划,oracle要收集很多信息,即统计信息.在oracle进行完一个SQL语句的语法和权限检查之后,它会使用从数据字典中收集的统计信息来计算为了得到查询所需要的结果可能会使用到的每一个运算以及运算组合的成本值.可能你所做的事情都是正确的,但是统计信息本身是错误的或不够准确的,所以你只关注你做的事情,可能几天你都不能发现问题,因为它本身是正确的.
另外,要注意,你写的sql语句有可能优化器不能很好的利用统计信息,例如:
我们假设每种车仅由一家公司生产,也就是说只有福特会有一款车叫福克斯
统计信息是:(优化器要利用统计信息)
table的总行数:1,000,000
不同厂商:4
不同车品牌:1000
select*fromcar_purchaseswheremanufacturer='Ford'andmake='Focus'
查询中有两个不同条件(或称为谓语),首先计算出它们的选择比,生产商的选择比是1/4,所谓选择比就是你要查的生产商值,这里是‘Ford’与全部的生产商的值的比,品牌的选择比是1/1000,由于谓语是通过and连接,这个条件组合的两个选择比将会通过各自选择比相乘得出.因此最后的结果是0.00025,这就是说优化器会确定该查询将会返回250行(0.00025*1000000一共有1000000行)问题出在什么地方呢?
事实上,我们知道‘Focus’这款车肯定出子’Ford’,那么谓词(条件)wheremanufacturer=‘Ford’包含在查询中使得总的选择比降低了25%,在这个例子中,真正的选择比仅仅是车型这一列的选择比,如果仅有这一个谓词,那么选择比是1/1000,优化器所计算出的需要返回的数据行数应该是1000行而不是250行.
执行计划并取得数据行
在优化器确定了执行计划并保存到librarycache中以备日后重用之后,实际上,下一个步骤是执行计划并取得满足你查询的数据行.执行计划中的每个步骤产生一个行源,然后与另一个行源相联结,直到所有的对象都被访问和联结.执行的步骤包括,解析,绑定,执行,提取.每次调用时客户端和数据库之间的网络往返回路将会影响语句总的响应时间.除了FETCH以外,其他的数据库调用类型在一次查询过程中都只会发生一次,oracle需要执行足够次数的FETCH调用来获取并返回满足查询所需要的所有结果.一次FETCH调用将会访问buffercache中的一个或多个块.每次访问一个数据块的时候,oracle都会从该块中取出数据行然后在一次回路中返回给客户端.一次回路传输的数据行大小,是在application级别定义的,Arraysize,在SQL*PLUS默认大小是15,你可以通过setARRARSIZEn来改变,jdbc默认值是10,可以用((OracleConnection)conn).setDefaultRowPrefetch(n)来更改.具有较大的数组大小的好处:减少FETCH调用的次数以减少网络往返.例如:
setarraysize15
[code]
setautotracetraceonlystatistics
select*fromorder_items;
[/code]
setarraysize45
[code]
setautotracetraceonlystatistics
select*fromorder_items;
[/code]
可以看到逻辑读次数从52->22,网络往返次数46–>16
SQL执行总览
总结:优化器位于所有你所写的SQL语句的核心位置.在写SQL语句时候时刻考虑优化器将会使你获得超出想象的帮助.
这里又再一次推荐了,Cost-BasedOracleFundamentals这本书,lewis写的,就是那个说oracledbaFF那个人.
相关文章推荐
- 02 SQL 执行
- mysql点滴_02程序中执行sql语句报字符集问题解决
- ADO.NET_第八篇_OracleCommand_执行多个SQL语句02
- Oracle之SQL优化专题02-稳固SQL执行计划的方法
- SQL优化【基础02】 - 执行计划的执行先后顺序
- DotNet下开发MySQL-02 (数据库执行SQL语句)
- 第二讲--SQL语句执行过程
- T-sql语句查询执行顺序
- 使用参数化和块语句来提高批处理SQL语句的执行效率
- mysql,dos下执行SQL语句创建存储过程出错ERROR 1064 (42000):
- 异步执行sql语句
- VS Web项目安装部署(安装数据库[执行sql脚本]并将连接写入web.config)
- LINQ to SQL:如何直接执行SQL语句
- 查看sql语句执行时间/测试sql语句性能
- 在oracle存储过程中执行动态sql
- SQL语句执行顺序
- 使用Hibernate SQLQuery执行原生SQL
- Mybatis程序中执行和单独执行同一条SQL出现结果不一致(部分数据没有查询出来)
- 使用Oracle的DBMS_SQL包执行动态SQL语句
- PostgreSql中如何kill掉正在执行的sql语句