<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    飛艷小屋

    程序--人生--哲學___________________歡迎艷兒的加入

    BlogJava 首頁 新隨筆 聯系 聚合 管理
      52 Posts :: 175 Stories :: 107 Comments :: 0 Trackbacks

    ORACLE Advanced SQL
    這篇文檔對深入研究Oracle有很大的用處。下面分別從以下幾個方面介紹。
    Transaction Management,joins,Subquerys,Optimizer,Indexs, Enhancement to other SQL operations
    如果您身邊就有Oracle系統,那么您也可以通過運行本篇文章的例子來做一些試驗。
    l 事務處理(Transaction Management):
    事務的四大特性原子性、隔離性、獨立性,持續性。僅僅通過字面理解理解事務處理可能不是太直觀。那么,我們可以借用Jim Gray的描述來理解——“ 事務概念是分布式計算的一個重要抽象。在某一個層次實現了事務,那么所有跟高層次上會有一個簡化的失敗語義(要么全做,要么全不做),并且錯誤處理也會簡單得多。 ”
    在Oracle中通過設置ISOLATION LEVEL來避免幾乎所有數據庫都可能遇到的問題——臟讀(一個事務讀取了另一個事務寫的但是尚未提交的數據)、不一致分析(一個事務前后讀取了不同的數據)和幻象數據讀取(例如,我要執行一個事務查詢,該事務查詢的任務是查詢13:00這個時間點的數據,但是這個事務執行時間可能需要一個小時,但是在這期間由于還有正常的數據操作。所以,當我在執行完事務查詢時可能得到的數據就是14: 00的數據了,我們稱這種現象為幻象讀取)。
    我們知道標準的SQL99提出的事務隔離級別有四種,分別是READ UNCOMMITED、 READ COMMITED、REPEATABLE READ、SERIALIZABLE。ORACLE分別實現了READ COMMITED和 SERIALIZABLE。從這一點當中我們可以看出,ORACLE默認是避免臟讀的(當然這也不一定如果在執行完DML語句完以后如果沒有做 COMMIT操作可能也會出現臟讀的情況。但是,我還沒有遇到這種情況,讀者可以自己試驗。)例句  SET TRANSACTION ISOLATION LEVEL [READ COMMITED| SERIALIZABLE]。 READ COMMITED可以避免臟讀,這也是ORACLE的默認選項。但是,它不可避免我們提出的不一致分析和幻象讀取的問題。那么我們可以將其隔離級別設置為SERIALIZABLE這樣我們就可以避免不一致分析和幻象讀取的問題。那么,既然SERIALIZABLE級別這么好,那么我們是不是任何事務都要設置呢?答案是否定的,我們知道世界萬物都是有其有利的一面就一定有其不利的一面。那么它的不利一面我們可以通過分析SERIALIZABLE的實現機制來看看。
     
    從圖中可以看出我們要實現SERIALIZABLE需要在系統中建立一個UNDO表空間來存放已經過時的數據。我們可以根據我們最大執行事務的執行時間來估算一個UNDO段的大小。顯然,我們要是實現SERIALIZABLE就必須要另外的空間來建立UNDO表空間。也就是說我們要實現SERIALIZABLE是通過空間來換取避免不一致分析和幻象讀取的。
    l Joins操作
    在敘述下面的內容之前先介紹一下我的機器環境:
    硬件環境:P4 2.8G,MEMORY 526M,80G硬盤。
    軟件環境:Windows 2000,ORACLE 9.2.0.1
    首先您要建立一個有足夠量數據的表,我建立一個有100萬行以上的數據的表
    建表sql語句如下:
    DROP SEQUENCE SEQ_EMP
    /
    CREATE SEQUENCE SEQ_EMP START WITH 1000000
    /
    DROP TABLE EMPLOYEE
    /
    CREATE TABLE EMPLOYEE
    (EMPNO NUMBER(8),
     ENAME VARCHAR2(10),
     SAL   NUMBER(5),
     COMM  NUMBER(5),
     DEGREE NUMBER(1),
     HIREDATE DATE,
     DEPTNO NUMBER(2),
     ID_NO  VARCHAR2(18),
     BIRTHDAY DATE,
     CONSTRAINT PK_EMPLOYEE PRIMARY KEY(EMPNO)
    )
    /
    CREATE OR REPLACE TRIGGER TRI_INS_EMPLOYEE 
     BEFORE INSERT ON EMPLOYEE
     FOR EACH ROW
    DECLARE
     V_EN EMPLOYEE.EMPNO%TYPE;
    BEGIN
     V_EN:=:NEW.EMPNO;
     
      :NEW.COMM:=CEIL(:NEW.EMPNO/500000)*1000;
      :NEW.SAL:=(4-MOD(:NEW.EMPNO,4))*1000;
      :NEW.DEGREE:=CASE WHEN MOD(:NEW.EMPNO,4)=0 THEN NULL 
                       WHEN MOD(:NEW.EMPNO,10)<7 THEN 1
                       WHEN MOD(:NEW.EMPNO,10)<9 THEN 2
                       ELSE 3
                  END;
      :NEW.DEPTNO:=CASE WHEN :NEW.EMPNO<1000000 THEN 10
                        WHEN :NEW.EMPNO<1500000 THEN 20
                        WHEN :NEW.EMPNO<2000000 THEN 30
                        ELSE 40
                   END;
      :NEW.ID_NO:=REPLACE('21010119'||(7+MOD(V_EN,3))||MOD(V_EN,10)||TO_CHAR(1+MOD(V_EN,12),'00')||TO_CHAR(MOD(V_EN,20)+1,'00')||0||TO_CHAR(MOD(V_EN,100),'00')||MOD(V_EN,2),' ',NULL);

      :NEW.ID_NO:=REPLACE(:NEW.ID_NO,' ',NULL);

     :NEW.BIRTHDAY:=TO_DATE(SUBSTR(:NEW.ID_NO,7,8),'YYYYMMDD');
    END;
    /
    INSERT INTO EMPLOYEE (EMPNO)
     VALUES(SEQ_EMP.NEXTVAL)
    /
    /
    INSERT INTO EMPLOYEE (EMPNO) (SELECT SEQ_EMP.NEXTVAL FROM EMPLOYEE)
    /
        在做好以上準備以后我們就可以進行我們以后的實驗了。
    在ORACLE中包括很多中連接方式EQUIJOINS、 SELF JOINS、 CARTESIAN PRODUCTS、 INNER JOINS、 OUTER JOINS、 ANTIJOINS, SEMIJOINS。
    由于我的個人水平有限我不能全部介紹,我只能將自己理解的內容介紹個大家。
    下面就僅僅介紹有關反連接的問題。
    反連接(ANTIJOINS)通過 SET AUTOTRACE TRACE打開跟蹤執行策略我們分別比較以下的語句。
    ============================================================
    SELECT ENAME FROM EMP WHERE DEPTNO NOT IN(
       SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');

    SELECT ENAME FROM EMP WHERE NOT EXISTS(
       SELECT 1 FROM DEPT WHERE DEPT.DEPTNO=EMP.DEPTNO AND LOC='NEW YORK');
    我們可以發現,運用NOT IN 要比 NOT EXISTS要快一些但是有個比較大的問題,就是在上面的實驗中我們假設EMP表和DEPT表中沒有字段內容為NULL的數據。如果我們要查詢的表中有為NULL的數據的時候,那么我們在運用NOT IN操作會發現查出的數據是不準確的情況。結論:在您為選擇 NOT IN 和 NOT EXISTS時而苦惱時請優先選擇運用NOT EXISTS語句。
    接下來是有關JOIN的一些實驗。
    下面我們做如下試驗
    比較JOIN與IN & 子查詢以及EXISTS & 子查詢 它們三者之間的執行效率

    --12.04秒
    SELECT * FROM EMPLOYEE A JOIN DEPT B ON A.DEPTNO=B.DEPTNO WHERE B.LOC='NEW YORK';
    --4.02秒
    SELECT * FROM EMPLOYEE WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');
    --4.02秒
    SELECT * FROM EMPLOYEE WHERE EXISTS (SELECT 1 FROM DEPT WHERE EMPLOYEE.DEPTNO=
    DEPT.DEPTNO AND LOC='NEW YORK');

    通過上面的實驗我們可以得出運用 IN或者 EXISTS操作的效率要高于運用JOIN操作的語句。我們知道ORACLE在執行用戶的查詢請求是 ORACLE會選擇一些查詢策略來完成操作,但是有時候ORACLE的選擇是很愚蠢的。比如,我要做一個簡單的連接查詢的操作 SELECT * FROM EMPLOYEE A JOIN EMP B ON A.EMPNO=B.EMPNO;通過跟蹤執行策略我們會發現 ORACLE可能選擇一個很慢的策略。那么,遇到這種情況該如何處理呢?我們可以通過兩種方式來處理。第一,我們可以為該查詢添加注釋讓它執行我們要求的策略。比如我們可以這么寫SELECT /*+ use_rule…(EMPLOYEE EMP)  */* FROM EMPLOYEE A JOIN EMP B ON A.EMPNO=B.EMPNO;(use_rule您可以選擇您需要的策略。例如HASH JOIN等其他的執行策略)。例如,我們為上面這個查詢語句選擇下面兩種策略:
    1. SELECT /*+USE_HASH(EMPLOYEE,DEPT)*/* FROM EMPLOYEE,DEPT WHERE EMPLOYEE.DEPTNO=DEPT.DEPTNO;
    2. SELECT /*+USE_MERGE(EMP,DEPT)*/* FROM EMP JOIN DEPT ON EMP.DEPTNO=DEPT.DEPTNO;
    第二,就是,為該表添加統計資料分析信息ANALYZE TABLE EMPLOYEE COMPUTE STATISTICS;這樣我們在對該表查詢時ORACLE就可以選擇一個較優的執行策略了。
    通過以上的實驗我們可以得出以下結論:ORACLE系統在執行DML語句的時候可能不會選擇其最優的執行策略,這需要我們通過為DML語句添加注釋強迫 ORACLE選擇一個我們人為指定的執行策略或者通過為表、試圖等添加統計資料分析信息來使ORACLE在執行DML時選擇較優的執行策略。
    l 子查詢(Subquery)
    這部分內容我們將會做一系列的實驗,最后我們從實驗的結果當中得出使用子查詢的情況。我并不是要介紹如何使用子查詢,而是通過使用子查詢來分析ORACLE的執行策略等信息。
    實驗1:比較JOIN、IN以及EXISTS三種執行方式的執行時間
    --12.04秒
    SELECT * FROM EMPLOYEE A JOIN DEPT B ON A.DEPTNO=B.DEPTNO WHERE B.LOC='NEW YORK';
    --4.02秒
    SELECT * FROM EMPLOYEE WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');
    --4.02秒
    SELECT * FROM EMPLOYEE WHERE EXISTS (SELECT 1 FROM DEPT WHERE EMPLOYEE.DEPTNO=
    DEPT.DEPTNO AND LOC='NEW YORK');

    通過實驗1我們得出結論:使用EXISTS、IN語句要比JOIN語句執行效率高。


    實驗2:表較幾個語句的查詢效率

    --27.07秒
    SELECT * FROM EMPLOYEE 
    WHERE SAL>(SELECT AVG(SAL) FROM EMPLOYEE);

    --27.03秒
    SELECT * FROM EMPLOYEE A 
    WHERE SAL>
    (SELECT AVG(SAL) FROM EMPLOYEE B WHERE A.DEPTNO=B.DEPTNO);

    --39秒
    SELECT * FROM EMPLOYEE A,
    (SELECT DEPTNO,AVG(SAL) AS SAL1 
    FROM EMPLOYEE GROUP BY DEPTNO) B
    WHERE A.DEPTNO=B.DEPTNO AND 
    A.SAL>B.SAL1 WHERE SAL>(SELECT AVG(SAL)
     FROM EMPLOYEE B WHERE A.DEPTNO=B.DEPTNO);

    --22.05秒
    SELECT YY.EMPNO,YY.ENAME,YY.DEPTNO,YY.SAL,
    YY.SAL-XX.SAL1 AS NEWSAL FROM EMPLOYEE YY,
    (SELECT DEPTNO,AVG(SAL) AS SAL1 FROM EMPLOYEE 
    GROUP BY DEPTNO) XX 
    WHERE YY.DEPTNO=XX.DEPTNO AND YY.SAL>XX.SAL1;

    --26.06秒
    SELECT YY.EMPNO,YY.ENAME,YY.DEPTNO,YY.SAL,
    YY.SAL-XX.SAL1 AS NEWSAL,XX.SAL1,XX.SAL2 
    FROM EMPLOYEE YY,
    (SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 FROM EMPLOYEE GROUP BY DEPTNO) XX 
    WHERE YY.DEPTNO=XX.DEPTNO AND YY.SAL>XX.SAL1;

    通過實驗2我們可以得出結論:
    1. 在執行子查詢盡可能將語句向連接的方式上靠。
    2. 在子查詢中可以將相關子查詢轉為連接的形式。
    3. 在做子查詢時select * 要比select 列名 的語句慢得多。
    另外,在ORACLE中我們也可以運用WITH語句代替子查詢以提高語句的可讀性。下面是運用with語句的例子。
    實驗3:運用WITH語句
    WITH 
      SUB_TABLE AS (
        SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 FROM EMPLOYEE GROUP BY DEPTNO)
    SELECT A.EMPNO,A.ENAME,E.DEPTNO,A.SAL,A.SAL-SUB_TABLE.SAL11,
            SUB_TABLE.SAL1,SUB_TABLE.SAL2
     FROM EMPLOYEE A,SUB_TABLE WHERE 
        A.DEPTNO=SUB_TABLE.DEPTNO AND A.SAL>SUB_TABLE.SAL1;

    WITH 
    SUB_TABLE AS (
      SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 
    FROM EMPLOYEE GROUP BY DEPTNO)
    SELECT *     
    FROM EMPLOYEE,SUB_TABLE WHERE 
    EMPLOYEE.DEPTNO=SUB_TABLE.DEPTNO AND EMPLOYEE.SAL>SUB_TABLE.SAL1;
    l 索引(Index)
    我們知道索引的使用可以提高查詢速度,這是我以前對索引的理解。但是使用索引也有副作用。我們知道在ORACEL中查詢一條記錄時如果沒有索引的情況下,它的執行方式如下所示
     
    如果我要做查詢 select C2 from 表 where C2=6 那么在ORACLE中系統會從最后一條掃描一直將表整個掃描一遍以后,這個查詢動作才算完成。從中我們可以看出沒有索引的表的記錄是無序存放的。相反如果我們對這個表在列C2建立一個索引以后它的查詢執行如下所示:
     
    我們可以看到在查詢是系統如果選用該索引的話那么ORACLE將會查找一些有序的數據,那么我們的查詢速度將會大大地提高。
    上面我們描述的是查詢一列數據時的情況,那么如果查詢所有數據呢,請看下圖所示
     
    如果我們帶上索引查詢,ORACLE首先會找到索引然后找到在基表中記錄的位置。顯然這樣比直接在表中查詢要慢。但是這個結論對不對呢?下面將會做一些實驗說明這個問題。
    在進行以下的實驗值前首先,您需要作如下準備:
    在EMPLOYEE表的SAL列上建立一個索引
    CREATE INDEX IND_EMPLOYEE_SAL ON EMPLOYEE(SAL);
    在EMPLOYEE表的SAL,DEGREE兩個列上建立一個聯合索引
    CREATE INDEX IND_EMPLOYEE_SALandDEGREE 
    ON EMPLOYEE(SAL,DEGREE);
    實驗1:使用索引與不使用索引

    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
    * FROM 
       EMPLOYEE WHERE SAL=1000;
    --13.03秒
    SELECT /*+NO_INDEX(EMPLOYEE)*/
    * FROM 
       EMPLOYEE WHERE SAL=1000;
    利用索引查詢的時間的數據由于我的粗心大意弄丟了,但是我記得運用索引的查詢要比沒有運用索引的查詢要慢一些。

    實驗2:單列索引與多列索引之間的區別
    //////////////////////////////////////////////////
    ////單列索引的情況
    --13.04秒 USE INDEX IND_EMPLOYEE_SAL
    SELECT * FROM EMPLOYEE WHERE SAL=1000;

    --13.04秒
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000;

    --19秒
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000;

    //////////////////////////////////////////////////
    ////單列索引的情況
    --22秒   USE TABLE ACCESS 
    SELECT * FROM EMPLOYEE WHERE DEGREE=1;




    --22秒     USE TABLE ACCESS 
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
    * FROM 
        EMPLOYEE WHERE DEGREE=1;

    --29秒
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
    * FROM 
        EMPLOYEE WHERE DEGREE=1;

    //////////////////////////////////////////////////
    ////多列索引的情況
    --8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
    SELECT * FROM EMPLOYEE WHERE SAL=1000 AND DEGREE=1;

    --9秒 USE INDEX IND_EMPLOYEE_SAL
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000 AND DEGREE=1;

    --8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000 AND DEGREE=1;

    //////////////////////////////////////////////////
    ////多列索引的情況
    --8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
    SELECT * FROM EMPLOYEE WHERE DEGREE=1 AND SAL=1000;

    --9秒 USE INDEX IND_EMPLOYEE_SAL
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
    * FROM 
        EMPLOYEE WHERE DEGREE=1 AND SAL=1000;



    --8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
    * FROM 
        EMPLOYEE WHERE DEGREE=1 AND SAL=1000;

    //////////////////////////////////////////////////
    ////多列索引的情況
    --3.4秒 USE INDEX IND_EMPLOYEE_SAL
    SELECT * FROM EMPLOYEE WHERE SAL=1000 AND COMM=2000;

    --3.4秒 USE INDEX IND_EMPLOYEE_SAL
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000 AND COMM=2000;

    --8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000 AND COMM=2000;

    實驗3:CLUSTER
    CREATE CLUSTER DD_DDMX (ID NUMBER(5)) 
    TABLESPACE users ;

    CREATE TABLE DD
    (ID NUMBER(5) PRIMARY KEY,
     DDTIME DATE)
    CLUSTER DD_DDMX(ID);

    CREATE TABLE DDMX
    (ID NUMBER(5),
     PROID VARCHAR2(20),
     PRO VARCHAR2(30))
    CLUSTER DD_DDMX(ID);

    CREATE INDEX DD_DDMX_index 
      ON CLUSTER DD_DDMX
      TABLESPACE indx;
    實驗4:BITMAP INDEX
    CREATE BITMAP INDEX INDBIT_EMPLOYEE_SAL ON EMPLOYEE(SAL)
    TABLESPACE INDX;

    --13.04秒
    SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000;

    --12.07秒  USE INDBIT_EMPLOYEE_SAL
    SELECT /*+INDEX (EMPLOYEE INDBIT_EMPLOYEE_SAL)*/
    * FROM 
        EMPLOYEE WHERE SAL=1000;
    實驗5:分區索引
    分區索引的原理如下所示:

     
    我們知道在磁道在磁盤上尋址的I/O操作的開銷是相當大的,如果我們建立了分區索引將其索引放在不同的磁盤上那么可以大大節省I/0開銷提高我們的查詢速度。


    ALTER TABLE EMP
      ADD CONSTRAINT PK_EMP
    PRIMARY KEY(EMPNO) USEING INDEX TABLESPACE INDX;
    ALTER TABLE EMPLOYEE
      DROP CONSTRAINT PK_EMPLOYEE;

    ALTER TABLE EMPLOYEE
      ADD CONSTRAINT PK_EMPLOYEE
    PRIMARY KEY(EMPNO) USING INDEX TABLESPACE INDX;

    CREATE INDEX IND_EMPLOYEE_BIRTHDAY 
      ON EMPLOYEE(BIRTHDAY)
    GLOBAL PARTITION BY RANGE(BIRTHDAY)
    (PARTITION P1 VALUES LESS THAN (DATE '1980-01-01') TABLESPACE USERS,
     PARTITION P2 VALUES LESS THAN (DATE '1990-01-01') TABLESPACE INDX,
     PARTITION P3 VALUES LESS THAN (DATE '2000-01-01') TABLESPACE USERS,
     PARTITION P4 VALUES LESS THAN(MAXVALUE) TABLESPACE INDX);

    CREATE TABLE wage
        ( empno              NUMBER, 
          year_month     INT NOT NULL,
          opdate              DATE)
      PARTITION BY RANGE (year_month)
        ( PARTITION wage_q1 VALUES LESS THAN (199701) 
            TABLESPACE users,
          PARTITION wage_q2 VALUES LESS THAN (199702) 
            TABLESPACE users,
          PARTITION wage_q3 VALUES LESS THAN (199703) 
            TABLESPACE users,
          PARTITION sales_q4 VALUES LESS THAN (199704) 
            TABLESPACE users); 
    -- Local Partitioned Index
    CREATE INDEX IND_WAGE(year_month)
    LOCAL
     (PARTITION P1,
      PARTITION P2,
      PARTITION P3,
      PARTITION P4);

    以上是我們關于索引的一些實驗信息,通過該實驗我們可以得出以下結論:
    1. 索引是為了提高查詢速度,排序數據。
    2. 索引不見得總能提高速度。
    3. 查詢結果占總記錄數越少越可以用索引。
    4. 分區索引 將數據分散到多個物理位置上來提高其IO的功能。
    5. 全表掃描有時性能也不錯。
    6. 在統計資料不完整時未必選擇正確的索引。
    7. 有時候添加索引可能會降低速度。
    8. 索引適合于結果行占所有行的比很小的時候運用 經驗值為比為5%左右為好,但是還可能還有許多其他情況影響查詢速度。
    9. 值是唯一的時候運用索引可以提高查詢速度。
    10. 當統計資料不完整的時候,查詢速度會很慢。可以通過對索引進行統計分析來調整。
    11. where條件中列的順序不影響其查詢速度。
    12. 多列索引中的后面的列最好不要運用索引。
    13. 多列索引中其列的順序是不同的。不同的順序可能造成查詢速度不一樣。
    14. 多列索引中第一列是有順序的,但是其后面的列是沒有順序。
    15. where條件查詢的列中只要有索引,那么oracle會選擇運用索引。
    16. 當查詢的結果大于所有行的記錄數的10%時,那么最好不要運用索引。
    17. 小表用索引反而會降低速度。
    18. 索引需要DBA經常刪除重新建立,因為索引結構變化過多可能造成以后的查詢變慢。
    19. DML語句會影響索引結構大的變化,所以經常作DML語句的表可以考慮不用索引。
    20. CLUSTER索引僅僅用于只讀的的表多用于cluster表。
    21. 維圖索引多用于行多但是值的類型很少的情況。
    22. 表和索引要放在兩個不同的表空間上。
    l Enhancement to other SQL operations
    下面的內容是介紹一些有關ORACLE技術的一些比較雜的內容。
    一、 層次查詢
    在同一個表中的不同記錄有著直接或者間接的關系。
    例如,我們查詢EMP表的信息如下:
      從圖中我們可以看到ENAME是’SMITH’的領導是編號為7902的人。而7902的領導是編號為7566的人。7566的領導是編號為7839的人。那么這樣的關系我們就可以通過運用層次查詢就可以查詢出來。
    層次查詢實驗
    SELECT * FROM EMP
    CONNECT BY PRIOR MGR=EMPNO
      START WITH ENAME='SMITH';

    SELECT * FROM EMP
    CONNECT BY PRIOR EMPNO=MGR
      START WITH ENAME='SMITH';

    SELECT LEVEL,ENAME FROM EMP
    CONNECT BY PRIOR EMPNO=MGR
      START WITH ENAME='SMITH';

    SELECT MID,PARENTID,AMOUNT,SYS_CONNECT_BY_PATH(MID,'/') PATH
    FROM MATERIAL
       WHERE STOCK=1
            CONNECT BY PRIOR MID=PARENTID
                              START WITH MID='1001';
    二、 分組查詢
    就是我們平時運用的group語句.
    分組查詢實驗
    SELECT DEPTNO,JOB,COUNT(*)
      FROM EMP
       GROUP BY ROLLUP(DEPTNO,JOB);

    SELECT DEPTNO,JOB,COUNT(*)
      FROM EMP
       GROUP BY cube(DEPTNO,JOB);

    SELECT DEPTNO,JOB,TO_CHAR(HIREDATE,'YYYY'),COUNT(*)
      FROM EMP
       GROUP BY GROUPING SETS((DEPTNO,JOB),
    (DEPTNO,TO_CHAR(HIREDATE,'YYYY')),());
    三、 并行執行的SQL語句
    并行執行的SQL語句實驗

    --19.09秒
    ALTER TABLE EMPLOYEE PARALLEL (DEGREE 1);
    SELECT * FROM EMPLOYEE WHERE SAL=1000;

    --18秒
    ALTER TABLE EMPLOYEE PARALLEL (DEGREE 2);
    SELECT * FROM EMPLOYEE WHERE SAL=1000;

    --19秒
    ALTER TABLE EMPLOYEE PARALLEL (DEGREE 4);
    SELECT * FROM EMPLOYEE WHERE SAL=1000;

    --20.3秒
    ALTER TABLE EMPLOYEE PARALLEL (DEGREE 6);
    SELECT * FROM EMPLOYEE WHERE SAL=1000;

    --19秒
    ALTER TABLE EMPLOYEE PARALLEL (DEGREE 10);
    SELECT * FROM EMPLOYEE WHERE SAL=1000;
    四、 實體化視圖
    在進行以下的實驗之前,我們需要作如下準備:
    建立一個普通視圖:
    CREATE VIEW V_EMPLOYEE
    AS SELECT SUM(SAL) AS C FROM EMPLOYEE;
    建立實體化視圖:
    CREATE MATERIALIZED VIEW 名字 AS 查詢;

    CREATE MATERIALIZED VIEW V_M_EMPLOYEE 
            AS SELECT SUM(COMM) AS C FROM EMPLOYEE;
    實體化視圖與普通視圖的比較

    --20.04秒
    SELECT * FROM V_EMP;

    --0.01秒
    SELECT * FROM V_M_EMPLOYEE;

    通過以上的實驗,我們可以得出結論:實體化視圖不包含新的數據,查詢速度很快。
    如果需要實體化視圖包含新的數據我們可以通過
    手工刷新:
       EXEC DBMS_MVIEW.REFRESH('V_M_EMPLOYEE','CF');  CF -- 完全快速刷新
            自動刷新:
    //每個表可以創建一個實體化視圖日志。
    CREATE MATERIALIZED VIEW LOG ON 表名
    WITH(列名列表),
        ROWID INCLUDING NEW VALUES;
           //創建自動刷新的實體化視圖
            CREATE MATERIALIZED VIEW 名字
            BUILD IMMEDIATE
    〔REFRESH FAST||REFRESH COMPLETE〕 ON COMMIT 
    --REFRESH FAST 僅僅支持Insert語句的刷新
                 AS ...
    運用REFRESH COMPLETE 適合于update,delete操作較少的表。并且試驗發現運用
    REFRESH COMPLETE時COMMIT操作會很慢!
    五、 查詢重寫技術(QUERY REWRITE)
    表面上看是在查詢表,但是oracle實際上是去查詢實體化視圖的技術。
    在下面的試驗中我們也可以看出實際上無論是查詢表還是視圖oracle都會轉向查詢實體化視圖 MV_EMP。
    對于反復執行的匯總查詢存放起來、節省查詢時間,多用于匯總的計算。
          
    //創建查詢重寫技術
            CREATE MATERIALIZED VIEW 名字
                   BUILD IMMEDIATE
        〔REFRESH FAST||REFRESH COMPLETE〕 ON COMMIT 
    --REFRESH FAST 僅僅支持Insert語句的刷新
            ENABLE QUERY REWRITE
                 AS ...
    運用查詢重寫技術的例子
    CREATE MATERIALIZED VIEW MV_EMP
        BUILD IMMEDIATE
            REFRESH FAST ON COMMIT
               ENABLE QUERY REWRITE
                    AS SELECT SUM(COMM) AS C FROM EMPLOYEE;

    ALTER SESSION SET QUERY_REWRITE_ENABLED=TRUE;

    --0.00秒
    -- EXPLAN: TABLE ACCESS (FULL) OF 'MV_EMP'
    SELECT SUM(COMM) FROM EMPLOYEE;

    --0.00秒
    --EXPLAN: TABLE ACCESS (FULL) OF 'MV_EMP'
    SELECT * FROM V_MEP;

    經過手工刷新以后查詢速度會變慢!
    六、 分布式技術
    分布式技術包括分布式數據庫、DB LINK、分布式查詢以及分布式事務管理。這里我們僅僅介紹有關DB LINK的內容。
    DB LINK的實驗
    GRANT CREATE DATABALSE LINK TO SCOTT;

    CONN SCOTT/TIGER

    CREATE DATABASLE LINK OEM_LINK
      CONNECT TO SCOTT IDENTIFIED BY TIGER
        USING 'AAA';

    SELECT ENAME FROM EMP@OEM_LINK

    通過以上實驗我們得出結論:如果在執行分布式事務的時候那么網絡一定要保持正常,如果網絡斷開就會發生死鎖的情況。這時要進行在服務端和客戶端分別將鎖刪除即可解決。


    七、 DML語句
    INSERT INTO ALL
       INTO 表1 VALUES(列名列表)
       ...
       INTO 表2 VALUES(列名列表)
    子查詢;

    DML語句的實驗
    CREATE TABLE TEST_DWRS
    (DEPTNO NUMBER(2),
     RENSHU NUMBER(10)
    );

    CREATE TABLE TEST_DWGZ
    (DEPTNO NUMBER(2),
     AVGSAL NUMBER(7,2)
    );

    INSERT ALL
      INTO TEST_DWRS VALUES (DEPTNO,RENSHU)
      INTO TEST_DWGZ VALUES (DEPTNO,AVGSAL)
    SELECT DEPTNO DEPT,COUNT(*) REN_SHU,AVG(SAL) AVG_SAL
      FROM EMP GROUP BY DEPTNO;

    INSERT ALL
      WHEN 條件1 THEN
         INTO 表名1 VALUES(列名列表)
      ...
      WHEN 條件2 THEN
         INTO 表名2 VALUES(列名列表)
    子查詢;
    INSERT ALL
      WHEN REN_SHU>1000 THEN
       INTO TEST_DWRS VALUES(DEPTNO,RENSHU)
      WHEN AVG_SAL>2000 THEN
    INTO TEST_DWGZ VALUES(DEPTNO,AVGSAL)
    SELECT DEPTNO,COUNT(*) REN_SHU,AVG(SAL) AVG_SAL
      FROM EMP GROUP BY DEPTNO;
    八、 外部表(EXTERNAL TABLES)
    外部表是我們應用中可能會常常碰到的問題之一。例如,我需要將文本文件的數據導入到ORACLE數據庫中我們就可以利用此項技術。
    假設外部文件有如下信息:
     
    我們可以將此信息轉為以逗號分割的一些信息存為test.txt文本文件。
    然后我們在ORACLE中建立一個訂單表BILL(BILL_ID 訂單編號,BILL_D訂單日期)
    以及訂單明細表BILL_MX(BILL_ID 訂單編號,P 產品編號,PAMOUNT 產品數量)。
    我們下面的實驗就是通過將外部的文件信息導入到BILL表和BILL_MX表中去。
    首先簡單介紹一下創建外部表的方法:
    第一步,創建一個DIRECTORY對象
    CREATE DIRECTORY 名字 AS '路徑'
            CREATE DIRECTORY test_dir AS 'D:\1';
    第二步,創建外部表

    外部表的實驗
    //創建外部表
    CREATE TABLE BILL_EXTERNAL
    (BILL_ID VARCHAR2(8),
     BILL_D DATE,
     P1 VARCHAR2(10),
     P1_AMOUNT VARCHAR2(10),
     P2 VARCHAR2(10),
     P2_AMOUNT VARCHAR2(10),
     P3 VARCHAR2(10),
     P3_AMOUNT VARCHAR2(10))
    ORGANIZATION EXTERNAL
    (TYPE ORACLE_LOADER
     DEFAULT DIRECTORY test_dir
     ACCESS PARAMETERS
      (RECORDS DELIMITED BY NEWLINE
       FIELDS TERMINATED BY ','
       (BILL_ID CHAR,
        BILL_D CHAR DATE_FORMAT 
            DATE MASK "YYYYMMDD",
        P1 CHAR,
        P1_AMOUNT CHAR,
        P2 CHAR,
        P2_AMOUNT CHAR,
        P3 CHAR,
        P3_AMOUNT CHAR
          
      
      LOCATION ('TEST.TXT')
    );

    //導入數據
    INSERT ALL
      WHEN BILL_ID<>0 THEN
       INTO BILL VALUES(BILL_ID,BILL_D)
      WHEN P1<>0 THEN
            INTO BILL_MX VALUES(BILL_ID,P1,P1_AMOUNT)
      WHEN P2<>0 THEN
            INTO BILL_MX VALUES(BILL_ID,P2,P2_AMOUNT)
      WHEN P3<>0 THEN
            INTO BILL_MX VALUES(BILL_ID,P3,P3_AMOUNT)
    SELECT * FROM BILL_EXTERNAL;
    九、 日期類型
    日期類型的實驗

    SELECT SYSDATE + TO_YMINTERVAL('01-02') FROM DUAL;
    SELECT SYSTIMESTAMP + TO_DSINTERVAL('01 01:02:01') FROM DUAL;

    SELECT SYSTIMESTAMP + TO_YMINTERVAL('10-3') +
           TO_DSINTERVAL('05 07:00:00') FROM DUAL;
    十、 自定義數據類型
    自定義數據類型我們將會簡單地介紹有關記錄和集合的內容。
    記錄類型的實驗

    ORACLE 對象可以理解為JAVA中的類的概念。
       


    CREATE TYPE T_REC AS OBJECT(
           A NUMBER,
           B NUMBER
       ;

    CREATE TABLE RTEST(A NUMBER,
                       B NUMBER,
                       C T_REC);

    INSERT INTO RTEST VALUES(1,2,T_REC(10,20));

    CREATE TYPE EMP_INFORMATION AS OBJECT(
        ADDR VARCHAR2(50),
        EMAIL VARCHAR2(50),
        PHONE VARCHAR2(11));

    CREATE TABLE EMP1(
      EMPID VARCHAR2(10),
      EMPNAME VARCHAR2(20),
      INFO EMP_INFORMATION);

    INSERT INTO EMP1(EMPID,EMPNAME,INFO) 
      SELECT '0001','SHI',
            EMP_INFORMATION('DALIAN','SHIBU@163.COM','240560560') 
       FROM DUAL;
    集合類型的實驗
    CREATE TYPE 類型名 AS TABLE OF 元素類型名
    CREATE TABLE...
      (....
        C1 (NESTED TABLE的類型名),
       ...
      
    CREATE TYPE NTA AS TABLE OF NUMBER;
    CREATE TABLE NTEST (A NUMBER,
      B NUMBER,
      C NTA)
    NESTED TABLE C STORE AS NTT;
    INSERT INTO NTEST VALUES(1,2,NTA(100,200,300));
    實驗:創建一個工資表 職工編號,工資,補貼其中補貼是嵌套表,每個職工的都可以有多種補貼數量不定
    CREATE TYPE BT_VALUE AS OBJECT(BT_NAME VARCHAR2(20),
                                    BT_JE NUMBER(4));

    CREATE TYPE BT_LIST AS TABLE OF BT_VALUE;

    CREATE TABLE GZB(EMPNO NUMBER(4),SAL NUMBER(4), BT BT_LIST)
       NESTED TABLE BT STORE AS BT_TAB;

    INSERT INTO GZB VALUES(1001,1000,BT_LIST(BT_VALUE('JTFEE',500),
                                             BT_VALUE('TELFEE',200)
                                            
                           
    UPDATE TABLE(SELECT BT FROM GZB WHERE EMPNO=1001) SET BT_JE=150 WHERE BT_NAME='JTFEE';
     
    SELECT EMPNO,SAL,(SELECT SUM(BT_JE) FROM TABLE(BT)) BT_JE
     FROM GZB;

    至此,有關的ORACLE Advanced  SQL的內容就介紹完了。通過,這篇文檔的總結我也體會到數據庫的學習與應用我們還有很長的路要走。

    2005-9-3
    分布式計算---JAVA技術

    運用JAVA技術實現分布式計算



    目前,運用JAVA技術實現分布式計算的技術主要有RMI、CORBA以及Scoket通信三方面技術,下面就這三方面技術分別做一下比較。



         RMI



    RMI技術遠程調用,是基于RPC技術發展而來的。其開發過程基本由下面幾個過程



    1.         定義遠程接口



    2.         實現遠程接口



    3.         編寫使用遠程對象



    4.         生成stub(客戶代理)以及skeletom(服務器實體)



    5.         啟動注冊表并且注冊對象



    6.         運行服務器和客戶



    下面就分別對其簡單介紹



    1.        定義遠程接口



     



    package shi.rmi;



    public interface RemoteShiInterface extends java.rmi.Remote



    {



              String message(String message)throws java.rmi.RemoteException;



    }



    接口必須繼承于java.rmi.Remote;并且定義方法



    必須拋出java.rmi.RemoteExcetption的異常。



    2.        實現遠程接口



     



    package shi.rmi;



    import java.rmi.Naming;



    import java.rmi.server.UnicastRemoteObject;



    import java.rmi.RemoteException;



    import java.rmi.RMISecurityManager;



    public class RemoteObject extends UnicastRemoteObject implements RemoteShiInterface

    {



              String name;



              public RemoteObject(String name)throws RemoteException



              {



                        super();



                        this.name=name;



              }



              public String message(String message)throws RemoteException



              {



                                String returnString="My Name is :"+name+",thank for your message:"+message;



                        System.out.println("Returning:"+returnString);



                        System.out.println("hello stonewall";



    return "My Name is:"+name+",thanks for your message:"+message+"stonewall";



              }



    public static void main(String args[])



              {



                        //System.setSecurityManager(new RMISecurityManager());



                        try



                        {



                                  String myName="RMI";



                        RemoteObject theServer=new RemoteObject(myName);



                                  Naming.rebind("http://192.168.1.169:1099/RMI",theServer);



                                  System.out.println("Ready to continue";



                        }catch(Exception e)



                        {



              System.out.println("An Exception occured while creating server";



              System.out.println(e);



                        }



              }



    }



     



    必須注意的就是運用紅筆標記出來的代碼。基本上就是按照此種樣式來編寫的。另外,Naming rebind(“URL”,””)其中URL 必須制定,而且還要指定其訪問端口如果不指定RMI默認端口是1099。



    3.        編寫使用遠程對象



    package shi.rmi;



    import java.rmi.RMISecurityManager;



    import java.rmi.Naming;



    public class RemoteClient



    {



              public static void main(String args[])



              {



                        //System.setSecurityManager(new RMISecurityManager());



                        try



                        {



    RemoteShiInterface server=(RemoteShiInterface)Naming.lookup("http://192.168.1.169:1099/RMI";



                        String serverString=server.message("Hello There";



                        System.out.println("The server says:\n"+serverString);



                        }catch(Exception e)



                        {



                                  System.out.println(e);



                        }



              }



    }



     



    System.setSecurityManager(new RMISecurityManager()) 安全管理的代碼,如果把它注釋掉,那么需要建立一個安全策略文件,比如文件名 policy.txt



    Grant {



         permission java.security.AllPermission “”,””;



    };



    運行程序形式如下:



    D:\RMISample\server>java -Djava.security.policy=policy.txt RemoteObject



    D:\RMISample\client>java -Djava.security.policy=policy.txt RemoteClient



    如果注釋就可以直接運行:



                                Java RemoteObject     java RemoteClient



    4.        生成stub(客戶代理)以及skeletom(服務器實體)



    rmic RemoteObject



    5.        啟動注冊表并且注冊對象



    start rmiregistry 1099



    6.        運行服務器和客戶



    java RemoteObject



    java RemoteClient



     



     



     



     



         Socket



    Socket編程相對來說就比較簡單,服務器端利用ServerSocket的accept()方法來傾聽客戶端發出的請求。如果,希望客戶端傾聽網絡中多臺機器發出的請求,那么可以將Socket放到一個Thread中去。下面分別列出服務器端和客戶端代碼。



     



    服務器端代碼:



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Server.java



     



    package shi.socket;



    import java.io.*;



    import java.net.*;



     



    public class Server extends ServerSocket



    {



           private static final int SERVER_PORT = 10000;



     



           public Server() throws IOException



           {



                  super(SERVER_PORT);



                  try{



                         while (true){



                                Socket socket = accept();



                                new CreateServerThread(socket);



                      }



               }catch (IOException e){



                      e.printStackTrace();



               }



                  finally{



                         close();



                  }



           }



    }



     



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CreateServerThread.java



    package shi.socket;



     



    import java.net.*;



    import java.io.*;



     



    public class CreateServerThread extends Thread



    {



           private Socket client;



           private BufferedReader in;



           private PrintWriter out;



           private static InetAddress serverAddr;



           public CreateServerThread(Socket s) throws IOException{client = s;



           in=new BufferedReader(new InputStreamReader(client.getInputStream(), "GB2312");



                  out = new PrintWriter(client.getOutputStream(), true);



                  out.println("--- Welcome ---";



                  start();



           }



     



           public void run(){   



                  try {



                        serverAddr =  InetAddress.getByName(null);



                        while (true) {  



                          String str = in.readLine();



                          if (str.equals("bye") break;



                          System.out.println("Echoing: " + str);



                          out.println(serverAddr + " : " + str);



                        }



                        out.println("---Good Bye---";



                        System.out.println("closing...";



                      } catch (IOException e) {



                      } finally {



                        try {



                               client.close();



                        } catch(IOException e) {e.printStackTrace();}



                      }



           }



           public static void main(String args[]) throws IOException{



           serverAddr =  InetAddress.getByName(null);



           System.err.println(serverAddr.toString() +"--"+ "Create server Thread...";



           new Server();



           }



    }



     



    客戶端代碼:



    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Client.java



    package shi.socket;



    import java.io.*;



    import java.net.*;



     



    public class Client



    {



                  Socket socket;



                  BufferedReader in;



                  PrintWriter out;



     



                  public Client()



                  {



                                try



                                {



                                              InetAddress addr = 



                                                    InetAddress.getByName("192.168.1.169";



                                              socket = new Socket(addr, 10000);



    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));



                                              out = new PrintWriter(socket.getOutputStream(),true);



                                System.err.println(addr.toString() +"--"+ "Create Client Thread...";



                                              System.out.println(in.readLine());



                                              for (int i=0;i<10;i++){



                                                            String str = addr.toString() + " : " + "The ";



                                                            out.println(str + i);



                                                            System.out.println(in.readLine());



                                              }



                                              out.println("bye";



                                              System.out.println(in.readLine());



                                }catch(Exception e){



                                              e.printStackTrace();



                                }finally{



                                              System.out.println("closing...";



                                              try {



                                                            in.close();



                                              } catch (IOException e1) {



                                                            e1.printStackTrace();



                                              }



                                              out.close();



                                              try {



                                                            socket.close();



                                              } catch (IOException e2) {



                                                            e2.printStackTrace();



                                              }



                                }



                  }



     



                  public static void main(String[] args){



                                              new Client();



                                }



    }



     





     



          // wait for invocations from clients



          orb.run();



        } 



           



          catch (Exception e) {



            System.err.println("ERROR: " + e);



            e.printStackTrace(System.out);



          }



             



          System.out.println("HelloServer Exiting ...";



           



      }



    }



    客戶端代碼:



     



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// HelloClient.java



     



    import shi.corba.*;



    import org.omg.CosNaming.*;



    //import org.omg.CosNaming.NamingContextPackage.*;



    import org.omg.CORBA.*;



     



    public class HelloClient



    {



      static Hello helloImpl;



     



      public static void main(String args[])



        {



          try{



            // create and initialize the ORB



                  ORB orb = ORB.init(args, null);



     



            // get the root naming context



            org.omg.CORBA.Object objRef = 



                      orb.resolve_initial_references("NameService";



            // Use NamingContextExt instead of NamingContext. This is 



            // part of the Interoperable naming Service.  



            NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);



     



            // resolve the Object Reference in Naming



            String name = "Hello";



            helloImpl = HelloHelper.narrow(ncRef.resolve_str(name));



     



            System.out.println("Obtained a handle on server object: " + helloImpl);



            System.out.println(helloImpl.sayHello());



            helloImpl.shutdown();



     



                  } catch (Exception e) {



              System.out.println("ERROR : " + e) ;



                    e.printStackTrace(System.out);



                    }



        }



     



    }



     



     



     



     



     



         綜述:



    分析以上三種技術,我個人認為CORBA適合大的網絡應用。Socket適合于網絡間小數據量傳輸。RMI遠程方法調用,不僅適合于網絡間的小型應用,而且RMI的擴展要好于Socket通信。



     

    2005-7-16
    關于COM Interop學習體會心得


    COM interop理論/實踐
    在.NET框架下,開發人員可以通過COM interop tools 將COM組件導入導一個應用中去,一旦導入成功,那么我么就可以非常容易地調用COM接口給我們所提供的方法了。
    A .NET Framework developer can incorporate COM components into a managed application by using COM interop tools to import the relevant COM types. Once imported, the COM types are ready to use.
    一、 COM interop 概述:
    COM Interop 看上去象是介乎于COM和.Net之間的一條紐帶,一座橋梁。為了保持向后兼容,COM Interop可以使得.Net程序在不修改原有COM組件的前提下方便的訪問COM組件。這一點是非常重要的。事實上,全球的COM組件的代碼量估計可能有數十億行,擁有這些COM組件的公司不可能重寫這些組件,所以COM Interop的存在為有此需求的開發者提供了很好的解決方案。
    COM和.NET之間存在著非常大的差異,為了使兩者可以有機的結合在一起進行協同工作,COM Interop中實際存在著2種橋接方式。一種是RCW,Runtime Callable Wrapper;另一種是 CCW,COM Callable Wrapper。RCW是在運行時通過CLR從Interop裝配件(Interop Assembly)的元數據中獲取相關信息動態的實例化而得到的。使用者將感覺不到自己是在調用COM組件,一切都是這么的自然,和調用一個.Net組件沒有任何區別。  
    需要注意的是,一個COM組件(指的是一個實例,即一個DLL文件)由且僅由一個RCW負責維護。那么這兒有一個問題了,對于一個COM組件的不同版本,是不是就會有不同的RCW與之相對應呢?答案是肯定的。那有些朋友會說,.Net中的組件不是已經解決了COM中的“DLL HELL”問題了嗎?按上面的說法,似乎并沒有得到解決嘛?這兒我要說的是,在.Net中導入一個COM組件的不同版本,是會出現此類問題。解決此類問題的方法是使用PIA (Primary Interop Assembly)。
    .Net提供三種方法來導入一個COM組件
    l 通過Visual Studio .Net提從的“添加引用”功能
    l 通過tlbimp.exe實現
    l 使用System.Runtime.InteropServices.TypeLibConverter類編程
    下面就分別介紹COM的封裝、HRESULTs and Exceptions、繼承、聚合和包容、如何運用COM interop來生成發出事件和處理事件以及System.Runtime.InteropServices命名空間幾個重要的概念
    1. COM的封裝(COM Wrappers)
    l 在一般的語言(諸如C++)當中在客戶端我們一定要控制該COM對象的生命周期
    l 客戶端的COM對象的方法在C++中的調用通過產生該對象的實例同時獲得該對象的接口指針,通過接口指針來訪問該對象的方法。在.NET框架下則可以直接通過函數的映射來獲得 (Clients of .NET objects can obtain a description of an object's functionality using Reflection.)
    l 在.NET框架下的運行環境中.NET可以在內存中為.NET重新對象分配內存使用。(NET objects reside in memory managed by the .NET Framework execution environment.)
    為了解決上述問題,.NET提供一個COM Wrappers.它可以使得Managed Code 和 Unmanaged Code可以很好結合在一起。COM Wrappers兩種橋接方式RCW(runtime callable wrapper)和CCW (COM callable wrapper)其中RCW是將Managed的客戶端與Unmanaged的服務器端聯接起來的;CCW是將 Unmanaged的客戶端與 Managed的服務器端聯接起來的。
    2. HRESULTs and Exceptions
    在COM編程中我們通過HRESULT來判斷所做的操作是否成功,在.NET框架下我們通過拋不同的異常(Throw Exceptions)來捕捉錯誤。
    注MSDN給我們列出了HRESULT不同值與.NET的不同異常的對照表。(.NET Framework Developer’s Guide—HRESULTs and Exceptions)
    3. 繼承、聚合和包容(Inheritance,Aggregation and Containment)
    繼承:.NET提供一些標準的接口,用戶在定義接口時,可以繼承這些接口。
    聚合:.NET也支持COM提供的聚合的概念即,外對象將內對象的接口暴露在用戶面前。
    包容:
        通過在外對象的構造函數中創建內對象的實例,這樣客戶端就可以通過該實例獲得接口進行調用接口的各個方法。
    4. 如何運用COM interop來生成發出事件和處理事件
    在以下內容中,將要描述有關COM對象出接口與事件接收器的連接機制。
    關于COM對象的出接口與事件接收器之間的連接機制與在描述COM原理與應用中的機制是一樣的,即COM對象聲明一個出接口,在事件接收器中表示該接口的實現方法。一旦,COM對象與事件接收器的連接建立好以后,那么客戶端就可以隨時接收到COM對象服務器端的事件、消息。下面我們從C#的服務器端和事件接收器兩個方面來描述這個問題。
    Handling Events Raised by a COM Source(描述COM源對象是如何產生一個事件的)
    Raising Events Handled by a COM Sink(通過接收器來處理事件)
    5. System.Runtime.InteropServices命名空間
    System.Runtime.InteropServices是一個有關訪問COM對象以及在.NET框架下的本地API函數。在創建COM接口時經常要運用這個命名空間。
    二、 C#接口編程
         下面將從接口的定義、接口的訪問、接口的實現以及接口的轉換編程這些方面來闡述運用C#進行接口編程的方法。
    1. 接口的定義
    接口的聲明:
    [attributes] [modifiers] interface identifier [:base-list] {interface-body}[;]
    l attributes(可選):附加的定義性信息。
    l · modifiers(可選):允許使用的修飾符有new和四個訪問修飾符。分別是:new、public、protected、internal、 private。在一個接口定義中同一修飾符不允許出現多次,new修飾符只能出現在嵌套接口中,表示覆蓋了繼承而來的同名成員。The public,  protected, internal, and private修飾符定義了對接口的訪問權限。 
    l 指示器和事件。
    l identifier:接口名稱。 
    l base-list(可選):包含一個或多個顯式基接口的列表,接口間由逗號分隔。 
    l interface-body:對接口成員的定義。 
    l 接口可以是命名空間或類的成員,并且可以包含下列成員的簽名: 方法、屬性、索引器 。 
    l 一個接口可從一個或多個基接口繼承。
    接口的主體:
    interface-body:  {   interface-member-declarationsopt   }
    接口可以包含一個和多個成員,這些成員可以是方法、屬性、索引指示器和事件,但不能是常量、域、操作符、構造函數或析構函數,而且不能包含任何靜態成員。接口定義創建新的定義空間,并且接口定義直接包含的接口成員定義將新成員引入該定義空間。
       2. 接口的訪問
    C#中的CASTS來代替QueryInterface
    (Using Casts Instead of QueryInterface)
    在C++中客戶端需要通過QueryInterface來獲得COM對象的接口指針。在C#編程中卻不必這么麻煩。我們可以直接將COM對象對應到相應的COM接口上。如果我們在程序中對應錯誤,那么在運行時C#會拋出異常。
       3. 接口的實現
    顯示地實現接口成員即可以直接利用類來實現接口的成員函數如:
        // Since the .NET Framework interface and coclass have to behave as 
        // COM objects, we have to give them guids.
        [Guid("DBE0E8C4-1C61-41f3-B6A4-4E2F353D3D05"]
        public interface IManagedInterface
        {
           int PrintHi(string name);
        }
        [Guid("C6659361-1625-4746-931C-36014B146679"]
        public class InterfaceImplementation : IManagedInterface
        {
           public int PrintHi(string name)
           {
              Console.WriteLine("Hello, {0}!", name);
               return 33;
              }
        }
    通過為Coclass的方法來實現接口如:
    [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"
            InterfaceType(ComInterfaceType.InterfaceIsDual)] 
        interface IMediaControl   // Cannot list any base interfaces here 
        { // COM methods     }
    // Declare FilgraphManager as a COM coclass:
         [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770"
         class FilgraphManager   // Cannot have a base class or
                                     // interface list here.
         { 
             // Cannot have any members here 
             // NOTE that the C# compiler will add a default constructor
             // for you (no parameters).
       }
    其中類FilgraphManager實現了ImediaControl的方法其中ComImport--它將類標記為在外部實現的 COM 類。
       4. 接口的轉換編程
    運用C#開發COM組件服務器端
    l 聲明一個COM接口(Declaring a COM Interface):
    首先聲明一個COM接口我們必須要明確該接口是繼承IUnknown or IDispatch或者其他接口。運用C#描述這方面內容必須使用C#提供的屬性InterfaceType(InterfaceType – 表明接口是繼承于 IUnknown or IDispatch)
    其次,為了使COM接口中出現成員函數,程序中也要指出ComImport and Guid屬性。(Guid—表明接口或者coclass的唯一標示號;ComImport—它將類標記為在外部實現的 COM 類。)
    注:coclass是(簡稱組件對象類――component object class)被包含在DLL或EXE中,并且包含著一個或者多個接口的代碼。客戶端通過創建該對象的實例來獲得COM對象的接口。
    例子代碼:
    [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"
            InterfaceType(ComInterfaceType.InterfaceIsDual)] 
        interface IMediaControl   // Cannot list any base interfaces here 
        {
    // COM methods
         }
    l 聲明一個組件對象類(Declaring a COM coclass)
    1. 該類不可以繼承于其他類。
    2. 也不可以實現任何接口。
    3. 必須有一個Guid來唯一標示該類
    例子代碼:
    // Declare FilgraphManager as a COM coclass:
         [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770"
    //"E436EBB3-524F-11CE-9F53-0020AF0BA770"標示號在系統注冊表中可以找到,說明//此處是調用外部的COM類來實現接口ImediaControl的。
         class FilgraphManager   // Cannot have a base class or
                                     // interface list here.
         { 
             // Cannot have any members here 
             // NOTE that the C# compiler will add a default constructor
             // for you (no parameters).
      }
    運用C#開發COM組件客戶端
    l C#中的CASTS來代替QueryInterface
    (Using Casts Instead of QueryInterface)
    在C++中客戶端需要通過QueryInterface來獲得COM對象的接口指針。在C#編程中卻不必這么麻煩。我們可以直接將COM對象對應到相應的COM接口上。如果我們在程序中對應錯誤,那么在運行時C#會拋出異常。
    例子代碼:
    // Create an instance of a COM coclass:
    FilgraphManager graphManager = new FilgraphManager();
    // See if it supports the IMediaControl COM interface. 
    // Note that this will throw a System.InvalidCastException if 
    // the cast fails. This is equivalent to QueryInterface for 
    // COM objects:
    IMediaControl mc = (IMediaControl) graphManager;
    // Now you call a method on a COM interface: 
    mc.Run();
    l C#運用拋異常的機制來代替HRESULT
    在創建COM對象實例以及獲得接口時可能都會出現問題,在C++中我們通過調用HRESULT來判斷所做的操作是否成功。但是,在C#編程中我們就不必那么麻煩,C#會拋出諸如System.COMException的異常。
    三、關于C#中幾個工具的用法
    1. Tlbimp.exe的用法:
    該工具適合將*.tlb的類庫文件可以轉換成*.dll的文件,同時該工具還可以將一個dll輸出到一個新的dll文件當中去。
    例:tlbimp myTest.tlb
    輸出 MYTESTLIB.dll的動態聯接庫文件

    tlbimp  myTest.tlb  /out:myTest.dll
    輸出生成 myTest.dll的文件
    tlbimp c:\winnt\system32\quartz.dll /out:QuartzTypeLib.dll
    將quartz.dll包含到QuartzTypeLib.dll中去。
    2. Ildasm工具可以查看*.dll, *.exe, .obj, .lib 文件
    例如可以查看.dll的具體情況比如接口、實現類以及成員函數等信息。
    如:可以通過命令Ildasm *.dll來查看具體生成的dll的情況。
    3. Regasm工具用于將dll動態聯接庫注冊到注冊表中的操作
    如:Regasm QuartzTypeLib.dll
    就將QuartzTypeLib.dll注冊到注冊表中去。
    如果要解除剛才的注冊操作可以運用
    Regasm /unregister QuartzTypeLib.dll命令即可。
    4. CSC工具的使用是編譯C#文件*.cs為exe或者dll文件的工具
    如:csc File.cs 就是將File.cs編譯為File.exe文件
    csc /out:my.exe File.cs將File.cs文件編譯為my.exe
    csc /target:library File.cs就是將File.cs編譯為File.dll文件
    5.guidgen工具用于生成Guid號
    在C#中使用工具
    $\Microsoft Visual Studio .NET 2003\Common7\Tools\$下的工具
    guidgen.exe生成一個隨機的GUID號。
    三、 運用C#開發COM的組件的簡單例子
    功能概述此例子非常簡單就是在服務器端寫一個具有現實Hello, **的方法
    客戶端通過調用服務器端的PrintHi(String name)函數顯示出 Hello, **的信息。
    服務器端代碼:
    //Copyright (C) 2000 Microsoft Corporation.  All rights reserved.
    // ShiServer.cs
    // compile with: /target:library
    using System;
    using System.Runtime.InteropServices;
    namespace ShiServer
    {
    // Since the .NET Framework interface and coclass have to behave as 
    // COM objects, we have to give them guids.
    [Guid("976F8704-6E29-4b67-AC4F-A5B1226D1F49"]
    public interface IshiInterface
    {
    int PrintHi(string name);
    }
    [Guid("2BDD9B83-E4FC-432a-BBE1-F71C05723AB6"]
    public class IshiImplementation : IshiInterface
    {
    public int PrintHi(string name)
    {
    Console.WriteLine("Hello, {0}!", name);
    return 33;
    }
    }
    }
    客戶端代碼:
    //Copyright (C) 2000 Microsoft Corporation.  All rights reserved.
    // shi.cs
    // Build with "csc /R:ShiServer.dll shi.cs"
    using System;
    class MainClass 

        /************************************************************ 
    show Hello,sname
        **************************************************************/ 
        public static void Main(string[] args) 
        { 
            // input a name
    string sname = args[0];
            // Create instance of ShiServer
            // (Calls CoCreateInstance(D3E09FD9-B987-47f3-89CB-95EFB4D68583,
            // NULL, CLSCTX_ALL, IID_IUnknown, &myHello).
            // Returns null on failure):         
            ShiServer.IshiImplementation myHello=
                  new ShiServer.IshiImplementation();
            // QueryInterface for the IshiInterface interface:
            ShiServer.IshiInterface myIhello=
                (ShiServer.IshiInterface)myHello;
                  
            // Call some methods on a COM interface 
    myIhello.PrintHi(sname);
        }
    }
    輸入: shi stonewall   輸出:Hello, stonewall

    注:以上資料均來自于
    MSDN Library –January 2002 
    TiTle: C# Programmer’s Reference   COM Interop Tutorials
    COM interop 概述 ::URL::http://www.xmlasp.net/n966c13.aspx
    What’s new for Interop in .NET Framework v2.0?       
    ::URL::http://www.dotnetinterop.com/features/default.aspx?q=Whidbey
    C#COM接口編程
    ::URL::http://tech.ccidnet.com/pub/disp/Article?columnID=295&articleID=40725&pageNO=1

    posted on 2007-04-27 09:51 天外飛仙 閱讀(2388) 評論(1)  編輯  收藏 所屬分類: Oracle

    Feedback

    # re: 研究Oracle 2008-11-21 12:29 過路人
    好長  回復  更多評論
      

    主站蜘蛛池模板: 免费人成视频在线播放| 久久青草免费91线频观看不卡| 少妇亚洲免费精品| 女人隐私秘视频黄www免费| 久久精品国产亚洲AV香蕉| 丁香花在线观看免费观看| 成人亚洲国产精品久久| 亚洲av无码潮喷在线观看| 国产无人区码卡二卡三卡免费 | 国产精品免费视频网站| 抽搐一进一出gif免费视频| 亚洲精品在线免费观看| 亚洲第一黄色网址| 麻豆视频免费观看| 五月天婷婷免费视频| 久久精品蜜芽亚洲国产AV| 国产伦精品一区二区三区免费下载| 日本二区免费一片黄2019| a级片免费在线播放| 亚洲免费福利在线视频| 国精无码欧精品亚洲一区| 日韩成人免费aa在线看| 97国产在线公开免费观看| 成年免费a级毛片| 亚洲三级在线观看| 亚洲自偷精品视频自拍| 国产亚洲精品免费视频播放| 最新仑乱免费视频| 四虎国产精品永久免费网址| 亚洲国产成人九九综合| 亚洲日本中文字幕天堂网| 在线播放高清国语自产拍免费| 野花香高清在线观看视频播放免费 | 亚洲精品tv久久久久| 大学生一级毛片免费看| 国产情侣久久久久aⅴ免费| 日韩在线观看视频免费| 亚洲熟伦熟女专区hd高清| 亚洲经典在线中文字幕| 日韩一卡2卡3卡4卡新区亚洲| 国产成人免费ā片在线观看|