zoukankan      html  css  js  c++  java
  • 02 SQL 执行

    sql 被保存在 share pool 后, 开始解析, 解析包括语句的语法, 检验及对象, 以确认该用于是否有该对象的权限, 如果这些都通过了, 接下来就要看这个语句之前是否被执行过, 如果是, oracle 将取回之前解析的信息并重用, 这就是软解析, 如果没有被执行过, 那么oracle就将执行所有的工作来为当前语句生成执行计划, 并将它存在于缓存中以便将来使用, 这就是硬解析.

    硬解析需要做很多工作, 如果想看硬解析都干了什么, 最简单的办法是打开扩展SQL追踪, 执行一个语句然后查询追踪数据.

    可以参考 share pool 工作原理

    可以看出最好是每个 sql 语句都用软解析, 查询 v$sql 区域查看执行过的 sql 语句, 例如

       1:  select * from employees where department_id = 60;
       2:  SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID = 60;
       3:  select /* a_comment */ * from employees where department_id = 60;

    上述3条语句, 执行结果完全相同, 但是他们却是不相同的SQL语句, 不能实现软解析. 因为 在执行一条语句时, oracle会首先将字符串转换成散列值, 当执行其他语句时, 其他语句的散列值与当前的散列值进行比较. 而散列值当输入的字母不一样, 它的值都会不同, 这也是为什么我们推荐使用绑定变量, 而不是常量, 因为一旦你使用常量, 那么常量的值不同时, 散列值也就不同, 也就不能利用软解析.

       1:  select * from employees where department_id = :v_dept;

    查看SQL area

       1:  select sql_text, sql_id, child_number, hash_value, address, executions
       2:    from v$sql
       3:   where sql_text like '%v_dept';

    另外, 这里要介绍一下锁存器, 锁存器是 oracle 为了读取存放在 dictionary cache或者其他内存结构 中信息时获得的一种锁, 锁存器可以保护 dictionary cache 同时被两个会话修改, 或者一个会话正要读取的信息被另一个会话修改而导致的损坏, 在读取 dictionary cache 中的任何信息之前, oracle 都会获得一个锁存器, 其他会话必须等待, 直到该锁存器被释放它们才能获得锁存器完成工作. 锁存器与典型的锁是不同的, 它并不是一个队列, 换句话说, 如果 oracle 为了检查你要执行的SQL语句是否已经存在而要在库高速缓存中获取一个锁存器, 它将会检查锁存器是否空闲, 如果锁存器是空闲的, 它将获取该锁存器做它需要的工作, 然后释放锁存器, 但是, 如果锁存器已经被使用了, oracle 将会做一件被称为 自旋 (spinning)的事情, 想象一下, 这就好像一个小朋友坐在车上一直问"我们到了吗?" 如果在一段时间里的自旋之后锁存器仍然不可用(oracle会不断的询问, 知道达到指定的次数(_spin_count) 默认是2000次), 该请求就会被暂时停止, 你的会话也不得不排到别的需要使用CPU的会话后面去. 它需要再次等待轮到它使用CPU的时间来检查锁存器是否可用. 这个迭代过程将会不断重复直到锁存器可用. 注意锁存器是串行的, oracle需要获得锁存器的频率越高(硬解析), 就越有可能发生争夺, 你也就不得不等待更长的时间, 这对性能和可扩展性的影响是巨大的, 因此, 正确的编写你的代码使其较少的使用锁存器(也就是硬解析)是非常关键的.

    块是 oracle 进行操作的最小单位.

    如果我们要读取的数据块在内存中, 那么叫做逻辑读取, 如果我们要读取的块在磁盘中, 我们需要先将其移动带内存中, 再读取, 这叫做物理读取.

    我们可以想象, 如果我们的操作都是 软解析+逻辑读取, 那速度肯定是很快, 反过来, 硬解析+物理读取, 就慢很多.

    硬解析+物理读取, 软解析+物理读取, 软解析+逻辑读取

    imageimageimage

       1:   -- 实验
       2:   alter system set events 'immediate trace name flush_cache';    -- 清空 buffer cache
       3:   -- alter system flush buffer_cache;    -- 10g 新特性, 清空 buffer cache
       4:   alter system flush shared_pool;    -- 清空 share pool
       5:   set autotrace traceonly statistics    -- 只看 sql 语句的统计信息, 也可以 set autotrace on 即看统计信息又看执行计划
       6:   set autotrace off    -- 关闭统计信息

    查询转换

    在生成执行计划之前, 会有一步查询转换, 该步骤发证在一个查询完成了语法和权限的检查, 优化器为了决定最终的执行计划而为不同的计划计算成本预估之前, 换句话说, 转换和优选是两种不同的任务.

    在你的查询通过了语法和权限检查之后, 查询就进入了转换为一系列查询块的转换阶段. 查询块是通过 select 关键字定义的, 如 select * from employees where department_id = 60 这个查询只有一个查询块, select * from employees where department_id in (select department_id from departments) 这个就有两个查询块. 各个查询块要么嵌套在一起, 要么以某种方式相联结. 查询书写的方式决定了查询块之间的关系, 查询转换的主要目的就是确定如果改变查询的写法会不会提供更好的查询计划. 所以, 查询转换能够并且可能会重写你的查询. 你所写的并不一定就是最终确定执行计划的语句.  但是这种转换有可能不是你想要的结果, 尤其是你想要的语句的一定部分执行顺序, 所以需要了解查询转换的内容, 以便写出可以正确得到你想要的行为的SQL语句.

    当然查询转换不会改变你的语句的结果集, 例如:

       1:   -- 查询转换
       2:   select * from employees where department_id in (select department_id from departments)
       3:   -- 上面sql, 可能转换为
       4:   select e.* from employees e, departments d where e.department_id = d.department_id

    通过查看执行计划可以了解是否发生了查询转换, 以下几种情况基本会发生查询转换:

    • 视图合并 视图合并是一种能将内嵌或存储式视图展开为能够独立分析或者与查询剩余部分合并成总体执行计划的独立查询块的转换. 改写后的语句基本上不包含视图
    • 子查询解嵌套
    • 谓语前推
    • 使用物化视图进行查询重写

    视图合并 (基本上可以认为是 oracle 自动进行就可以了)

    例如:

       1:   select *
       2:     from orders o,
       3:          ( select sales_rep_id
       4:              from orders
       5:          ) o_view
       6:    where o.sales_rep_id = o_view.sales_rep_id(+)
       7:      and o.order_total > 100000;

    -- 进行视图合并

    image

    -- 不进行视图合并

    image

    注意, 在不进行视图合并中, 该计划是通过 VIEW 关键字来表明视图是保持”原样”的. 通过单独处理视图, 在于外部的 orders 表联结之前就要对 orders 表进行全表扫描, 然而使用视图合并的版本中, 计划运算合并为一个计划而不是让内嵌视图保持独立.

    合并视图是 oracle 自动进行的, 我们之所以得到了没有合并的执行计划, 是因为我们在执行 sql 语句时, 增加了 NO_MERGE 提示符, 这样看起来:

       1:  select *
       2:     from orders o,
       3:          ( select /*+ NO_MERGE*/ sales_rep_id
       4:              from orders
       5:          ) o_view
       6:    where o.sales_rep_id = o_view.sales_rep_id(+)
       7:      and o.order_total > 100000;

    还有其他一些情况也会阻止视图合并的发生. 如果一个查询块包含解析函数或聚合函数, 集合运算(例如 union, intersect, minus), order by 子句或者使用了 rownum, 视图合并将会被禁止或限制. 即使出现了上面的默些情形, 你也可以通过使用 MERGE 提示来强制执行视图合并. 不过你要确定视图合并不影响查询结果. 例如:

       1:  select e1.last_name, e1.salary, v.avg_salary
       2:    from employees e1,
       3:          ( select department_id, avg(salary) avg_salary
       4:              from employees e2
       5:             group by department_id) v
       6:   where e1.department_id = v.department_id
       7:     and e1.salary > v.avg_salary;

    imageimage

    视图合并行为时通过一个隐藏参数 _complex_view_merging来控制, 从 oracle10g版开始, 转换后的查询将会由优化器进行复查, 视图合并以及不合并的查询计划所需成本都会被评估, 然后优化器就会选择成本最低的执行计划.

    子查询解嵌套 (基本上可以认为 oracle 自动进行就可以了)

    基本与视图合并相似, 不同就是位置不一样, 这个是位于 where 子句.

    不相关子查询

       1:  select * from employees where department_id in (select department_id from departments);

    上边例子, 就会通过转化为表联结来合并到主查询中, 转换成类似如下例子

       1:  select e.*
       2:    from employees e, departments d
       3:   where e.department_id = d.department_id

    通过 NO_UNNEST 提示, 可以强制该查询按照书写的方式进行优选, 也就意味着将会为子查询单独生成一个子执行计划

       1:   select employee_id, last_name, salary, department_id
       2:     from employees
       3:    where department_id in
       4:              (select /*+ NO_UNNEST */ department_id
       5:                  from departments where location_id > 1700);

    谓语前退

    主要目标, 将不需要的数据行尽可能早的过滤掉, 要一直这样想, 早点儿进行筛选. 例如:

       1:  select e1.last_name, e1.salary, v.avg_salary
       2:    from employees e1,
       3:          (select department_id, avg(salary) avg_salary
       4:             from employees e2
       5:            group by department_id) v
       6:   where e1.department_id = v.department_id
       7:     and e1.salary > v.avg_salary
       8:     and e1.department_id = 60;

    另外, 使用 rownum 条件时一定要小心, 它会阻止 oracle 自动进行 合并视图等等.

    使用物化视图进行查询重写

    查询重写是一种发生在当一个查询或查询的一部分已经被保存为一个物化视图, 转换器重写该查询以使用预先计算好的物化视图数据而不需要执行当前查询的转换.

    物化视图与普通视图的区别: 查询已经被执行并将结果集存入了一张表中. 这样做的好处就是预先计算了查询的结果并且在特定查询执行的时候可以直接调去该结果. 也就是说, 所有的确定执行计划, 执行查询以及收集所有数据的工作都已经做完了.

    例如:

       1:  -- 没有使用物化视图 
       2:   select p.prod_id, p.prod_name, t.time_id, t.week_ending_day,
       3:          s.channel_id, s.promo_id, s.cust_id, s.amount_sold
       4:     from sales s, products p, times t
       5:    where s.time_id = t.time_id
       6:      and s.prod_id = p.prod_id;
       7:   
       8:  -- 创建物化视图
       9:  create materialized view sales_time_product_mv
      10:  enable query rewrite as
      11:   select p.prod_id, p.prod_name, t.time_id, t.week_ending_day,
      12:          s.channel_id, s.promo_id, s.cust_id, s.amount_sold
      13:     from sales s, products p, times t
      14:    where s.time_id = t.time_id
      15:      and s.prod_id = p.prod_id;
      16:      
      17:  -- 还是普通查询, 执行计划跟上边的普通查询一样
      18:  select p.prod_id, p.prod_name, t.time_id, t.week_ending_day,
      19:          s.channel_id, s.promo_id, s.cust_id, s.amount_sold
      20:     from sales s, products p, times t
      21:    where s.time_id = t.time_id
      22:      and s.prod_id = p.prod_id;
       1:  -- 物化视图进行查询
       2:  select /*+ rewrite(sales_time_product_mv) */
       3:          p.prod_id, p.prod_name, t.time_id, t.week_ending_day,
       4:          s.channel_id, s.promo_id, s.cust_id, s.amount_sold
       5:    from sales s, products p, times t
       6:   where s.time_id = t.time_id
       7:     and s.prod_id = p.prod_id;

    可以看到使用了物化视图后, 执行计划大幅缩减 (这是当然)

    确定执行计划

    当发生硬解析的时候, oracle 将会确定哪个执行计划对于该查询是最优的. 简而言之, 一个执行计划就是 oracle 访问你的查询所使用的对象并返回相应结果数据将会采用的一系列步骤. 为了确定执行计划, oracle 要收集很多信息, 即统计信息. 在 oracle 进行完一个 SQL 语句的语法和权限检查之后, 它会使用从数据字典中收集的统计信息来计算为了得到查询所需要的结果可能会使用到的每一个运算以及运算组合的成本值.

    可能你所做的事情都是正确的, 但是统计信息本身是错误的或不够准确的, 所以你只关注你做的事情, 可能几天你都不能发现问题, 因为它本身是正确的.

    另外, 要注意, 你写的 sql 语句有可能优化器不能很好的利用统计信息, 例如:

    我们假设每种车仅由一家公司生产, 也就是说只有福特会有一款车叫福克斯

    统计信息是: (优化器要利用统计信息)

    table 的总行数: 1,000,000

    不同厂商: 4

    不同车品牌: 1000

       1:  select * from car_purchases where manufacturer = 'Ford' and make = 'Focus'

    查询中有两个不同条件(或称为谓语), 首先计算出它们的选择比, 生产商的选择比是 1/4, 所谓选择比就是你要查的生产商值, 这里是 ‘Ford’与全部的生产商的值的比, 品牌的选择比是 1/1000, 由于谓语是通过 and 连接, 这个条件组合的两个选择比将会通过各自选择比相乘得出. 因此最后的结果是 0.00025, 这就是说优化器会确定该查询将会返回250行(0.00025 * 1000 000 一共有 1000 000 行) 问题出在什么地方呢?

    事实上, 我们知道 ‘Focus’这款车肯定出子’Ford’, 那么谓词(条件) where manufacturer = ‘Ford’包含在查询中使得总的选择比降低了25%, 在这个例子中, 真正的选择比仅仅是车型这一列的选择比, 如果仅有这一个谓词, 那么选择比是 1/1000, 优化器所计算出的需要返回的数据行数应该是1000行而不是250行.

    执行计划 并取得数据行

    在优化器确定了执行计划并保存到 library cache 中以备日后重用之后, 实际上, 下一个步骤是执行计划并取得满足你查询的数据行. 执行计划中的每个步骤产生一个行源, 然后与另一个行源相联结, 直到所有的对象都被访问和联结.  执行的步骤包括, 解析, 绑定, 执行, 提取.

    image

    每次调用时客户端和数据库之间的网络往返回路将会影响语句总的响应时间. 除了 FETCH 以外, 其他的数据库调用类型在一次查询过程中都只会发生一次, oracle需要执行足够次数的FETCH调用来获取并返回满足查询所需要的所有结果. 一次 FETCH 调用将会访问 buffer cache 中的一个或多个块. 每次访问一个数据块的时候, oracle 都会从该块中取出数据行然后在一次回路中返回给客户端. 一次回路传输的数据行大小, 是在 application 级别定义的, Arraysize, 在 SQL*PLUS 默认大小是 15, 你可以通过 set ARRARSIZE n 来改变, jdbc 默认值是 10, 可以用 ((OracleConnection)conn).setDefaultRowPrefetch(n) 来更改. 具有较大的数组大小的好处: 减少 FETCH 调用的次数以减少网络往返.  例如:

       1:  set arraysize 15
       2:   
       3:  set autotrace traceonly statistics
       4:   
       5:  select * from order_items;

    image

       1:  set arraysize 45
       2:   
       3:  set autotrace traceonly statistics
       4:   
       5:  select * from order_items;

    image

    可以看到 逻辑读次数从 52 -> 22, 网络往返次数 46 –> 16

    SQL 执行总览

    image

    总结: 优化器位于所有你所写的 SQL 语句的核心位置. 在写 SQL 语句时候时刻考虑优化器将会使你获得超出想象的帮助.

    这里又再一次推荐了, Cost-Based Oracle Fundamentals 这本书, lewis 写的, 就是那个说 oracle dba FF 那个人.

  • 相关阅读:
    iOS- 优化与封装 APP音效的播放
    iOS- iPhone App 如何运营?
    iOS- 封装单例宏
    iOS- 详解文本属性Attributes
    iOS- 如何将应用集成发短信、发邮件、打电话
    iOS- <项目笔记> UIApplication常见属性与方法总结
    iOS- <项目笔记>iOS6 & iOS7屏幕图片适配
    iOS- <项目笔记>项目配置常见文件
    iOS- <项目笔记>UI控件常见属性总结
    iOS- UIPickerView餐厅点餐系统
  • 原文地址:https://www.cnblogs.com/moveofgod/p/3656591.html
Copyright © 2011-2022 走看看