Showing posts with label performance. Show all posts
Showing posts with label performance. Show all posts

Sunday, May 4, 2014

Light Weight Monitoring System (LWM) ตอนที่ 3

โพสต์ครั้งแรก: 8 เมษายน 2011

ขั้นตอนการทำ Step-by-Step
ขั้นตอนการทำต่อไปนี้เป็นการสาธิตให้เห็นถึงวิธีการสร้างระบบ LWM อย่างง่าย ๆ โดยเริ่มจากสร้าง Tablespace ที่จะใช้เก็บข้อมูล และสร้าง User/Schema ที่จะใช้เก็บข้อมูลของระบบ โดยเราตั้งชื่อว่า LWM, เราจะใช้ SYS เป็นผู้สร้างระบบนี้ เริ่มจากการ Connect เข้าระบบด้วย SYSDBA
วิธีการและสคริปต์ต่าง ๆ ที่ระบุไว้ ณ ที่นี้ได้รับการทดสอบแล้วเป็นการภายใน ซึ่งสามารถใช้งานได้ตามที่ระบุไว้ อย่างไรก็ตามเราไม่สามารถรับรองได้ว่าจะได้ผลสำเร็จทุกประการสำหรับผู้อ่าน กรุณาทดสอบในสิ่งแวดล้อมของคุณก่อนที่จะใช้งานจริง
$ sqlplus "/ as sysdba"

SQL*Plus: Release 10.2.0.2.0 - Production on Fri Apr 8 10:42:33 2011

Copyright (c) 1982, 2005, Oracle.  All Rights Reserved.


Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.2.0 - Production
With the Partitioning, OLAP and Data Mining options

SQL> create tablespace lwm datafile '/opt/oracle/oradata/PAO1/lwm01.dbf' size 5m autoextend on maxsize 10m;

Tablespace created.

SQL> create user lwm identified by lwm
2 default tablespace lwm;

User created.

SQL> grant connect to lwm;

Grant succeeded.

SQL> alter user lwm quota unlimited on lwm;

User altered.

หลังจากนั้นเราจะสร้างตารางที่เอาไว้สำหรับเก็บข้อมูล Session ทุก ๆ หนึ่งนาที (SESS_MON), ตารางที่เอาไว้เก็บข้อมูลเก่าของตาราง SESS_MON ชื่อ SESS_MON_HISTORY และสุดท้ายตารางที่ใช้ในการเก็บค่า Baseline ชื่อ SESS_MON_BASE

SQL> create table lwm.sess_mon
2 (sess_time date,
3 sess_cnt number(4),
4 max_last_call_et number(8))
5 nologging;

Table created.

SQL> create table lwm.sess_mon_hist
2 as select * from lwm.sess_mon;

Table created.

SQL> create table lwm.sess_mon_base
2 (sess_cnt_base number(4),
3 max_last_call_et_base number(6),
4 base_date date);

Table created.

สิ่งที่เราต้องการทำทุก ๆ หนึ่งนาทีคือการบันทึก (Insert) ข้อมูลจำนวนของ Session ณ ขณะนั้น และ Last Call Elapse Time ที่สูงที่สุด ณ ขณะนั้น โดยเราใช้คำสั่ง
SQL> insert into lwm.sess_mon (sess_time,sess_cnt,max_last_call_et)
2 select sysdate,count(*),max(last_call_et)
3 from v$session
4 where username is not null
5 and status = 'ACTIVE';

จากนั้นเราจะมาถึงส่วนที่ยุ่งยากขึ้นอีกนิดคือการสร้าง Job ซึ่งก็คือการกำหนดเวลาให้ Oracle ทำงานทุก ๆ ช่วงเวลาที่เรากำหนดเช่นทุกหนึ่งนาทีให้ Insert ข้อมูลเข้าตารางข้างต้น
SQL> variable jobno number
SQL> begin
2 dbms_job.submit(:jobno,
3 'insert into lwm.sess_mon
4 (sess_time,sess_cnt,max_last_call_et)
5 select sysdate,count(*),max(last_call_et)
6 from v$session
7 where username is not null
8 and status = ''ACTIVE'';',
9 sysdate,'SYSDATE+1/24/60');
10 commit;
11 end;
12 /

PL/SQL procedure successfully completed.

เราสามารถเช็คได้ว่า Job ทำงานอยู่โดยคิวรีข้างล่างนี้ โดยทุก ๆ หนึ่งนาทีค่าในคอลัมน์ NEXT_SEC จะต้องบวกเพิ่มขึ้นหนึ่งนาที, BROKEN จะต้องเท่ากับ ‘N’ และ FAILURES = 0
SQL> set time on
09:31:34 SQL> select job,substr(what,1,20),last_sec,next_sec,broken,failures
2 from dba_jobs
3 ;
JOB        SUBSTR(WHAT,1,20)    LAST_SEC NEXT_SEC B FAILURES
---------- -------------------- -------- -------- - ----------
1          EMD_MAINTENANCE.EXEC 09:30:46 09:31:46 N 0
22         insert into lwm.sess 09:31:06 09:32:06 N 0

09:31:50 SQL> /

JOB        SUBSTR(WHAT,1,20)    LAST_SEC NEXT_SEC B FAILURES
---------- -------------------- -------- -------- - ----------
1          EMD_MAINTENANCE.EXEC 09:31:46 09:32:46 N 0
22         insert into lwm.sess 09:32:06 09:33:06 N 0

ถ้าเราต้องการลบ Job ทิ้งเพื่อรันใหม่ให้รันคำสั่งต่อไปนี้ ระวังต้องใส่หมายเลข JOB ให้ถูก เพราะบางทีอาจจะมี Job อื่น ๆ อยู่ด้วย ดูให้ดี ๆ นะครับ
SQL> begin
2 dbms_job.remove (22)
3 ;
4 end;
5 /

PL/SQL procedure successfully completed.

สมมติว่า Job ของเราใช้การได้ เราจะพบข้อมูลในตาราง SESS_MON ของเราเมื่อเวลาผ่านไปสักพัก
SQL> set pagesize 100
SQL> select to_char(sess_time,'dd-mon-yy hh24:mi'),sess_cnt,max_last_call_et
2 from lwm.sess_mon;

TO_CHAR(SESS_TI   SESS_CNT MAX_LAST_CALL_ET
--------------- ---------- ----------------
08-apr-11 09:21          3               30
08-apr-11 09:22          3               30
08-apr-11 09:23          3               30
08-apr-11 09:24          3               30
08-apr-11 09:28          3              197
08-apr-11 09:28          3               15
08-apr-11 09:29          3               20
08-apr-11 09:30          3               20
08-apr-11 09:31          3               20
08-apr-11 09:32          3               20


10 rows selected.


Light Weight Monitoring System (LWM) ตอนที่ 5 (จบ)

โพสต์ครั้งแรก: 21 มิถุนายน 2011

เราได้พูดถึงระบบ Light Weight Monitoring System (LWM) มาแล้วสี่ตอน ตอนนี้จะเป็นตอนสุดท้าย ซึ่งจะเป็นการนำเอาข้อมูลที่เราได้เตรียมไว้เรียบร้อยแล้วในฐานข้อมูล มาจัดให้อยู๋ในรูปแบบที่เข้าใจง่าย และแน่นอนจะต้องสร้างขึ้นและปรับปรุงข้อมูลได้ง่ายด้วย ด้วยเครื่องมือที่หาได้ทั่วไป
เราจะใช้ Excel ดึงข้อมูลผ่าน ODBC จากฐานข้อมูล LWM, ที่เราเตรียมไว้ดังได้กล่าวมาแล้วในสี่ขั้นตอนแรก เราจะมาเริ่มจากการสร้าง ODBC Data Source แล้วไปดึงข้อมูลผ่าน ODBC Data Source เข้าสู่ไฟล์ Excel 2007 โดยมีขั้นตอนดังต่อไปนี้
หากจำเป็นผู้อ่านสามารถใช้ Excel2003 แทน โดยวิธีการเข้าถึงเมนูต่าง ๆ จะต่างไปจากบน Excel2007 อยู่บ้าง ขอให้ผู้อ่านไปลองทดสอบดูนะครับ

  1. ติดตั้ง Oracle Client บนเครื่อง แล้วใช้ Net Manager หรือ Net Configuration Assistant
  2. สร้างเส้นทางการเชื่อมจาก Oracle (LWM) ไป Excel โดยใช้ ODBC
  3. ใช้ Excel ดึงข้อมูลจาก LWM และตั้งค่าให้เป็นแบบ Auto Refresh
  4. สร้างกราฟที่แสดงความเคลื่อนไหวจากข้อมูลที่ดึงมา


    สำหรับขั้นตอนที่ 1 จะไม่ขอพูดถึงในที่นี้นะครับ เพราะถือเป็นเบสิคทั่วไปแต่ขอให้แน่ใจก่อนว่าเราสามารถติดต่อกับระบบฐานข้อมูลผ่าน Oracle Net ได้ โดยอาจจะใช้ SQL*Plus หรือ Tool อื่น ๆ เช่น TOAD หรือ PL/SQL Developer ก็ได้ครับ
    เมื่อตรวจสอบแล้วว่าสามารถเชื่อมต่อกับระบบฐานข้อมูลที่ติดตั้ง LWM ไว้ได้แล้ว เราจะสร้างเส้นทางเชื่อมต่อให้ MSExcel สามารถคุยกับระบบฐานข้อมูล Oracle ได้ โดยใช้ ODBC ซึ่งมีอยู่บน Microsoft Windows ทุก ๆ เวอร์ชั่นอยู่แล้ว การติดตั้ง Oracle Client ส่วนหนึ่งจะมีการติดตั้ง Oracle Driver ไว้ในเครื่องด้วย โดย ODBC จะใช้ Driver ตัวนี้ในการติดต่อกับระบบฐานข้อมูล อ้อ..เครื่องของผมเป็น Windows XP SP3 และสำหรับ Excel เป็นเวอร์ชั่น 2007 นะครับ เริ่มกันเลยดีกว่า
    สร้างเส้นทางเชื่อมต่อ ODBC
    1. คลิ๊ก Start Menu => Settings => Control Panel, ดับเบิ้ลคลิ๊ก “Administrative Tools”, ดับเบิ้ลคลิ๊ก “Data Sources (ODBC)”
    2. ที่หน้าต่าง “ODBC Data Source Administrator” เลือกแท็บ “System DSN” แล้วคลิ๊ก Add จะปรากฎหน้าต่าง "Create New Data Source" (รูปที่ 1) เลือก Driver ที่เหมาะสมเพื่อติดต่อกับฐานข้อมูล LWM, Driver ตัวนี้คือตัวที่เกิดจากการ Install Oracle Client ในขั้นตอนแรก สำหรับเครื่องของผมเป็น "Oracle in OraHome92" จากนั้นคลิ๊ก Finish
    excel2
    รูปที่ 1: หน้าต่าง "Create New Data Source" เลือก Oracle Driver ที่เหมาะสม

    3. ที่หน้าต่าง "Oracle ODBC Driver Configuration ใส่ (รูปที่ 2)
    • Data Source Name : ชื่อ Data Source ตั้งชื่อให้เข้าใจได้ง่าย เช่น _ เป็นต้น เราจะใช้ชื่อนี้ตอนเลื่อก Connection ใน Excel
    • TNS Service Name ชื่อ TNS ที่เราตั้งไว้ Net Manager เป็นชื่อที่เราใช้ตอน Connect ดังเช่นเมื่อ Connect ด้วย SQL*PLUS เช่น scott/tiger@pao1
    • User ID : ชื่อ User ที่จะติดต่อกับฐานข้อมูล เช่น lwm
    • excel3
    รูปที่ 2: Oracle ODBC Driver Configuration

    • ทดสอบ Connection โดยคลิ๊ก "Test Connection" ใส่ Username/Password ถ้าหากว่า Configuration ใช้ได้จะขึ้นข้อความว่า "Connection Successful" คลิ๊ก OK เพื่อกลับมาที่หน้าต่าง "Oracle ODBC Driver Configuration" คลิ๊ก OK อีกครั้ง Data Source Name ที่เราสร้างขึ้นจะปรากฎใน List ของ Data Source (รูปที่ 3) คลิ๊ก OK เพื่อปิดหน้าจอ

      excel6
    รูปที่ 3: Data Source ที่เราสร้างปรากฎใน ODBC Data Source Administrator

    คราวนี้เราก็ได้เส้นทางเชื่อมต่อคือ ODBC Data Source สำหรับนำไปใช้ใน Excel ต่อไป

    ดึงข้อมูลเข้า Excel ผ่านช่องทางการเชื่อมต่อ ODBC
    1. เปิดไฟล์ Excel ใหม่ขึ้นมาแล้วเลือกเมนูหลัก Data และเมนูย่อย From Other Sources และเลือก From Microsoft Query (รูปที่ 4) จะปรากฎหน้าต่างให้เลือก "Choose Data Source"
    excel7
    รูปที่ 4 : การดึงข้อมูลจาก ODBC ใน Excel โดยเปิดเมนู "Data"

    2. ในหน้าต่าง "Choose Data Source" เลือกชื่อ Data Source ที่เราสร้างไว้ แล้วคลิ๊ก OK จะปรากฎหน้าต่างขึ้นมาให้ล็อคอิน ใส่ Username (lwm) และพาสเวิร์ดที่เราตั้งไว้ตอนสร้าง Schema (ดีฟอลต์คือ 'lwm' ซึ่งเหมือนกับชื่อ Username) คลิ๊ก OK จะปรากฎ หน้าต่าง "Query Wizard - Choose Columns" (รูปที่ 5) ขึ้นมาให้เลือกตาราง (วิว) และคอลัมน์ที่เราต้องการดึงข้อมูลมา
    excel10
    รูปที่ 5: เลือกวิว VW_SESS_MON_NORM จากใน Query Wizard

    3. ในหน้าต่าง "Query Wizard - Choose Column" เลือกวิวชื่อ "VW_SESS_MON_NORM" ซึ่งเป็นวิวที่เราสร้างขึ้นจากขั้นตอนก่อนหน้า (ตอนที่ 4) เพื่อแสดงผลของจำนวน Session พร้อม Baseline และ Maximum Last Call Elapse Time พร้อม Baseline ในวิวเดียว คลิ๊กเครื่องหมาย ">" เพื่อให้แสดงทุกคอลัมน์ของวิว คอลัมน์ทั้งหมดในวิวจะปรากฎในช่องด้านขวา จากนั้นคลิ๊ก Next ไปเรื่อย ๆ จนกระทั่งเจอหน้าต่าง "Query Wizard - Finish" (รูปที่ 6) คลิ๊ก Finish เพื่อยืนยัน
    excel11
    รูปที่ 6: หน้าต่างสุดท้ายของ Query Wizard

    4. จะปรากฎหน้าต่างเพื่อให้ระบุตำแหน่งบน Excel ที่จะนำข้อมูลลง ให้ใช้ค่าดีฟอลต์ คลิ๊ก OK
    excel12
    รูปที่ 7: เลือกตำแหน่งที่จะลงข้อมูล

    5. ข้อมูลจากวิวในฐานข้อมูลจะถูกนำมาแสดงใน Excel Sheet ดังรูปที่ 8 คลิ๊กขวาบนพื้นที่ที่มีข้อมูลปรากฎอยู่ เลือก Table => External Data Properties
    excel16
    รูปที่ 8: ข้อมูลจากฐานข้อมูลปรากฎใน Excel

    6. ที่หน้าต่าง External Data Properties คลิ๊กปุ่ม Properties ข้าง ๆ ชื่อของ Connection
    excel17
    รูปที่ 9: คลิ๊กปุ่ม Properties ขวามือของชื่อ Connection

    7. ในหน้าต่าง Connection Properties ที่แท็บ "Usage" ทำเครื่องหมายถูกที่ "Refresh every ... minutes" และใส่เลข 1 ในช่องว่าง เพื่อให้ระบบ Refresh ทุก 1 นาที ดังรูปที่ 10
    excel18
    รูปที่ 10: การตั้งค่าให้ระบบ Refresh ทุกช่วงเวลาที่กำหนด

    8. คลิ๊กที่แท็บ "Definition"แล้วคลิ๊ก "Save password" ซึ่งจะทำให้เมื่อเรา Refresh ข้อมูลจากใน Excel แล้วไม่ต้องใส่พาสเวิร์ดอีก เมื่อเสร็จแล้ว คลิ๊ก OK
    excel19
    รูปที่ 11: เข้าแท็บ "Definition" เพื่อ Save Password

    มาถึงตอนนี้ข้อมูลใน Excel ที่เรา Import ข้อมูลไว้ ควรจะ Refresh ข้อมูลใหม่ทุก 1 นาที ให้ Save ไฟล์ในชื่อ LWM_SID.xlsx โดย SID คือชื่อของฐานข้อมูลของคุณเอง

    กราฟที่มีชีวิต ความสำเร็จอยู่แค่เอื้อม
    ถ้าเราสังเกตข้อมูลในไฟล์ Excel ที่เราเพิ่งทำเสร็จ จะเห็นว่าวันที่และเวลาที่ปรากฎในไฟล์จะมีแค่หนึ่งชั่วโมงเท่านั้นเช่น 9:00-10:00 ซึ่งเมื่อเวลาผ่านไปหนึ่งนาที ข้อมูลแถวสุดท้าย (ข้อมูลตอน 10:00) จะกลายมาเป็นแถวรองสุดท้าย แล้วแถวสุดท้ายจะถูกแทนด้วยข้อมุลตอน 10:01 แทน เช่นเดียวกันข้อมูลเมื่อ 9:00 จะไม่ปรากฎบนแถวบนสุดแล้ว แต่จะถูกแทนที่ด้วยข้อมูลตอน 9:01 และจะเป็นเช่นนี้ไปเรื่อย ๆ ทุก ๆ นาที
    ข้อมูลที่เคลื่อนไหวเช่นนี้เมื่อเรานำมาสร้างกราฟ เราก็จะได้กราฟที่มีการเคลื่อนไหวคล้ายกับกราฟที่ปรากฎใน Performance Monitor หรือใน Performance Tab ใน Task Manager หรือแม้แต่ Active Session History ใน Oracle Enterprise Manager
    เราลองมาสร้างกราฟกันดีกว่า ขั้นตอนการสร้างพื้นฐานไม่ซับซ้อนเลย คุณอาจจะลองปรับเปลี่ยนคุณสมบัติหรือหน้าตาของกราฟเพื่อให้เข้าใจง่ายกับงานของคุณเอง ลองดูครับ สนุกดีทีเดียว!
    1. ไปที่เมนู Insert ในส่วนของ Chart เลือก "Line" แล้วเลือก 2-D Line อันแรกสุด ดังรูปที่ 12
    excel22
    รูปที่ 12: การเข้าถึงเมนูในการสร้างกราฟใน Excel
    2. จะปรากฎกราฟบนหน้าจอ คุณสามารถจัดแต่งกราฟได้ตามที่ต้องการเพื่อให้ดูง่ายสำหรับตัวคุณเอง
    excel23
    รูปที่ 13: กราฟที่เพิ่งสร้างเสร็จ

    3. ลองปรับแต่งสีสันให้ดูง่ายขึ้น ผมย้ายเอาส่วนของตารางข้อมูลไปไว้ทางขวาของชีท และเอากราฟวางไว้ทางซ้าย และเปลี่ยนสีของกราฟเล็กน้อยดังรูปที่ 14
    excel24
    รูปที่ 14: ปรุงแต่งรูปแบบให้กราฟดูง่าย
    สรุป
    วัตถุประสงค์ของ LWM คือเพื่อให้เราสามารถตรวจตราการทำงานของระบบฐานข้อมูล โดยกินทรัพยากรน้อย คล่องตัว ติดตั้งและจัดการง่าย มีประสิทธิภาพโดยสะท้อนภาพปัจจุบันของการใช้งานฐานข้อมูลของ Session ต่าง ๆ โดยรวม หวังว่าจะเป็นประโยชน์บ้าง ผู้อ่านสามารถนำไปใช้ได้นะครับ โดยเฉพาะผู้ที่ใช้ Oracle9i ลงไป หากได้ผลยังไงช่วยส่งข่าวด้วยนะคร๊าบบบบ...

    หมายเหตุ
    จากการทดลองใช้จริง พบว่าเมื่อทำการ Refresh ผ่าน Worksheet บน Excel จะมีจำนวน Session ล็อกอินเข้ามาเท่ากับจำนวนครั้งของการ Refresh เช่นถ้าให้ Refresh ทุกหนึ่งนาที ในหนึ่งชั่วโมงจะมี Session ล็อกอินเข้ามา 60 Session จำนวน Session เหล่านี้อาจจะเป็นปัญหาคือทำให้ Session เต็ม ซึ่งเราสามารถทำเป็น Job เพือ Kill Session เหล่านี้ออกไป ณ ช่วงเวลาหนึ่ง ส่วนวิธีการไม่ได้เป็นส่วนที่เราสนใจในบทความนี้ ฝากผู้อ่านที่สนใจค้นหาหรือพัฒนาต่อไปละกันครับ

    Light Weight Monitoring System (LWM) ตอนที่ 4

    โพสต์ครั้งแรก: 1 พฤษภาคม 2011

    การสร้าง Job เพื่อ Maintain และสร้าง Baseline อัตโนมัติ
    วันนี้จะเป็นตอนต่อของการสร้าง LWM แบบ Step-by_Step โดยเมื่อตอนที่แล้วเราได้เตรียมโครงสร้างต่าง ๆ ไว้แล้ว และได้สร้าง Job ที่กำหนดเวลาไว้ให้รันทุก ๆ หนึ่งนาทีเพื่อนำข้อมูลที่เกี่ยวกับ Session เก็บไว้ในตารางก่อนนำมาพล็อตกราฟต่อไป สำหรับวันนี้จะเป็นการเขียนสคริปต์เพื่อ Maintain ข้อมูลในตารางต่าง ๆ ในระบบให้รันได้อย่างมีประสิทธิภาพ ตลอดจนการสร้าง Baseline เก็บไว้ในฐานข้อมูลเพื่อนำมาพล็อตเปรียบเทียบกับข้อมูลปัจจุบัน
    Maintain ข้อมูลในตาราง SESS_MON และข้อมูล Baseline ใน SESS_MON_BASE
    วิธีการและสคริปต์ต่าง ๆ ที่ระบุไว้ ณ ที่นี้ได้รับการทดสอบแล้วเป็นการภายใน ซึ่งสามารถใช้งานได้ตามที่ระบุไว้ อย่างไรก็ตามเราไม่สามารถรับรองได้ว่าจะได้ผลสำเร็จทุกประการสำหรับผู้อ่าน กรุณาทดสอบในสิ่งแวดล้อมของคุณก่อนที่จะใช้งานจริง

    เนื่องจากเราต้องการให้การคิวรีข้อมูลจากตาราง SESS_MON มีประสิทธิภาพที่สุด (เร็วที่สุดและไม่สิ้นเปลืองทรัพยากรระบบ เนื่องจากจะต้องคิวรีตลอดเวลา) เราจะสร้าง Job ขึ้นมาให้ทำการ Purge ข้อมูลใน SESS_MON และนำไปเก็บไว้ใน SESS_MON_HIST แทน เราใช้ข้อมูลใน SESS_MON_HIST ในการสร้าง Baseline โดยการหาค่าเฉลี่ยของค่าที่เราต้องการมอนิเตอร์
    • คำสั่งข้างล่างใช้ในการ Purge ข้อมูลโดยย้ายจาก SESS_MON ไปไว้ที่ SESS_MON_HIST
    insert into lwm.sess_mon_hist select * from lwm.sess_mon where sess_time < trunc(sysdate);
    delete from lwm.sess_mon where sess_time < trunc(sysdate);
    • คำสั่งที่ใช้ในการสร้าง Baseline ใหม่ โดยการนำเอาข้อมูลจากตาราง SESS_MON_HIST (ซึ่งเก็บข้อมูลย้อนหลังของ Session) ย้อนหลัง 30 วัน นำเฉพาะค่าที่มีความถี่สูง ๆ (อยู่ในช่วง +/- Standard Deviation /2) มาหาค่าเฉลี่ย
    insert into lwm.sess_mon_base
    select round(avg(sess_cnt)),round(avg(max_last_call_et)),sysdate
    from lwm.sess_mon_hist
    where sess_time between sysdate-30 and sysdate
    and max_last_call_et between
    (select avg(max_last_call_et)- stddev(max_last_call_et)/2
    from lwm.sess_mon_hist
    where sess_time between sysdate-30 and sysdate)
    and (select avg(max_last_call_et)+ stddev(max_last_call_et)/2
    from lwm.sess_mon_hist
    where sess_time between sysdate-30 and sysdate);
    คำสั่งที่ใช้ในการ Maintain ทั้งหมดจะถูกนำมาสร้าง Job เพื่อให้ทำการ Maintain ระบบโดยอัตโนมัติตอน 2:00 น. ของทุกวัน
    SQL> variable jobno number
    SQL> begin
    2 dbms_job.submit(:jobno,
    3 -- Move data to SESS_MON_HIST
    4 'insert into lwm.sess_mon_hist
    5 select * from lwm.sess_mon where sess_time < trunc(sysdate);
    6 -- Recreate Baseline
    7 delete from lwm.sess_mon_base;
    8 insert into lwm.sess_mon_base
    9 select round(avg(sess_cnt)),round(avg(max_last_call_et)),sysdate
    10 from lwm.sess_mon_hist
    11 where sess_time between sysdate-30 and sysdate
    12 and max_last_call_et between
    13 (select avg(max_last_call_et)- stddev(max_last_call_et)/2
    14 from lwm.sess_mon_hist
    15 where sess_time between sysdate-30 and sysdate)
    16 and (select avg(max_last_call_et)+ stddev(max_last_call_et)/2
    17 from lwm.sess_mon_hist
    18 where sess_time between sysdate-30 and sysdate);
    19 -- Clear old data
    20 delete from lwm.sess_mon where sess_time < trunc(sysdate);'
    21 ,SYSDATE,'TRUNC(SYSDATE+1)+2/24');
    22 commit;
    23 end;
    24 /

    PL/SQL procedure successfully completed.
    ข้อมูลใน SESS_MON_HIST จะโตขึ้นเรื่อย ๆ วันละ 1440 เรคคอร์ดทุกวัน (60 x 24 = 1440) คุณอาจจะทำ Job อีกตัวที่จะทำการ Aggregate ข้อมูลจากรายนาทีเป็นรายวันแล้วเก็บไว้ในอีกตารางหนึ่ง แล้วจึงลบข้อมูลใน SESS_MON_HIST ที่ได้ทำการ Aggregate แล้วออกไป ด้วยวิธีนี้ข้อมูลในระบบ LWM จะสามารถเก็บไว้ได้เป็น 100 ปี (365 x 10 = 3650 เรคคอร์ด) โดยไม่ต้อง Maintain!

    สร้างวิวเพื่อนำข้อมูลมาพล็อตกราฟ
    ขั้นตอนสุดท้ายเป็นสร้างวิวที่ใช้ในการเรียกข้อมูลปัจจุบันจาก SESS_MON มาเปรียบเทียบกับ Baseline ใน SESS_MON_BASE ซึ่งเราจะใช้ในการนำไปพล็อตกราฟ
    SQL> create or replace view lwm.vw_sess_mon_norm as
    2 select to_char(a.sess_time,'hh24:mi') as sess_time
    3 ,round(a.sess_cnt/b.sess_cnt_base,2) as sess_cnt_per_base,
    4 1 as sess_cnt_base
    5 ,round(max_last_call_et/b.max_last_call_et_base,2) as max_last_call_et_per_base,
    6 1 as max_last_call_base
    7 from lwm.sess_mon a, lwm.sess_mon_base b
    8 where a.sess_time between sysdate-1/24 and sysdate
    9 order by a.sess_time;

    View created.

    SQL> select * from lwm.vw_sess_mon_norm;

    SESS_ SESS_CNT_PER_BASE SESS_CNT_BASE MAX_LAST_CALL_ET_PER_BASE MAX_LAST_CALL_BASE
    ----- ----------------- ------------- ------------------------- ------------------
    11:59 .67 1 .52 1
    12:00 .67 1 703.16 1
    12:01 .67 1 .97 1
    12:02 .67 1 .97 1
    12:03 .67 1 .97 1
    12:04 .67 1 .97 1
    12:05 .67 1 .97 1


    7 rows selected.


    SQL> desc lwm.vw_sess_mon_norm;
    Name Null? Type
    ---------------------------- -------- ------------------------------------
    SESS_TIME VARCHAR2(5)
    SESS_CNT_PER_BASE NUMBER
    SESS_CNT_BASE NUMBER
    MAX_LAST_CALL_ET_PER_BASE NUMBER
    MAX_LAST_CALL_BASE NUMBER

    คอลัมน์ต่างๆ มีความหมายดังนี้
    ชื่อคอลัมน์ความหมาย
    SESS_TIMEเวลา ณ ขณะจับข้อมูล Session
    SESS_CNT_PER_BASEจำนวนของ Session ต่อ Baseline ถ้าค่านี้เท่ากับ 1 หมายความว่าจำนวน Session ณ ขณะนั้นมีค่าเท่ากับค่า Baseline
    SESS_CNT_BASEค่า Baseline ของ SESS_CNT ค่านี้จะเป็น 1 เสมอ
    MAX_LAST_CALL_ET_PER_BASEค่า LAST_CALL_ET สูงสุด ณ ขณะจับข้อมูล เปรียบเทียบกับ Baseline ที่คำนวณได้ ถ้าค่านี้เท่ากับ 1 หมายความว่าค่า LAST_CALL_ET สูงสุด ณ ขณะนั้นมีค่าเท่ากับค่า Baseline
    MAX_LAST_CALL_BASEค่า Baseline ของ MAX_LAST_CALL_ET ค่านี้่จะมีค่าเป็น 1 เสมอ

    ครั้งหน้าเราจะมาดูกันเรื่องการนำข้อมูลจากวิวขึ้นพล็อตเป็นกราฟบน Excel นะครับ

    Light Weight Monitoring System (LWM) ตอนที่ 2

    โพสต์ครั้งแรก: 20 มีนาคม 2011


    เราจะสร้างกราฟได้อย่างไร?
    กราฟจะถูกสร้างใน MS Excel บนเครื่อง Database Server หรือ Client ก็ได้ แต่เนื่องจากเครื่องที่จะใช้แสดงกราฟจะต้องมี MS Excel ผมจึงแนะนำให้รันกราฟบนเครื่อง Client ที่เป็น WindowsXP โดยก่อนที่จะดึงข้อมูลมาแสดงได้ เราจะต้องลงโปรแกรม Oracle Client ก่อน จากนั้นก็สร้าง ODBC Data Source แล้วใช้ฟังก์ชัน Get External Data ใน MS Excel ในการดึงข้อมูลที่เราเก็บไว้ในฐานข้อมูลมาแสดง (วิธีการจะแตกต่างกันไปในแต่ละ Excel เวอร์ชั่น) จากนั้นเราก็ตั้งเวลาในการให้ Excel ดึงข้อมูลที่เราต้องการ (ซึ่งควรจะมากกว่าหรือเท่ากับช่วงห่างของแต่ละ Snapshot) เราก็จะได้เห็นข้อมูลที่อัพเดทไปเรื่อย ๆ เมื่อเวลาผ่านไป และเมื่อเราสร้างเป็นกราฟบนข้อมูลเหล่านี้ เราก็จะเห็นกราฟไหลเลื่อนไปเรื่อย ๆ ตามข้อมูลที่เปลี่ยนแปลงไปตามกาลเวลา

    ทำไมจึงเรียกว่า Light Weight มันเบาจริงหรือ?
    ข้อมูลที่ผมทำ Snapshot เป็นการดึงมาจาก Dynamic Performance View ชื่อ V$SESSION เพียงตารางเดียวเท่านั้นโดยผมใช้คำสั่ง
    select sysdate, count(*), max(last_call_et)
    from v$session
    where username is not null
    and status = ''ACTIVE'

    ซึ่งปกติแล้ว Dynamic Performance View เป็นวิวที่เราใช้ในการตรวจสอบ Real Time Performance ของระบบฐานข้อมูล Oracle อยู่แล้ว โดยการคิวรีวิวนี้จะใช้ทรัพยากรระบบน้อยมาก
    Dynamic Performance View จะมีชื่อขึ้นต้นด้วย V$ ซึ่งจริง ๆ แล้วเป็น Synonym ที่ชี้ไปที่วิว V_$ ซึ่งเขียนขึ้นเพื่อไปดึงข้อมูลจากวิว X$ อีกที โดย X$ เป็นวิวในระดับ Low Level มาก ๆ ที่แสดงถึงโครงสร้างภายในของระบบฐานข้อมูล Oracle, X$ไม่ใช่ตาราง แต่เป็นโครงสร้างข้อมูลที่เป็นภาษา C และจัดการโดย Oracle Kernel (ดูเพิ่มเติม The architecture of the Dynamic Performance Views, http://www.dba-oracle.com/concepts/v$_dynamic_performance_views.htm)

    V$SESSION เป็นข้อมูลของผู้ที่เชื่อมต่อกับระบบฐานข้อมูลอยู่ ณ ขณะนั้น ๆ ข้อมูลที่เราต้องการจาก V$SESSION คือจำนวนของผู้เชื่อมต่อที่ทำงาน “ACTIVE” กับฐานข้อมูลอยู่ นอกจากนั้นเราก็ต้องการข้อมูลว่า ณ ขณะนั้นผู้ที่เชื่อมต่อที่มีสถานะเป็น ACTIVE นั้นได้ ACTIVE มาเป็นเวลานานเท่าใดแล้ว (LAST_CALL_ET) โดยเราต้องการเฉพาะเวลา LAST_CALL_ET ที่สูงที่สุด ณ ขณะนั้นเท่านั้น นอกจากนั้นผมได้เพิ่มข้อมูลวันที่และเวลาเข้าไปในคิวรีด้วยเพื่อให้ทราบเวลาที่เราเก็บข้อมูล ข้อมูลเหล่านี้จะถูกเก็บเข้าตารางชื่อ SESS_MON ทุก ๆ ช่วงเวลาที่เราตั้งไว้ โดยใช้ Package ของ Oracle ที่ชื่อว่า DBMS_JOB ซึ่งตาราง SESS_MON เป็นตารางที่ไม่เก็บข้อมูลใน Redo Log (NOLOGGING) ซึ่งจะทำให้การทำงานกินทรัพยากรน้อยลง
    ทุก ๆ หลังเที่ยงคืน ข้อมูลของเมื่อวานจะถูกย้ายไปเข้าตาราง History (SESS_HIST) เพื่อให้ข้อมูลที่ใช้ในการคิวรีจากตาราง SESS_MON ตอนกลางวันมีปริมาณน้อยที่สุด ผมไม่ได้สร้างอินเด็กซ์บน SESS_MON เพื่อลดการใช้ทรัพยากรระบบในขณะเก็บข้อมูล (ผมตั้งสมมติฐานว่าการทำ Full Table Scan บน SESS_MON ไม่ได้กระทบกับการทำงานของระบบเท่าไรนัก) จากนั้นข้อมูล Baseline จะถูกคำนวณโดยใช้ข้อมูลจากตาราง SESS_HIST เป็นเวลา 30 วันย้อนหลังนับจากวันที่ล่าสุดที่เก็บเข้าไป ข้อมูลที่คำนวณได้จะถูกเก็บแทนที่ข้อมูลเก่าในตาราง SESS_AVG เพื่อใช้เป็น Baseline ต่อไป กระบวนการทั้งหมดนี้จะถูกทำโดยอัตโนมัติโดย DBMS_JOB สิ่งที่ผู้ดูแลระบบจะต้องทำคือการคอยลบข้อมูลออกจาก SESS_HIST เมื่อต้องการพื้นที่คืนเท่านั้น และเนื่องจาก SESS_HIST ไม่ได้ใช้ในข้อมูลที่แสดงกราฟ จำนวนข้อมูลใน SESS_HIST จึงไม่มีผลกระทบต่อการคิวรีข้อมูลเพื่อแสดงกราฟในตอนกลางวัน
    ข้อมูลที่ถูกดึงขึ้นบน Excel เป็นวิว Cartesian ที่เกิดจากการ Join ข้อมูลจากตาราง SESS_MON และ SESS_AVG (ตาราง Baseline มีเรคคอร์ดเดียว) วิวดังกล่าวจะดึงเฉพาะข้อมุลเพียง 60 นาทีย้อนหลัง การตั้งเวลาในการดึงข้อมูลจากวิวบน Excel ก็จะได้ข้อมูลใหม่ที่นับจากนาทีปัจจุบันย้อนหลังไป 60 นาทีทุกครั้ง
    ผมทำการทดสอบบนระบบฐานข้อมูล Oracle9i บน Windows XP โดยก่อนติดตั้งระบบ LWM ผมตรวจสอบการใช้ CPU และ Memory และบันทึกผลที่ได้ จากนั้นจึงติดตั้งระบบให้ทำงานโดยกำหนดช่วง Snapshot ที่ 1 นาที และเปิดไฟล์ Excel ที่ได้ติดตั้งระบบกราฟแสดงผลที่จะ Refresh ข้อมูลทุก ๆ 1 นาทีไว้ โดยได้ผลการทดสอบดังตารางข้างล่าง โดยจากผลการทดสอบพบว่าก่อนและหลังการติดตั้งระบบและรัน MS Excel มีการใช้หน่วยความจำเพิ่มขึ้นประมาณ 1.2 MB ดังแสดงในตารางที่1, รูปที่1 และรูปที่ 2
    หัวข้อทดสอบ
    CPU Usage*
    Memory Usage
    ก่อนการติดตั้ง LWM และรัน Excel
    00
    233,588 K
    หลังการติดตั้ง LWM (Snapshot ทุก 1 นาที)
    และรัน Excel (Refresh ทุก 1 นาที)
    00
    234,784 K
    ตารางที่1 เปรียบเทียบการใช้ทรัพยากรก่อนและหลังติดตั้ง LWM
    *CPU Usage มีค่าเท่ากับ 00 ไม่ได้หมายถึงไม่ได้ใช้ CPU เลยแต่ปริมาณการใช้ CPU อาจจะน้อยมาก
    lwm task mgr1
    รูปที่1 แสดงการใช้ CPU และ Memory ก่อนการติดตั้ง LWM
    lwm task mgr2
    รูปที่2 หลังการติดตั้ง LWM (Snapshot ทุก 1 นาที) และรัน Excel (Refresh ทุก 1 นาที)

    การจัดการเรื่องสิทธิ์และความปลอดภัยของระบบ ฯ
    เราจะสร้าง Schema ใหม่ขึ้นมาเพื่อทำการเก็บข้อมูลและองค์ประกอบต่าง ๆ ของระบบ การติดตั้งระบบจะต้องทำโดย User ที่มีสิทธิ์เป็น DBA เท่านั้น ผมไม่ใช้วิธีการ Grant สิทธิ์ต่าง ๆ ให้กับ User ที่สร้างขึ้นใหม่เนื่องจากไม่ต้องการให้ User ที่จะ Connect เข้ามาทาง ODBC ซึ่งอาจจะเป็น System Operator มีสิทธิ์อื่น ๆ นอกเหนือจากสิทธิ์ที่ให้ใน Schema ที่สร้างขึ้นใหม่ โดย Schema หรือ User ที่สร้างขึ้นใหม่นี้จะมีสิทธิ์ เฉพาะการ Connect และดูหรือจัดการข้อมูลเฉพาะที่อยู่ใน Schema ของตนเท่านั้น ซึ่ง User นี้จะใช้ใน ODBC Data Source เพื่อที่จะดึงข้อมูลมาแสดงใน Excel เท่านั้น, User นี้จึงไม่สิทธิ์ในการไปใช้ (เรียกดูหรือแก้ไข) ข้อมูลใน Schema อื่น ๆ


    ขอบคุณที่ให้ความสนใจ โปรดติดตามต่อตอนหน้านะครับ

    Light Weight Monitoring System (LWM) ตอนที่ 1

    โพสต์ครั้งแรก: 14 มีนาคม 2011


    LWM คือระบบที่จะเก็บข้อมูลตามช่วงเวลาที่กำหนดเช่น ทุก ๆ 1 นาที หรือ 5 นาทีเป็นต้น และแสดงผลออกมาในรูปของกราฟ ระบบจะทำการ “เรียนรู้” จากข้อมูลที่ได้ในอดีตเพื่อที่จะสร้าง Baseline ที่ใกล้เคียงและสะท้อนถึงความเป็นจริงปัจจุบันมากที่สุดโดยอัตโนมัติ ซึ่งจะทำให้ผู้ดูแลระบบสามารถตรวจสอบสถานะของระบบโดยเปรียบเทียบกับ Baseline ที่ได้จากข้อมูลในอดีตโดยผ่านภาพกราฟฟิค
    ปั
    ญหาหนึ่งที่ผมพบอยู่เสมอ ๆ คือกรณีที่มีระบบฐานข้อมูลที่เราต้องดูแลเป็นจำนวนมาก การจะรู้ถึงปัญหาที่เกิดขึ้นกับระบบฐานข้อมูลตัวใด ๆ นั้นมักจะช้าเกินไป คือเมื่อมีปัญหาสักพักเราจึงจะรู้ เช่นเมื่อมีการรอคอยของ Session ใด ๆ นานผิดปกติ หรือการมีจำนวนของ Session มากขึ้นผิดปกติ เมื่อรู้ปัญหาถึงปัญหาช้าก็ทำให้เราแก้ไขได้ช้า ซึ่งอาจจะทำให้เราตกข้อตกลงที่ให้ไว้เกี่ยวกับระดับคุณภาพของการให้บริการหรือ Service Level Agreement (SLA) ได้ การมีเครื่องมือที่จะช่วยให้เราสามารถมองเห็นปัญหานี้แต่เนิ่น ๆ จะช่วยให้เราสามารถรับมือกับสถานการณ์ได้ดีขึ้น

    บทความนี้จะแสดงถึงวิธีการสร้างเครื่องมือดังกล่าวที่จะแสดงผลออกมาเป็นกราฟที่สร้างขึ้นบน Microsoft Excel ซึ่งจะเปลี่ยนแปลงไปทุก ๆ นาที ยิ่งไปกว่านั้นบนกราฟยังประกอบด้วย Baseline เพื่อที่จะช่วยให้เราสามารถบอกได้ว่าเส้นกราฟที่เห็นมีความผิดปกติไปมากน้อยแค่ไหน เครื่องมือนี้ผมเป็นผู้เขียนขึ้นเพื่อแก้ปัญหาดังกล่าว และเรียกมันว่า "Light Weight Monitoring System" (LWM) ผู้สนใจสามารถนำไปพัฒนาใช้ต่อได้ ไม่สงวนลิขสิทธิ์ครับ แต่จะขอบคุณมากถ้าจะแจ้งให้ทราบถึงปัญหาหรือผลการใช้งานกันบ้างนะคร๊าบ : )


    Light Weight Monitoring System
    ปัญหาอันหนึ่งที่เราในฐานะผู้ดูแลระบบฐานข้อมูลพบกันบ่อย ๆ ก็คือ “ทำอย่างไรจึงจะสามารถบ่งชี้การเพิ่มขึ้นของตัวชี้วัดบางตัว (เช่นจำนวนของ Session ที่ทำงานอยู่ หรือเวลาที่ Session ได้ใช้ไปแล้วในการรันคำสั่งปัจจุบัน) หรือแม้ว่าเราจะรู้ว่าค่าของตัวชี้วัดเหล่านี้กำลังเพิ่มขึ้น เราจะรู้ได้อย่างไรว่าการเพิ่มขึ้นนี้ผิดปกติหรือไม่ ตัวอย่างเช่น ถ้าจำนวนของ Session เพิ่มขึ้นจาก 50 ไปเป็น 80 หมายถึงความผิดปกติหรือไม่ คำตอบคืออาจจะใช่ หรืออาจจะไม่ใช่ กล่าวได้ว่าถ้าเราไม่มีค่ากลาง ๆ ที่จะใช้ในการเปรียบเทียบ เราจะไม่มีทางรู้เลยว่าการเพิ่มขึ้นของตัวชี้วัดเหล่านี้มีความวิกฤตเพียงใด
    LWM คือระบบที่จะเก็บข้อมูลตามช่วงเวลาที่กำหนดเช่น ทุก ๆ 1 นาที หรือ 5 นาทีเป็นต้น และแสดงผลออกมาในรูปของกราฟ ระบบจะทำการ “เรียนรู้” จากข้อมูลที่ได้ในอดีตเพื่อที่จะสร้าง Baseline ที่ใกล้เคียงและสะท้อนถึงความเป็นจริงปัจจุบันมากที่สุดโดยอัตโนมัติ ซึ่งจะทำให้ผู้ดูแลระบบสามารถตรวจสอบสถานะของระบบโดยเปรียบเทียบกับ Baseline ที่ได้จากข้อมูลในอดีตโดยผ่านภาพกราฟฟิค

    lwm_excel
    รูปที่1 LWM ใช้กราฟอัพเดท Real-time ที่สร้างขึ้นบน MS Excel เป็นตัวแสดงผล
    ปกติปัญหาที่ไม่ได้ส่งผลปัจจุบันทันด่วนเช่น พวก ORA-00600 เราอาจจะสามารถใช้วิธีการอื่น ๆ เช่นเขียนสคริปต์เพื่อตรวจสอบ Error เหล่านี้ได้แบบวันต่อวัน แต่ปัญหาที่ส่งผลแบบปัจจุบันทันด่วนจะทำให้ผู้ใช้ระบบได้รับผลกระทบในทันที ซึ่งโดยมากจะทำให้ผู้ใช้ระบบต้องรอคอยเป็นเวลานาน วัตถุประสงค์ของ LWM คือการจับเอาแนวโน้มของค่าทางสถิติบางตัวที่เราสนใจซึ่งเบี่ยงเบนไปจากค่าเฉลี่ยเมื่อเวลาผ่านไป เพื่อให้ผู้ดูแลระบบสามารถที่จะพบปัญหาได้โดยง่ายก่อนที่ปัญหาจะบานปลายหรือ ก่อนที่จะมีผลกระทบกับผู้ใช้ระบบ
    เพื่อที่จะตรวจดูแนวโน้มนี้บนกราฟได้ เราจะต้องกำหนดช่วงห่างเวลาของแต่ละ “Snapshot” (การเก็บข้อมูลในแต่ละจุดของเวลา) ยิ่งช่วงห่างนี้น้อยเท่าใดเราก็จะได้กราฟที่แสดงข้อมูลที่ล่าสุดที่สุด อย่างไรก็ตามช่วงห่างที่น้อยจะทำให้ระบบทำงานบ่อยมากกว่าการกำหนดช่วงห่างที่ห่างกว่าซึ่งอาจจะมีผลกับประสิทธิภาพโดยรวมของระบบบ้าง เราอาจจะกำหนดช่วงห่างเริ่มต้นไว้ที่ 1 ถึง 5 นาที แล้วปรับแต่งให้เข้ากับความจำเป็นของแต่ละระบบก็ได้ (ช่วงเวลาที่ห่างน้อยกว่า 1 นาทีมักจะไม่ค่อยแสดงความแตกต่างระหว่างจุดสองจุดมากนัก) เพื่อที่จะลดภาระของระบบข้อมูลที่เก็บจะต้องมากพอที่จะบอกถึงสภาวะโดยรวมของการเปลี่ยนแปลงของการทำงานบนระบบฐานข้อมูลที่เราสนใจ ในขณะเดียวกันข้อมูลที่เก็บจะต้องเล็กพอที่จะไม่ก่อให้เกิดการดึงประสิทธิภาพของระบบให้เลวลงด้วย

    เราแสดงอะไรบนกราฟบ้าง?
    ตัวชี้วัดที่เราใส่ไว้ในกราฟประกอบด้วย
    • จำนวนของ Session: จำนวนของ Session ที่ Active หมายถึง Session ที่กำลังทำงานกับฐานข้อมูลอยู่ ณ เวลาที่ทำการเก็บข้อมูล ซึ่งทำโดยการ Select ข้อมูลจาก V$SESSION
    • ค่า Baseline ของจำนวน Session: ที่ได้จากการคำนวณโดยใช้ข้อมูลย้อนหลังไป 30 วัน Baseline นี้คำนวณจากการหาค่าเฉลี่ยของจำนวน Session ย้อนหลังไป 30 วัน
    • ค่าของ Last Call Elapse Time สูงสุด: เมื่อ Session ใด ๆ เชื่อมต่อกับฐานข้อมูลมันจะอยู่ในสถานะ Active ในบางช่วงเวลา และอยู่ในสถานะ Inactive ในบางช่วงเวลา เรารู้ว่าเมื่อ Session อยู่ในสถานะ Active แสดงว่ามันกำลังทำอะไรบางอย่างกับฐานข้อมูลอยู่ ค่านี้แสดงเวลาที่ผ่านไปแล้ว (เป็นวินาที) นับตั้งแต่ Session เริ่มอยู่ในสถานะ Active ครั้งหลังสุด ในการเก็บข้อมูลแต่ละครั้งเราจะเก็บเฉพาะค่า Last Call Elapse Time ที่มากที่สุด ณ ขณะนั้น โดยการ Select จาก V$SESSION เช่นกัน
    • ค่า Baseline ของค่า Last Call Elapse Time สูงสุด: ที่ได้จากการคำนวณโดยใช้ข้อมูลย้อนหลังไป 30 วัน ในทำนองเดียวกับการหาค่า Baseline ของจำนวน Session
    V$SESSION เป็น Dynamic Performance View ที่ให้ข้อมูลเกี่ยวกับ Session ทั้งหมดที่กำลังติดต่อกับฐานข้อมูล ณ ขณะนั้น ๆ โดย มีสถานะต่าง ๆ ตามเอกสารอ้างอิงใน Oracle9i Database Reference Release 2 (9.2) ดังนี้
    - ACTIVE currently executing SQL
    - INACTIVE ยังคงเชื่อมต่ออยู่แต่ไม่ได้ทำกิจกรรมใด ๆ บนฐานข้อมูล
    - KILLED marked to be killed
    - CACHED temporarily cached for use by Oracle*XA
    - SNIPED session inactive, waiting on the client

    สถานะของ Session จะปรากฎในคอลัมน์ STATUS ใน V$SESSION และจะเปลี่ยนไปมาระหว่างที่ Session ยังติดต่อกับฐานข้อมูล กล่าวคือในบางขณะจะมีสถานะเป็น Active และในบางสถานะจะเป็น Inactive ฯลฯ โดยสถานะที่เราสนใจคือ Active ซึ่งเป็นสถานะที่ Session กำลังรัน SQL บนระบบฐานข้อมูล ถ้าเราใช้คอลัมน์นี้ร่วมกับคอลัมน์ LAST_CALL_ET (Last Call Elapse Time) เราจะรู้เวลา (เป็นวินาที) ที่ผ่านไปจากการรันคำสั่ง SQL ตัวปัจจุบันของ Session นั้น ๆ ดังนั้นหากค่าของ LAST_CALL_ET เพิ่มสูงขึ้นผิดปกติก็แสดงว่า Session กำลังใช้เวลาในการรันคำสั่งนานเกินไปนั่นเอง

    โดยข้อมูลดังกล่าวถ้าเรารู้ค่าเฉลี่ยของ LAST_CALL_ET ที่สูงที่สุด ณ ช่วงเวลาต่าง ๆ โดยตัดเอาค่าที่สูงผิดปกติออกไป เราก็จะสามารถรู้ว่าระบบฐานข้อมูลตัวดังกล่าวควรจะมี LAST_CALL_ET ไม่เกินเท่าใดจึงจะถือว่าอยู่ในเกณฑ์ปกติ เราเรียกค่าเฉลี่ยนี้ว่า Baseline กรณีเดียวกันเราสามารถหาค่า Baseline ของจำนวน Session ปกติได้ โดยหาค่าเฉลี่ยของจำนวน Session ณ ช่วงเวลาต่าง ๆ และตัดเอาช่วงเวลาที่มีค่าจำนวน Session สูงผิดปกติออกไป


    ขอบคุณที่ให้ความสนใจ โปรดติดตามต่อตอนหน้านะครับ


    Thursday, July 15, 2010

    Query Plan เปี๊ยนไป๋!!?

    Query Plan เปี๊ยนไป๋!!?
    Oracle Version: 10g
    เมื่อเราทำการ Gather Statistics กับตารางที่ข้อมูลไม่ได้มีการปลี่ยนแปลงสามารถทำให้ Query Plan ที่เกิดกับคิวรีบนตารางเหล่านั้นเปลี่ยนไปได้ เช่นหากวันหนึ่งเราทำการ Gather Statistics รันคิวรี (และบันทึก Query Plan) เอาไว้ วันต่อมาเรา Gather Statistics อีก (และไม่ได้แก้ไขข้อมูลเดิมแต่อย่างใด) เมื่อเราคิวรีข้อมูลในตารางเหล่านั้นเราอาจจะพบว่า Query Plan ที่ได้ เปลี่ยนไป ซึ่ง Query Plan ที่ได้ ดูเหมือนวาจะดีกว่าตัวก่อน สาเหตุเป็นเพราะอะไร?

    ความเปลี่ยนแปลงข้างต้นทางหนึ่งอาจจะมาจากค่าดีฟอลต์ของพารามิเตอร์ในการ Gather Statistics ที่ชื่อ METHOD_OPT ใน DBMS_STATS โดยใน Oracle10g พารามิเตอร์ตัวนี้มีค่าดีฟอลต์เป็น SIZE AUTO หลังจากที่เรารันคิวรี Oracle จะจำรูปแบบการคิวรีรวมถึงคอลัมน์ต่าง ๆ ไว้ในตาราง Dictionary ชื่อ SYS.COL_USAGE$ ซึ่งครั้งต่อไปที่เรารัน DBMS_STATS เพื่อรวมรวมสถิติของตารางเดิมเหล่านี้ DBMS_STATS จะคิวรีตารางนี้ก่อนเพื่อดูว่ามีคอลัมน์ไหนบ้างที่จะควรจะเก็บข้อมูล Histogram ด้วยโดยดูจาก Workload ของคิวรีตัวก่อนหน้า

    ลองดูกับตัวอย่างง่าย ๆ กันนะครับ เริ่มจากการสร้างตารางเล็ก ๆ ตารางหนึ่งซึ่งมีข้อมูลที่มีความ "เบ้" (Skew) สูง ๆ อยู่ในคอลัมน์ชื่อ ID และรวบรวมสถิติแบบปกติก่อน โดยเราจะทำให้ข้อมูลมีความเบ้สูงโดยที่หากคิวรีข้อมูลในคอลัมน์ ID ที่มีค่าอยู่ระหว่าง 0 ถึง 4 ค่าใดค่าหนึ่งจะได้จำนวนแถวประมาณ 20% ของข้อมูลทั้งหมด ในขณะที่ถ้าเราคิวรีเอาค่า 99 ฐานข้อมูลจะแสดงผลออกมาเพียงแถวเดียว
    SQL> create table t
    2 as
    3 select mod(rownum,5) id, a.*
    4 from all_objects a;
    Table created.

    SQL> update t
    2 set id = 99
    3 where rownum = 1;
    1 row updated.

    SQL> create index t_idx on t(id);
    Index created.

    SQL> begin
    2 dbms_stats.gather_table_stats
    3 ( user, 'T' );
    4 end;
    5 /

    SQL> select column_name, count(*)
    2 from user_tab_histograms
    3 where table_name = 'T'
    4 and column_name = 'ID'
    5 group by column_name;

    COLUMN_NAME COUNT(*)
    ----------- ------------
    ID 2

    ตอนนี้คอลัมน์ ID ไม่มี Histogram ที่สมบูรณ์ โดยดีฟอลต์ -- มีแค่ 2 Bucket ดังที่แสดงในคิวรีตาราง USER_TAB_HISTOGRAMS เพื่อที่จะให้ Optimizer เข้าใจในลักษณะความเบ้ของข้อมูล ผมต้องการ Histogram ที่มีมากกว่า 2 Bucket การมีแค่ 2 Bucket ใน USER_TAB_HISTOGRAM จะบอก Optimizer ได้แค่ว่าค่าใดสูงสุดและต่ำสุดเท่านั้น จากการ Gather Statistics ตอนนี้ Optimizer มีข้อมูลดังต่อไปนี้
    1. ค่าสูงสุด (99)
    2. ค่าต่ำสุด (0)
    3. จำนวนของค่าที่ไม่ซ้ำกัน (กรณีนี้คือ 6 ได้แก่ 0,1,2,3,4,99)
    4. จำนวนแถวในตาราง (50,119)
    ด้วยข้อมูลเหล่านี้ Optimizer จะเชื่อว่าค่าแต่ละค่า (ใน 6 ค่า) มีจำนวนแถวเท่า ๆ กันในตารางคือ 50119/6 = 8,353 ซึ่งถ้าคิวรีกำหนด WHERE ID = <ค่าใด ๆ> จะให้แถวเป็นจำนวน 8,353 แถว เพื่อพิสูจน์เราลองคิวรีด้วย ID=1 หรือ ID=99
    {xtypo_code}SQL> set autotrace traceonly explain

    SQL> select *
    2 from t
    3 where id = 1;

    -----------------------------------------------------------------------
    | Id | Operation | Name | Rows | Bytes| Cost (%CPU) | Time |
    -----------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | 8353 | 783K | 163 (2) | 00:00:02 |
    |* 1 | TABLE ACCESS FULL| T | 8353 | 783K | 163 (2) | 00:00:02 |
    -----------------------------------------------------------------------


    SQL> select *
    2 from t
    3 where id = 99;

    Execution Plan
    -----------------------------------------------------------------------
    Plan hash value: 1601196873
    -----------------------------------------------------------------------
    | Id | Operation | Name | Rows | Bytes| Cost (%CPU) | Time |
    -----------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | 8353 | 783K | 163 (2) | 00:00:02 |
    |* 1 | TABLE ACCESS FULL| T | 8353 | 783K | 163 (2) | 00:00:02 |
    -----------------------------------------------------------------------


    Optimizer เชื่อว่ามีแถวที่ค่า ID = 1 อยู่ 8,353 แถว ซึ่งมากกว่าประมาณ 10% ของจำนวนแถวของข้อมูลทั้งตาราง มันจึงเลือกที่จะทำการอ่านข้อมูลทั้งตาราง (Table Access Full) แทนที่จะใช้อินเด็กซ์ เช่นเดียวกับเมื่อใช้ WHERE ID = 99 ซึ่งมีอยู่เพียงแถวเดียวในตาราง แต่ Optimizer คิดว่ามีอยู่ 8,353 แถว มันจึงใช้วิธีการเดียวกันคือ Table Access Full คราวนี้ลอง Gather Statistics อีกครั้ง โดยใช้คำสั่งเหมือนกับคำสั่งก่อนหน้า
    SQL> set autotrace off
    SQL>begin
    2 dbms_stats.gather_table_stats
    3 ( user, 'T' );
    4 end;
    5 /
    SQL> select column_name, count(*)
    2 from user_tab_histograms
    3 where table_name = 'T'
    4 and column_name = 'ID'
    5 group by column_name;

    COLUMN_NAME COUNT(*)
    ------------ -----------
    ID 5


    สังเกตว่าการทำ Gather Statistics ครั้งหลังนี้ เราได้มากกว่า 2 Bucket ใน USER_TAB_HISTOGRAMS, DBMS_STATS ที่ใช้ค่าพารามิเตอร์เป็น AUTO จะรวบรวมข้อมูลมากขึ้นกว่าเดิม ถ้าเราคิวรีตาราง SYS.COL_USAGE$ ตอนนี้ เราจะพบว่ามีแถวข้อมูลใหม่เพิ่มขึ้นมาแถวหนึ่งเป็นคอลัมน์และตารางที่เราทำการคิวรีก่อนหน้า ซึ่งหมายความว่ามีคิวรีในระบบที่ใช้เครื่องหมายเท่ากับกับคอลัมน์ดังกล่าว ซึ่งนั่นเป็นความลับที่ทำให้ DBMS_STATS เปลี่ยนวิธีการรวบรวมสถิติ และทำให้เกิดความเปลี่ยนแปลงขึ้นอย่างมากใน Query Plan ต่อไปนี้
    SQL> select *2 from t3 where id = 1;

    Execution Plan
    -----------------------------------------------------------------------
    Plan hash value: 1601196873
    -----------------------------------------------------------------------
    | Id | Operation | Name | Rows | Bytes| Cost (%CPU) | Time |
    -----------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | 10260| 961K | 164 (2) | 00:00:02 |
    |* 1 | TABLE ACCESS FULL| T | 10260| 961K | 164 (2) | 00:00:02 |
    -----------------------------------------------------------------------

    SQL> select *
    2 from t
    3 where id = 99;

    Execution Plan
    -----------------------------------------------------------------------
    Plan hash value: 470836197
    -----------------------------------------------------------------------
    | Id| Operation |Name |Rows|Bytes|Cost(%CPU)|Time |
    -----------------------------------------------------------------------
    | 0 | SELECT STATEMENT | |1 |96 |2 (0) |00:00:01|
    | 1 | TABLE ACCESS BY INDEX ROWID|T |1 |96 |2 (0) |00:00:01|
    |* 2| INDEX RANGE SCAN |T_IDX|1 | |1 (0) |00:00:01|
    -----------------------------------------------------------------------


    สังเกตดูว่า Cardinality (Rows) ต่างกันอย่างไร ระหว่างเดิม 8,353 เปลี่ยนไปเป็น 10,260 (กรณี WHERE ID = 1) หรือ 1 (กรณี WHERE ID = 99) การเปลี่ยนแปลงไปของ Cardinality นี้มีผลต่อผลรวมของ Cost ทั้งหมดของตัว Query Plan ซึ่งทำให้ Optimizer สามารถเลือก Query Plan ได้ดีกว่า

    และนี่คือข้อเท็จจริงที่ทำให้ Query Plan อาจจะเปลี่ยนเมื่อเวลาผ่านไป ซึ่งมีผลมาจากวิธีการรวบรวมสถิติของ Oracle ที่เปลี่ยนแปลงไป เมื่อเรากำหนดค่าของพารามิเตอร์ METHOD_OPT ใน DBMS_STATS ให้เป็น AUTO (AUTO ปกติเป็นค่าดีฟอลต์)
    เรียบเรียงจาก:
    http://www.oracle.com/technology/oramag/oracle/06-may/o36asktom.html

    Sunday, June 27, 2010

    การ Join กับ Query Plan

     การ Join กับ Query Plan
    สอง Query ข้างล่างนี้ให้ผลเหมือนกันแต่ตัวไหนเร็วกว่า?
    1) SELECT distinct D.deptno, D.dname
    FROM dept D, emp E
    WHERE E.deptno = D.deptno
    ORDER BY D.deptno;

    2) SELECT D.deptno, D.dname
    FROM dept D
    WHERE EXISTS
    (
    SELECT 1
    FROM emp E
    WHERE E.deptno = D.deptno))ORDER BY D.deptno;

    ปกติเราต้องใช้การ Join ของสองตารางหรือมากกว่า ก็ต่อเมื่อเราต้องการคอลัมน์จากตารางทุกตารางที่เอามา Join กัน ดังนั้นเราไม่ควรจะ Join ตาราง EMP เข้ากับตาราง DEPT ถ้าในผลลัพธ์ที่เราต้องการไม่มีคอลัมน์ของ EMP อยู่เลย ซึ่งถ้าเป็นกรณีอย่างนี้คุณควรจะใช้ WHERE EXISTS หรือไม่ก็ WHERE IN มากกว่า (Cost-Based Optimizer [CBO] เห็นสองตัวนี้เหมือนกัน) เราอาจจะเขียน Query แบบนี้
    select deptno, dname
    from dept
    where exists
    ( select NULL
    from emp
    where emp.deptno
    = dept.deptno )
    order by deptno;

    หรือแบบนี้
    select deptno, dname
    from dept
    where deptno in
    ( select deptno
    from emp )
    order by deptno;

    ทั้งสองกรณี Optmizer อาจจะใช้ Semi Join (ซึ่งเอา deptno ใน dept ทีละ Row ไปเทียบกับ deptno ใน emp และจะหยุดการเปรียบเทียบของ Row นั้นทันทีที่พบว่ามี) หรือไม่ก็ใช้อินเด็กซ์กับ Nested Loops ในการหาใน EMP ว่ามี Row นั้นอยู่หรือเปล่า
    {xtypo_info}Semi-Join ระหว่างตาราง 2 ตารางจะให้ผลเป็นแถวจากตารางแรกที่สามารถจับคู่กับแถวในตารางที่สองได้หนึ่งหรือมากกว่าหนึ่งคู่ ความแตกต่างระหว่าง Semi-Join และการ Join แบบธรรมดาคือแถวจากในตารางแรกจะถูกดึงมาครั้งเดียว แม้ว่าในตารางที่สองจะมีอยู่สองแถวที่สามารถจับคู่กับตารางแรกได้, Semi-Join จะถูกเขียนโดยใช้รูปแบบ EXISTS หรือ IN{/xtypo_info}

    ตัวอย่างต่อไปนี้แสดงว่าโดยทั่วไปแล้วจะเกิดอะไรขึ้น เมื่อ Optimizer ตรวจพบว่า DEPT มีขนาดเล็ก และ EMP มีขนาดใหญ่ โดยมันจะสแกนแต่ละ Row ของ DEPT และจะเข้าไปหา DEPTNO (โดยใช้อินเด็กซ์) ในตาราง EMP ซึ่งมีขนาดใหญ่ ในทางกลับกันถ้า DEPT มีขนาดใหญ่ Optimizer ก็จะทำ Semi Join ทั้งก้อนโดยไม่ใช้ Index เราจะใช้ก๊อปปี้ของตาราง EMP และ DEPT ในการทดสอบดังนี้
    SQL> create table emp as
    2 select * from scott.emp;
    Table created.

    SQL> create table dept as
    2 select * from scott.dept;
    Table created.

    SQL> create index emp_deptno_idx
    2 on emp(deptno);
    Index created.

    จากนั้นเราก็เมคค่าสถิติของตารางขึ้นมาเอง โดยใช้ SET_TABLE_STATS ใน Package DBMS_STATS เพื่อที่จะทำให้ EMP ดูเหมือนตารางที่ใหญ่ มีขนาด 1,000,000 แถว และให้ DEPT ดูเหมือนตารางที่มีขนาดเล็กเพียง 100 แถว นอกจากนั้นเราจะบอก Optimizer ว่ามีอินเด็กซ์บนคอลัมน์ EMP.DEPTNO แต่เนื่องจาก Oracle10g จะทำการคำนวณสถิติของอินเด็กซ์ตั้งแต่ตอนสร้างอินเด็กซ์ เราจึงต้องเอาสถิติเดิมของอินเด็กซ์ออกก่อน และใส่ (เมค) สถิติใหม่เข้าไปให้สอดคล้องกับสถิติของตารางที่เราเมคเอาไว้แต่แรก
    SQL> begin
    2 dbms_stats.set_table_stats
    3 ( user,
    4 'EMP',
    5 numrows => 1000000,
    6 numblks => 100000 );

    7 dbms_stats.delete_index_stats
    8 ( user,
    9 'EMP_DEPTNO_IDX' );

    10 dbms_stats.set_index_stats
    11 ( user,
    12 'EMP_DEPTNO_IDX',
    13 numrows => 1000000,
    14 numdist => 10000,
    15 numlblks =>10000 );

    16 dbms_stats.set_column_stats
    17 ( user,
    18 'EMP',
    19 'DEPTNO',
    20 DISTCNT => 10000 );

    21 dbms_stats.set_table_stats
    22 ( user,
    23 'DEPT',
    24 numrows=> 100,
    25 numblks => 100 );
    26 end;
    27 /

    ตอนนี้เราก็พร้อมที่จะจับตาดูการตัดสินใจของ Optimizer เราใช้ AUTOTRACE เพื่อที่จะดู Query Plan ที่สร้างขึ้นจากคิวรีทั้งสองตัว เราแสดงคิวรีทั้งสองเพื่อที่จะแสดงให้เห็นว่าเมื่อ Developer ใช้ CBO (Cost-Based Optimizer) เขาไม่จำเป็นต้องตัดสินใจว่าจะเขียนอย่างไรให้ดีที่สุด (คือเพียงในระดับที่ยอมรับได้ก็เพียงพอ ส่วนที่เหลือให้ Optimizer ช่วยจัดการ) -- Optimizer จะเข้าใจรูปแบบของคิวรีที่เขียนขึ้นและเลือกวิธีการนำข้อมูลออกมาที่ดีที่สุด ซึ่งแตกต่างโดยสิ้นเชิงกับวิธีแบบ Rule-Based Optimizer (RBO) ซึ่งเป็น Optimizer แบบเก่าที่เราจะต้องเป็นผู้กำหนดแปลนในการรันคำสั่งเอาเอง ซึ่งจะมองเห็น WHERE IN กับ WHERE EXISTS ว่าแตกต่างกันโดยสิ้นเชิง

    เมื่อเราใช้ CBO กับตาราง EMP ซึ่งมีขนาดใหญ่ และ DEPT ซึ่งมีขนาดเล็ก, Oracle อาจจะใช้ Query Plan ดังแสดงข้างล่างนี้ คืออ่านทุก ๆ แถวของ DEPT และทำสแกนอินเด็กซ์เฉพาะช่วง (Index Range Scan) บนตาราง EMP เพื่อหา DEPTNO ที่ตรงกับใน DEPT และเนื่องจากการสแกนจากในอินเด็กซ์เพื่อเปรีบบเทียบนี้มีแค่ 100 ครั้ง (เนื่องจากเราทำให้ Optimizer เห็นว่ามีข้อมูลใน DEPT แค่ 100 แถว เมื่อนำ 100 แถวเข้าไปหาในอินเด็กซ์จึงต้องทำทั้งหมด 100 ครั้ง) และเนื่องจาก (เราทำให้ Optimizer เห็นว่า) ตาราง EMP มีขนาดใหญ่ Optimizer จึงใช้ Query Plan นี้ทั้งกับกรณี WHERE IN และ WHERE EXISTS
    SQL> set autotrace traceonly explain
    SQL> select deptno, dname
    2 from dept where deptno in
    3 ( select deptno
    4 from emp )
    5 order by deptno;

    Execution Plan
    -----------------------------------------------------------
    Plan hash value: 3383088615

    -----------------------------------------------------------
     
    | Id | Operation       | Name          | Rows  | Bytes |
    -----------------------------------------------------------
    | 0  |SELECT STATEMENT |               | 100   | 3500  |
    | 1  |SORT ORDER BY    |               | 100   | 3500  |
    | 2  |NESTED LOOPS SEMI|               | 100   | 3500  |
    | 3  |TABLE ACCESS FULL| DEPT          | 100   | 2200  |
    |* 4 |INDEX RANGE SCAN | EMP_DEPTNO_IDX| 1000K | 12M   |
    -----------------------------------------------------------

    Predicate Information (identified by operation id):
     
    -----------------------------------------------------------

    4 - access("DEPTNO"="DEPTNO")
     

    SQL> select deptno, dname
     
    2 from dept where exists
    3 ( select null
    4 from emp
    5 where emp.deptno =
    6 dept.deptno )
    7 order by deptno;

    Execution Plan
     
    ------------------------------------------------------------
    Plan hash value: 3383088615

    ------------------------------------------------------------
     
    | Id | Operation        | Name          | Rows  | Bytes |
    ------------------------------------------------------------
    | 0  | SELECT STATEMENT |               | 100   | 3500  |
    | 1  | SORT ORDER BY    |               | 100   | 3500  |
    | 2  | NESTED LOOPS SEMI|               | 100   | 3500  |
    | 3  | TABLE ACCESS FULL| DEPT          | 100   | 2200  |
    |* 4 | INDEX RANGE SCAN | EMP_DEPTNO_IDX| 1000K | 12M   |
    ------------------------------------------------------------

    Predicate Information (identified by operation id):
     
    ------------------------------------------------------------

    4 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")



    ต่อมาหากจำนวนของข้อมูลของ DEPT เพิ่มขึ้น และ Optimizer เห็นว่าจำนวนครั้งที่มันจะต้องเข้าไปตรวจข้อมูลจากอินเด็กซ์ของตาราง EMP มากขึ้นมาก ๆ แปลนที่ Optimizer จะใช้ในการดึงข้อมูลก็จะเปลี่ยนไป จากเดิมที่ใช้ Nested Loops ร่วมกับ Index Range Scan ก็จะเปลี่ยนไปเป็นการทำงานกับข้อมูลทั้งก้อน (แทนการใช้อินเด็กซ์) เพื่อที่จะให้เห็นภาพเราต้องบอก Optimizer ว่าตอนนี้ DEPT มีขนาดใหญ่ขึ้นมาก
    {xtypo_code}SQL> begin
    2 dbms_stats.set_table_stats
    3 ( user,
    4 'DEPT',
    5 numrows=> 100000,
    6 numblks => 10000 );
    7 end;
    8 /

    จากนั้นก็รันคิวรีใหม่อีกทีหนึ่ง และดู Query Plan ที่เกิดขึ้นดังนี้
    SQL> set autotrace traceonly explain
    SQL> select deptno, dname
    2 from dept where deptno in
    3 ( select deptno
    4 from emp )
    5 order by deptno;

    Execution Plan
    -----------------------------------------------------------------
    Plan hash value: 3127359958
    -----------------------------------------------------------------
    | Id | Operation           | Name           | Rows | Bytes |
    -----------------------------------------------------------------
    | 0  | SELECT STATEMENT    |                | 100K | 3417K |
    | 1  | SORT ORDER BY       |                | 100K | 3417K |
    |* 2 | HASH JOIN SEMI      |                | 100K | 3417K |
    | 3  | TABLE ACCESS FULL   | DEPT           | 100K | 2148K |
    | 4  | INDEX FAST FULL SCAN| EMP_DEPTNO_IDX | 1000K| 12M   |
    -----------------------------------------------------------------
    Predicate Information (identified by operation id):
    -----------------------------------------------------------------
    2 - access("DEPTNO"="DEPTNO")
    SQL> select deptno, dname
    2 from dept where exists
    3 ( select null
    4 from emp
    5 where emp.deptno =
    6 dept.deptno )
    7 order by deptno;

    Execution Plan
     
    ---------------------------------------------------------------
    Plan hash value: 3127359958

    ---------------------------------------------------------------
     
    | Id | Operation           | Name          | Rows | Bytes |
    ---------------------------------------------------------------
    | 0  | SELECT STATEMENT    |               | 100K | 3417K |
    | 1  | SORT ORDER BY       |               | 100K | 3417K |
    |* 2 | HASH JOIN SEMI      |               | 100K | 3417K |
    | 3  | TABLE ACCESS FULL   | DEPT          | 100K | 2148K |
    | 4  | INDEX FAST FULL SCAN| EMP_DEPTNO_IDX| 1000K| 12M   |
    ---------------------------------------------------------------

    Predicate Information (identified by operation id):
     
    ---------------------------------------------------------------

    2 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")


    เราจะเห็นว่าขณะนี้ในคิวรีทั้งสองกรณี Optimizer เลือกใช้ (Index) Full Scan และ Hash Semi Join แทนการเข้าไปค้นใน Index ดังทีแรก เนื่องมาจากว่าไม่งั้นมันก็ต้องเข้าไปค้นในอินเด็กซ์เป็นจำนวน 100,000 ครั้ง
    เรียบเรียงจาก:
    http://www.oracle.com/technology/oramag/oracle/06-may/o36asktom.html