天天看點

SQL優化經典案例----RLS(ROW LEVEL SECURITY)

SQL優化經典案例----RLS(ROW LEVEL SECURITY)

  ---轉載http://blog.sina.com.cn/s/blog_61cd89f60102efd2.html

    最近看到一個SQL優化牛人部落格,上面例子堪稱經典,唯一不足的時候,好多分析都是點到為止,并沒有說其中的原理,有點像EYLE網站,不過這樣也好,畢竟網上得來終覺淺,看過後自己進行分析,掌握的更加牢固,看一個經典例子。

    具體前後說明詳見:http://blog.csdn.net/robinson1988/article/details/8644565 從落落的這個部落格上隻能看到這個查詢變快了,但是他并沒有說這個做的原因,原理就在hint上,明白該hint的原理也就了解為什麼要這樣做了,說一下hint no_unnest原理:

    hint no_unnest和unnest是一對,unnest是強制子查詢進行展開,就是讓子查詢不在孤單的嵌套在nest loop裡面,那麼no_unnest剛好和它相反,強制子查詢嵌套在nest loop裡面,原理就這麼簡單,關鍵在于在什麼場合适用,為了搞清楚,我做了如下實驗

create table wxw1 as select * from dba_objects;

create table wxw2 as select * from dba_objects;

SQL> exec dbms_stats.gather_table_stats(user,'wxw1');

     PL/SQL procedure successfully completed.

SQL> exec dbms_stats.gather_table_stats(user,'wxw2');

     PL/SQL procedure successfully completed.

select wxw1.object_id

  from wxw1

 where exists

 (select 1 from wxw2 where wxw1.object_id = wxw2.object_id * 10);

7009 rows selected.

Execution Plan

----------------------------------------------------------

Plan hash value: 120165691

-----------------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |

-----------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |     1 |    10 |       |   692   (1)| 00:00:09 |

|*  1 |  HASH JOIN SEMI    |      |     1 |    10 |  1200K|   692   (1)| 00:00:09 |

|   2 |   TABLE ACCESS FULL| WXW1 | 71998 |   351K|       |   287   (1)| 00:00:04 |

|   3 |   TABLE ACCESS FULL| WXW2 | 71999 |   351K|       |   287   (1)| 00:00:04 |

-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - access("WXW1"."OBJECT_ID"="WXW2"."OBJECT_ID"*10)

Statistics

----------------------------------------------------------

          0  recursive calls

          0  db block gets

       2523  consistent gets    --注意此處的邏輯讀

          0  physical reads

          0  redo size

     127923  bytes sent via SQL*Net to client

       5656  bytes received via SQL*Net from client

        469  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

       7009  rows processed 

    這裡子查詢自動展開(unnest),即wxw2和wxw1 hash join在一起,接下來如果我們不希望wxw2展開,想先讓它單獨的執行完,然後再來和外部查詢進行一種叫做FILTER的操作,那麼我們加入hint no_unnest:

select wxw1.object_id

  from wxw1

 where exists (select 1

          from wxw2

         where wxw1.object_id = wxw2.object_id * 10);

 ---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |     1 |     5 |   122K  (1)| 00:24:31 |

|*  1 |  FILTER            |      |       |       |            |          |

|   2 |   TABLE ACCESS FULL| WXW1 | 71998 |   351K|   287   (1)| 00:00:04 |

|*  3 |   TABLE ACCESS FULL| WXW2 |     1 |     5 |     2   (0)| 00:00:01 |

---------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter( EXISTS (SELECT 0 FROM "WXW2" "WXW2"

              WHERE "WXW2"."OBJECT_ID"*10=:B1))

   3 - filter("WXW2"."OBJECT_ID"*10=:B1)

Statistics

----------------------------------------------------------

          0  recursive calls

          0  db block gets

 67282878  consistent gets   --邏輯讀大了N倍

          0  physical reads

          0  redo size

     127923  bytes sent via SQL*Net to client

       5656  bytes received via SQL*Net from client

        469  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

       7009  rows processed

    對比兩個執行結果中的邏輯讀,由添加前的2523--> 67282878,看來該hint并不是所有場合都适用,為什麼邏輯讀變得如此大?因為這裡wxw1和wxw2進行了一種FILTER操作,他其實很像我們熟悉的neested loop,但它的獨特之處在于會維護一個hash table,舉例,如果wxw1裡取出object_id=1,那麼對于wxw2來說即select 1 from wxw2 where wxw2.object_id*10=1,如果條件滿足,那麼對于子查詢,輸入輸出對,即為((wxw1.object_id),1(常量)),他存儲在hash table裡,并且由于條件滿足,wxw1.object_id=1被放入結果集,然後接着從wxw1取出object_id=2,如果子查詢依舊條件滿足,那麼子查詢産生另一個輸入和輸出,即(2,1),被放入hash table裡;并且wxw1.object_id=2被放入結果集。接着假設wxw1裡有重複的object_id,例如我們第三次從wxw1取出的object_id=2,那麼由于我們對于子查詢來說,已經有輸入輸出對(2,1)在hash table裡了,是以就不用去再次全表掃描wxw2了,那麼在這種情況下,filter和neested loop相比,省去了一次全表掃描wxw2。這個hash table是有大小限制的,當被占滿的時候,後續新的wxw.object_id的FILTER就類似neested loop了。

    經過上面的分析我們得出一個這樣的結論:從buffer gets層面上來看,FILTER是應該優于neested loop的,尤其當外部查詢需要傳遞給子查詢的輸入(此例中為wxw1.object_id)的distinct value非常小時,FILTER就會顯得更優,即使在我這個例子中wxw1.object_id的distinct value上萬,其結果應該是neested loop和FILTER,FILTER仍然略優,接下來我們進行實驗驗證。

SQL> select wxw1.object_id from wxw1,wxw2 where wxw1.object_id=wxw2.object_id*10;

7009 rows selected.

Elapsed: 00:16:58.99

Execution Plan

----------------------------------------------------------

Plan hash value: 3886856525

---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      | 71999 |   703K|    20M  (1)| 68:29:04 |

|   1 |  NESTED LOOPS      |      | 71999 |   703K|    20M  (1)| 68:29:04 |

|   2 |   TABLE ACCESS FULL| WXW1 | 71998 |   351K|   287   (1)| 00:00:04 |

|*  3 |   TABLE ACCESS FULL| WXW2 |     1 |     5 |   285   (0)| 00:00:04 |

---------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   3 - filter("WXW1"."OBJECT_ID"="WXW2"."OBJECT_ID"*10)

Statistics

----------------------------------------------------------

          1  recursive calls

          0  db block gets

   74159892  consistent gets

          0  physical reads

          0  redo size

     127923  bytes sent via SQL*Net to client

       5656  bytes received via SQL*Net from client

        469  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

       7009  rows processed

我們對比一下實驗二和實驗三的邏輯讀,發現

實驗二邏輯讀為:67282878 

實驗三邏輯讀為:74159892

對比發現,實驗二添加hint no_unnest邏輯讀略優于nested loop,驗證了我們前面的猜測

     是以我們再返過來看看落落的案例,子查詢是查詢使用者權限,權限個數必定很少,因為FILTER的效率要好于nested loop,這也就為什麼落落如選擇此hint的原因。連接配接中優化後的SQL如下:

SELECT *

  FROM (SELECT A.*, ROWNUM RN

          FROM (select b.*

                  from os_form_base b, ttwo_wo_info t

                 where t.form_id = b.form_id

                   and site_code in

                       (select 

SQL優化經典案例----RLS(ROW LEVEL SECURITY)

                         resCode

                          from (select *

                                  from (select dept_code as resCode,

                                              dept_name as resName

                                          from sec_dept)

                                 WHERE 1 != 1

                                    OR ((1 = 1 and

                                       resCode in

                                       ('0001030102',

                                          '0001030105',

                                          --此處省略N行..........

                                          '000103010501',

                                         '00010301190701')) or (1 != 1))