數據庫性能的優化


轉自:http://www.cnblogs.com/kex1n/archive/2010/03/26/2286504.html

mysql執行腳本


首要步驟(一般可省略):
開啓mysql服務(默認是開機時就自動運行了):
控制面板-->管理工具-->服務,在裏面找到名稱爲mysql的項,雙擊就可看它的服務狀態是啓動還是停止,把它設爲啓動

連接mysql:
在命令行下輸入 mysql -h localhost -u root -p回車,然後輸入密碼即可;或直接運行mysql自帶的連接工具,然後輸入密碼即可.

1.編寫sql腳本,假設內容如下:

  create database dearabao;
  use dearabao;
  create table niuzi (name varchar(20));

  保存腳本文件,假設我把它保存在F盤的hello world目錄下,於是該文件的路徑爲:F:/hello world/niuzi.sql


2.執行sql腳本,可以有2種方法:
  第一種方法:
 在命令行下(未連接數據庫),輸入 mysql -h localhost -u root -p123456 < F:/hello world/niuzi.sql (注意路徑不用加引號的!!) 回車即可.
  第二種方法:
 在命令行下(已連接數據庫,此時的提示符爲 mysql> ),輸入 source F:/hello world/niuzi.sql (注意路徑不用加引號的) 或者 /. F:/hello world/niuzi.sql (注意路徑不用加引號的) 回車即可

 

SQL編寫規範

1.書寫格式最好統一
示例代碼:

存儲過程SQL文書寫格式例
select 
        c.dealerCode,
        round(sum(c.submitSubletAmountDLR + c.submitPartsAmountDLR + c.submitLaborAmountDLR) / count(*), 2) as avg, 
        decode(null, 'x', 'xx', 'CNY') 
from ( 
        select 
                a.dealerCode,
                a.submitSubletAmountDLR, 
                a.submitPartsAmountDLR, 
                a.submitLaborAmountDLR 
        from SRV_TWC_F a
        where (to_char(a.ORIGSUBMITTIME, 'yyyy/mm/dd') >= 'Date Range(start)' 
        and to_char(a.ORIGSUBMITTIME, 'yyyy/mm/dd') <= 'Date Range(end)'   
        and nvl(a.deleteflag, '0') <> '1') 
        union all 
        select 
                b.dealerCode,
                b.submitSubletAmountDLR, 
                b.submitPartsAmountDLR, 
                b.submitLaborAmountDLR 
        from SRV_TWCHistory_F b
        where (to_char(b.ORIGSUBMITTIME, 'yyyy/mm/dd') >= 'Date Range(start)' 
        and to_char(b.ORIGSUBMITTIME,'yyyy/mm/dd') <= 'Date Range(end)'   
        and nvl(b.deleteflag,'0') <> '1')
) c
group by c.dealerCode 
order by avg desc;

Java source裏的SQL字符串書寫格式例
strSQL = "insert into Snd_FinanceHistory_Tb "
    + "(DEALERCODE, "
    + "REQUESTSEQUECE, "
    + "HANDLETIME, "
    + "JOBFLAG, "
    + "FRAMENO, "
    + "INMONEY, "
    + "REMAINMONEY, "
    + "DELETEFLAG, "
    + "UPDATECOUNT, "
    + "CREUSER, "
    + "CREDATE, "
    + "HONORCHECKNO, "
    + "SEQ) "
    + "values ('" + draftInputDetail.dealerCode + "', "
    + "'" + draftInputDetail.requestsequece + "', "
    + "sysdate, "
    + "'07', "
    + "'" + frameNO + "', "
    + requestMoney + ", "
    + remainMoney + ", "
    + "'0', "
    + "0, "
    + "'" + draftStruct.employeeCode + "', "
    + "sysdate, "
    + "'" + draftInputDetail.honorCheckNo + "', "
    + index + ")";

  1).縮進
    對於存儲過程文件,縮進爲8個空格
    對於Java source裏的SQL字符串,不可有縮進,即每一行字符串不可以空格開頭

  2).換行
    1>.Select/From/Where/Order by/Group by等子句必須另其一行寫
    2>.Select子句內容如果只有一項,與Select同行寫
    3>.Select子句內容如果多於一項,每一項單獨佔一行,在對應Select的基礎上向右縮進8個空格(Java source無縮進)
    4>.From子句內容如果只有一項,與From同行寫
    5>.From子句內容如果多於一項,每一項單獨佔一行,在對應From的基礎上向右縮進8個空格(Java source無縮進)
    6>.Where子句的條件如果有多項,每一個條件佔一行,以AND開頭,且無縮進
    7>.(Update)Set子句內容每一項單獨佔一行,無縮進
    8>.Insert子句內容每個表字段單獨佔一行,無縮進;values每一項單獨佔一行,無縮進
    9>.SQL文中間不允許出現空行
    10>.Java source裏單引號必須跟所屬的SQL子句處在同一行,連接符("+")必須在行首

  3).空格
    1>.SQL內算數運算符、邏輯運算符連接的兩個元素之間必須用空格分隔
    2>.逗號之後必須接一個空格
    3>.關鍵字、保留字和左括號之間必須有一個空格

2.不等於統一使用"<>"
  Oracle認爲"!="和"<>"是等價的,都代表不等於的意義。爲了統一,不等於一律使用"<>"表示

3.使用表的別名
  數據庫查詢,必須使用表的別名

4.SQL文對錶字段擴展的兼容性
  在Java source裏使用Select *時,嚴禁通過getString(1)的形式得到查詢結果,必須使用getString("字段名")的形式
  使用Insert時,必須指定插入的字段名,嚴禁不指定字段名直接插入values

5.減少子查詢的使用
  子查詢除了可讀性差之外,還在一定程度上影響了SQL運行效率
  請儘量減少使用子查詢的使用,用其他效率更高、可讀性更好的方式替代

6.適當添加索引以提高查詢效率
  適當添加索引可以大幅度的提高檢索速度
  請參看ORACLE SQL性能優化系列

7.對數據庫表操作的特殊要求
  本項目對數據庫表的操作還有以下特殊要求:

  1).以邏輯刪除替代物理刪除
    注意:現在數據庫表中數據沒有物理刪除,只有邏輯刪除
    以deleteflag字段作爲刪除標誌,deleteflag='1'代表此記錄被邏輯刪除,因此在查詢數據時必須考慮deleteflag的因素
    deleteflag的標準查詢條件:NVL(deleteflag, '0') <> '1'

  2).增加記錄狀態字段
    數據庫中的每張表基本都有以下字段:DELETEFLAG、UPDATECOUNT、CREDATE、CREUSER、UPDATETIME、UPDATEUSER
    要注意在對標進行操作時必須考慮以下字段

    插入一條記錄時要置DELETEFLAG='0', UPDATECOUNT=0, CREDATE=sysdate, CREUSER=登錄User
    查詢一條記錄時要考慮DELETEFLAG,如果有可能對此記錄作更新時還要取得UPDATECOUNT作同步檢查
    修改一條記錄時要置UPDATETIME=sysdate, UPDATEUSER=登錄User, UPDATECOUNT=(UPDATECOUNT+1) mod 1000, 
    刪除一條記錄時要置DELETEFLAG='1'

  3).歷史表
    數據庫裏部分表還存在相應的歷史表,比如srv_twc_f和srv_twchistory_f
    在查詢數據時除了檢索所在表之外,還必須檢索相應的歷史表,對二者的結果做Union(或Union All)

8.用執行計劃分析SQL性能


  EXPLAIN PLAN是一個很好的分析SQL語句的工具,它可以在不執行SQL的情況下分析語句
  通過分析,我們就可以知道ORACLE是怎樣連接表,使用什麼方式掃描表(索引掃描或全表掃描),以及使用到的索引名稱

  按照從裏到外,從上到下的次序解讀分析的結果
  EXPLAIN PLAN的分析結果是用縮進的格式排列的,最內部的操作將最先被解讀,如果兩個操作處於同一層中,帶有最小操作號的將首先被執行

  目前許多第三方的工具如PLSQL Developer和TOAD等都提供了極其方便的EXPLAIN PLAN工具
  PG需要將自己添加的查詢SQL文記入log,然後在EXPLAIN PLAN中進行分析,儘量減少全表掃描


ORACLE SQL性能優化系列


1.選擇最有效率的表名順序(只在基於規則的優化器中有效)
   ORACLE的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最後的表(基礎表driving table)將被最先處理
   在FROM子句中包含多個表的情況下,必須選擇記錄條數最少的表作爲基礎表
   當ORACLE處理多個表時,會運用排序及合併的方式連接它們
   首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行排序;
   然後掃描第二個表(FROM子句中最後第二個表);
   最後將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合併
   例如:
     表 TAB1 16,384 條記錄
     表 TAB2 5 條記錄
     選擇TAB2作爲基礎表 (最好的方法)
     select count(*) from tab1,tab2 執行時間0.96秒 
     選擇TAB2作爲基礎表 (不佳的方法)
     select count(*) from tab2,tab1 執行時間26.09秒

   如果有3個以上的表連接查詢,那就需要選擇交叉表(intersection table)作爲基礎表,交叉表是指那個被其他表所引用的表
   例如:
     EMP表描述了LOCATION表和CATEGORY表的交集
       SELECT * 
       FROM LOCATION L,
       CATEGORY C,
       EMP E
       WHERE E.EMP_NO BETWEEN 1000 AND 2000
       AND E.CAT_NO = C.CAT_NO
       AND E.LOCN = L.LOCN

     將比下列SQL更有效率
       SELECT * 
       FROM EMP E ,
       LOCATION L ,
       CATEGORY C
       WHERE E.CAT_NO = C.CAT_NO
       AND E.LOCN = L.LOCN
       AND E.EMP_NO BETWEEN 1000 AND 2000

2.WHERE子句中的連接順序
  ORACLE採用自下而上的順序解析WHERE子句
  根據這個原理,表之間的連接必須寫在其他WHERE條件之前,那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾
  例如:
     (低效,執行時間156.3秒)
       SELECT *
       FROM EMP E
       WHERE SAL > 50000
       AND JOB = 'MANAGER'
       AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO);

     (高效,執行時間10.6秒)
       SELECT *
       FROM EMP E
       WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO)
       AND SAL > 50000
       AND JOB = 'MANAGER';

3.SELECT子句中避免使用'*'
  當你想在SELECT子句中列出所有的COLUMN時,使用動態SQL列引用'*'是一個方便的方法,不幸的是,這是一個非常低效的方法
  實際上,ORACLE在解析的過程中,會將'*'依次轉換成所有的列名
  這個工作是通過查詢數據字典完成的,這意味着將耗費更多的時間

4.減少訪問數據庫的次數 
  當執行每條SQL語句時,ORACLE在內部執行了許多工作:解析SQL語句,估算索引的利用率,綁定變量,讀數據塊等等
  由此可見,減少訪問數據庫的次數,就能實際上減少ORACLE的工作量
  例如:
    以下有三種方法可以檢索出僱員號等於0342或0291的職員
    方法1 (最低效)
      SELECT EMP_NAME, SALARY, GRADE
      FROM EMP
      WHERE EMP_NO = 342;

      SELECT EMP_NAME, SALARY, GRADE
      FROM EMP
      WHERE EMP_NO = 291;

    方法2 (次低效)
      DECLARE
      CURSOR C1 (E_NO NUMBER) IS
      SELECT EMP_NAME,SALARY,GRADE
      FROM EMP
      WHERE EMP_NO = E_NO;
      BEGIN
      OPEN C1(342);
      FETCH C1 INTO …,…,…;
      …
      OPEN C1(291);
      FETCH C1 INTO …,…,…;
      …
      CLOSE C1;
      END;                                  

    方法2 (高效)
      SELECT A.EMP_NAME, A.SALARY, A.GRADE,
             B.EMP_NAME, B.SALARY, B.GRADE
      FROM EMP A, EMP B
      WHERE A.EMP_NO = 342
      AND B.EMP_NO = 291;

5.使用DECODE函數來減少處理時間
  使用DECODE函數可以避免重複掃描相同記錄或重複連接相同的表
  例如:
    SELECT COUNT(*), SUM(SAL)
    FROM EMP
    WHERE DEPT_NO = '0020'
    AND ENAME LIKE 'SMITH%';

    SELECT COUNT(*), SUM(SAL)
    FROM EMP
    WHERE DEPT_NO = '0030'
    AND ENAME LIKE 'SMITH%';

    你可以用DECODE函數高效地得到相同結果
    SELECT COUNT(DECODE(DEPT_NO, '0020', 'X', NULL)) D0020_COUNT,
    COUNT(DECODE(DEPT_NO, '0030', 'X', NULL)) D0030_COUNT,
    SUM(DECODE(DEPT_NO, '0020', SAL, NULL)) D0020_SAL, 
    SUM(DECODE(DEPT_NO, 0030, SAL, NULL)) D0030_SAL
    FROM EMP
    WHERE ENAME LIKE 'SMITH%';

  'X'表示任何一個字段
  類似的,DECODE函數也可以運用於GROUP BY和ORDER BY子句中

6.用Where子句替換HAVING子句
  避免使用HAVING子句,HAVING只會在檢索出所有記錄之後纔對結果集進行過濾,這個處理需要排序、統計等操作
  如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷
  例如:
    低效
    SELECT REGION, AVG(LOG_SIZE)
    FROM LOCATION
    GROUP BY REGION
    HAVING REGION REGION != 'SYDNEY'
    AND REGION != 'PERTH'

    高效
    SELECT REGION, AVG(LOG_SIZE)
    FROM LOCATION
    WHERE REGION REGION != 'SYDNEY'
    AND REGION != 'PERTH'
    GROUP BY REGION

7.減少對錶的查詢
  在含有子查詢的SQL語句中,要特別注意減少對錶的查詢
  例如:
    低效
    SELECT TAB_NAME
    FROM TABLES
    WHERE TAB_NAME = (SELECT TAB_NAME 
                      FROM TAB_COLUMNS
                      WHERE VERSION = 604)
    AND DB_VER = (SELECT DB_VER
                  FROM TAB_COLUMNS
                  WHERE VERSION = 604)

    高效
    SELECT TAB_NAME
    FROM TABLES
    WHERE (TAB_NAME, DB_VER) = (SELECT TAB_NAME, DB_VER
                                FROM TAB_COLUMNS
                                WHERE VERSION = 604)

  Update多個Column例子:
    低效
    UPDATE EMP
    SET EMP_CAT = (SELECT MAX(CATEGORY) 
                   FROM EMP_CATEGORIES), 
    SAL_RANGE = (SELECT MAX(SAL_RANGE) 
                 FROM EMP_CATEGORIES)
    WHERE EMP_DEPT = 0020;

    高效
    UPDATE EMP
    SET (EMP_CAT, SAL_RANGE) = (SELECT MAX(CATEGORY), MAX(SAL_RANGE)
                                FROM EMP_CATEGORIES)
    WHERE EMP_DEPT = 0020;

8.使用表的別名(Alias)
  當在SQL語句中連接多個表時,請使用表的別名並把別名前綴於每個Column上
  這樣可以減少解析的時間並減少那些由Column歧義引起的語法錯誤

9.用EXISTS替代IN                                    
  在許多基於基礎表的查詢中,爲了滿足一個條件,往往需要對另一個表進行聯接
  在這種情況下,使用EXISTS(或NOT EXISTS)通常將提高查詢的效率
    低效
    SELECT *
    FROM EMP (基礎表)
    WHERE EMPNO > 0
    AND DEPTNO IN (SELECT DEPTNO
                   FROM DEPT
                   WHERE LOC = 'MELB')

    高效
    SELECT *
    FROM EMP (基礎表)
    WHERE EMPNO > 0
    AND EXISTS (SELECT 'X'
    FROM DEPT
    WHERE DEPT.DEPTNO = EMP.DEPTNO
    AND LOC = 'MELB')

10.用NOT EXISTS替代NOT IN
  在子查詢中,NOT IN子句將執行一個內部的排序和合並
  無論在哪種情況下,NOT IN都是最低效的,因爲它對子查詢中的表執行了一個全表遍歷
  爲了避免使用NOT IN,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS
  例如:
    SELECT …
    FROM EMP
    WHERE DEPT_NO NOT IN (SELECT DEPT_NO
                          FROM DEPT
                          WHERE DEPT_CAT = 'A');

    爲了提高效率改寫爲
    高效
    SELECT …
    FROM EMP A, DEPT B
    WHERE A.DEPT_NO = B.DEPT(+)
    AND B.DEPT_NO IS NULL
    AND B.DEPT_CAT(+) = 'A'

    最高效
    SELECT …
    FROM EMP E
    WHERE NOT EXISTS (SELECT 'X'
                      FROM DEPT D
                      WHERE D.DEPT_NO = E.DEPT_NO
                      AND DEPT_CAT = 'A');

11.用表連接替換EXISTS
  通常來說,採用表連接的方式比EXISTS更有效率
  例如:
    SELECT ENAME
    FROM EMP E
    WHERE EXISTS (SELECT 'X'
                  FROM DEPT
                  WHERE DEPT_NO = E.DEPT_NO
                  AND DEPT_CAT = 'A');

    更高效
    SELECT ENAME
    FROM DEPT D, EMP E
    WHERE E.DEPT_NO = D.DEPT_NO
    AND DEPT_CAT = 'A';

12.用EXISTS替換DISTINCT 
  當提交一個包含多表信息(比如部門表和僱員表)的查詢時,避免在SELECT子句中使用DISTINCT,一般可以考慮用EXIST替換
  例如:
    低效
    SELECT DISTINCT DEPT_NO, DEPT_NAME
    FROM DEPT D, EMP E
    WHERE D.DEPT_NO = E.DEPT_NO

    高效
    SELECT DEPT_NO, DEPT_NAME
    FROM DEPT D
    WHERE EXISTS (SELECT 'X'
                  FROM EMP E
                  WHERE E.DEPT_NO = D.DEPT_NO);

  EXISTS使查詢更爲迅速,因爲RDBMS核心模塊將在子查詢的條件一旦滿足後,立刻返回結果

13.用索引提高效率
  索引是表的一個概念部分,用來提高檢索數據的效率。實際上,ORACLE使用了一個複雜的自平衡B-tree結構
  通常,通過索引查詢數據比全表掃描要快。當ORACLE找出執行查詢和Update語句的最佳路徑時,ORACLE優化器將使用索引
  同樣,在聯結多個表時使用索引也可以提高效率。另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證

  除了那些LONG或LONG RAW數據類型,你可以索引幾乎所有的列
  通常在大型表中使用索引特別有效,當然,在掃描小表時,使用索引同樣能提高效率

  雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價
  索引需要空間來存儲,也需要定期維護,每當有記錄在表中增減或索引列被修改時,索引本身也會被修改
  這意味着每條記錄的INSERT、DELETE、UPDATE將爲此多付出4、5次的磁盤I/O
  因爲索引需要額外的存儲空間和處理,那些不必要的索引反而會使查詢反應時間變慢

  ORACLE對索引有兩種訪問模式:
  1).索引唯一掃描(INDEX UNIQUE SCAN)
    大多數情況下, 優化器通過WHERE子句訪問INDEX
    例如:
      表LODGING有兩個索引:建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER
      SELECT *
      FROM LODGING
      WHERE LODGING = 'ROSE HILL';

      在內部,上述SQL將被分成兩步執行:
      首先,LODGING_PK索引將通過索引唯一掃描的方式被訪問,獲得相對應的ROWID;然後通過ROWID訪問表的方式執行下一步檢索
      如果被檢索返回的列包括在INDEX列中,ORACLE將不執行第二步的處理(通過ROWID訪問表)
      因爲檢索數據保存在索引中,單單訪問索引就可以完全滿足查詢結果

  2).索引範圍查詢(INDEX RANGE SCAN)
    適用於兩種情況: 
    1>.基於唯一性索引的一個範圍的檢索
    2>.基於非唯一性索引的檢索
    例1
      SELECT LODGING
      FROM LODGING
      WHERE LODGING LIKE 'M%';

      WHERE子句條件包括一系列值,ORACLE將通過索引範圍查詢的方式查詢LODGING_PK
      由於索引範圍查詢將返回一組值,它的效率就要比索引唯一掃描低一些

    例2
      SELECT LODGING
      FROM LODGING
      WHERE MANAGER = 'BILL GATES';

      這個SQL的執行分兩步,LODGING$MANAGER的索引範圍查詢(得到所有符合條件記錄的ROWID),通過ROWID訪問表得到LODGING列的值
      由於LODGING$MANAGER是一個非唯一性的索引,數據庫不能對它執行索引唯一掃描

  WHERE子句中,如果索引列所對應的值的第一個字符由通配符(WILDCARD)開始,索引將不被採用
    SELECT LODGING
    FROM LODGING
    WHERE MANAGER LIKE '%HANMAN';

  在這種情況下,ORACLE將使用全表掃描

14.避免在索引列上使用計算
  WHERE子句中,如果索引列是函數的一部分,優化器將不使用索引而使用全表掃描
  例如:
    低效
    SELECT …
    FROM DEPT
    WHERE SAL * 12 > 25000;

    高效
    SELECT …
    FROM DEPT
    WHERE SAL > 25000/12;

  請務必注意,檢索中不要對索引列進行處理,如:TRIM,TO_DATE,類型轉換等操作,破壞索引,使用全表掃描,影響SQL執行效率

15.避免在索引列上使用IS NULL和IS NOT NULL
  避免在索引中使用任何可以爲空的列,ORACLE將無法使用該索引
  對於單列索引,如果列包含空值,索引中將不存在此記錄;
  對於複合索引,如果每個列都爲空,索引中同樣不存在此記錄。如果至少有一個列不爲空,則記錄存在於索引中

  如果唯一性索引建立在表的A列和B列上,並且表中存在一條記錄的A,B值爲(123,null),
  ORACLE將不接受下一條具有相同A,B值(123,null)的記錄插入
  如果所有的索引列都爲空,ORACLE將認爲整個鍵值爲空,而空不可能等於空,因此你可以插入1000條具有相同鍵值的記錄,當然它們都是空!
  因爲空值不存在於索引列中,所以WHERE子句中對索引列進行空值比較將使ORACLE停用該索引

  低效(索引失效)
  SELECT …
  FROM DEPARTMENT
  WHERE DEPT_CODE IS NOT NULL

16.使用UNION-ALL和UNION
  當SQL語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合併,然後在輸出最終結果前進行排序
  如果用UNION ALL替代UNION,這樣排序就不是必要了,效率就會因此得到提高

  需要注意的是,UNION ALL將重複輸出兩個結果集合中相同記錄,因此還是要從業務需求分析使用UNION ALL的可行性


關於索引下列經驗請參考: 
1).如果檢索數據量超過30%的表中記錄數,使用索引將沒有顯著的效率提高
2).在特定情況下,使用索引也許會比全表掃描慢,但這是同一個數量級上的差距;而通常情況下,使用索引比全表掃描要快幾倍乃至幾千倍!

其他具體內容請參考《ORACLE SQL性能優化系列》

17.使用PrepareStatement
在同一個方法中,當循環使用SQL文時,爲了提高性能,
請使用PreparedStatement。注意,
僅限使用於少數的模塊。
方法如下:

? PreparedStatement stmt
   = conn.prepareStatement("select a from TABLE_A where b=? c=?");

 for(?? ){
???? stmt.setInt(1, varB);     
???? stmt.setString(2, varC);     
?   ResultSet rst = stmt.executeQuery();

 }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章