----------------------------SQL語句的優化以及索引的應用範圍---------------------

SQL優化的原則是:將一次操作需要讀取的BLOCK數減到最低。
調整不良SQL通常可以從以下幾點切入:
檢查不良的SQL,考慮其寫法是否還有可優化內容;
檢查子查詢考慮SQL子查詢是否可以用簡單連接的方式進行重新書寫;
檢查優化索引的使用;
考慮數據庫的優化器;
查詢的一般規則
Ø       明確指出檢索的字段,儘量減少對多餘的列與多餘的行讀取。
禁止使用 select * from table ……的方式訪問表。
Ø       在一個SQL語句中,如果一個where條件過濾的數據庫記錄越多,定位越準確,則該where條件越應該前移。
Ø       查詢時儘可能使用索引覆蓋。即對SELECT的字段建立複合索引,這樣查詢時只進行索引掃描,不讀取數據塊。
Ø       在判斷有無符合條件的記錄時不要用SELECT COUNT (*) 語句。 如:
BF: select count(*) from table where condition
在MSSQL和Sybase中,使用 if exists (select 1 from table_name where condition)性能較好,在Oracle中,使用select 1 from table_name where condition較好。
Ø       內層限定原則
在拼寫SQL語句時,將查詢條件分解、分類,並儘量在SQL語句的最裏層進行限定,以減少數據的處理量。
應絕對避免在order by子句中使用表達式。
  
正確使用INDEX的SQL
如果和查詢條件相關的column上有建index,以下幾點能幫助SQL正確的使用index 。
Ø       避免顯式或隱含的類型轉換。
where子句中存在數據類型隱形轉換的,如用Numeric 型和 Int型的列的比較時,不能使用index。
Ø       WHERE子句中任何對列的操作都將無法使用index,它包括數據庫函數、計算表達式等等,所以要儘量減少在=左邊的列的運算。如:
BF:select staff_no, staff_name from staff_member where salary*2 <= 10000;
RP:select staff_no, staff_name from staff_member where salary <= 5000;
Ø       WHERE子句中使用不等於(<>)運算的,將無法使用index。可以用union all改寫。如:
BF:select staff_no, staff_name from staff_member where dept_no<>2001;
RP:select staff_no, staff_name from staff_member where dept_no < 2001
Union all
select staff_no, staff_name from staff_member where dept_no > 2001;
Oralce中可以考慮使用函數索引。
Ø       WHERE子句中使用substr字符串函數的,將無法使用index,可以用like改寫。如:
BF:select staff_no, staff_name from staff_member where substr(last_name,1,4)=’FRED’;
RP:select staff_no, staff_name from staff_member where last_name like ’FRED%’;
Ø       WHERE子句中‘%’通配符在第一個字符的,將無法使用index。如:
select staff_no, staff_name from staff_member where first_name like ‘%DON’;
這種情況的優化方式比較複雜,在後面有關index優化的內容中我們介紹一種在oracle中使用反向索引的優化方式。
Ø       LIKE語句後面不能跟變量,否則也不會使用索引。
where Prod_name like :v_name || '%' -- 不會使用索引
如果一定要使用變量,可以使用如下技巧:
where Prod_name between :v_name and :v_name || chr(255) -- 會使用索引
Ø       WHERE子句中使用IS NULL和IS NOT NULL不會使用索引。好的設計習慣是表中儘量不使用允許爲空的字段,可以根據業務邏輯,將字段設爲NOT NULL的同時,提供一個DEFAULT值。另外,當表中建有索引的字段包含NULL時,索引的效率會降低。
Ø       WHERE子句中使用字符串連接(||)的,將無法使用index。我們應該改寫這個查詢條件。如:
BF:select staff_no, staff_name from staff_member
where first_name||' '||last_name ='Beill Cliton';
RP:select staff_no, staff_name from staff_member
where first_name = ‘Beill’
and last_name ='Cliton';
Ø       WHERE條件中使用’in’子句的情況,如:
BF:select count(*) from staff_member
Where id_no in (‘0’,’1’);
WHERE條件中的'in'在邏輯上相當於'or',所以語法分析器會將in ('0','1')轉化爲id_no ='0' or id_no='1'來執行。我們期望它會根據每個or子句分別查找,再將結果相加,這樣可以利用id_no上的索引;但實際上(根據showplan),它卻採用了"OR策略",即先取出滿足每個or子句的行,存入臨時數據庫的工作表中,再建立唯一索引以去掉重複行,最後從這個臨時表中計算結果。我們可以將or子句分開:
RP:DECLARE
a integer;
        b integer;
BEGIN
   select count(*) into a from stuff where id_no='0';
   select count(*) into b from stuff where id_no='1';
a := a + b;
END;
Ø       如果在table上創建了一個順序爲col1,col2,col3的複合index時,在查詢中只有以下三種where條件子句能有效的使用index:
…where col1= @col1 and col2= @col2 and col3= @col3;
…where col1= @col1 and col2= @col2;
…where col1= @col1
子查詢的調整
a.       調整具有IN和EXISTS子句的子查詢
具有IN的子查詢:
BF:
   select emp_id from EMP
   where dep_id IN ( select dep_id from DEP
where dep_no = ‘001’);
具有EXISTS的子查詢:
BF:
   select emp_id from EMP e
   where exists ( select dep_id from DEP d
where e.dep_id = d.dep_id
and d.dep_no = ‘001’);
用對等連接調整具有IN和EXISTS的子查詢:
上面的例子中的子查詢有兩種情況,dep_id unique和nounique。
當dep_id是unique,
RP:
   select e.emp_id from EMP e, DEP d
   where e..dep_id = d.dep_id and d.dep_no = ‘001’;
當dep_id是nounique,                                                                                                                                                  
RP:
select e.emp_id from EMP e,
(select distinct dep_id from DEP where dep_no = ‘001’ ) d
where e..dep_id = d.dep_id;
需要注意的是,具有IN子句的非關聯子查詢和EXISTS子句的關聯子查詢,Oracle的優化器雖然能將其轉換爲標準的連接操作,但Oracle轉換用的是NESTED LOOPS連接操作,而且有很多其他因素支配着SQL優化器是否將一個子查詢自動轉換爲一個連接操作。首先,連接操作的兩個數據表列通常都應該有唯一的數據索引。所以,我們應該自己重寫這些子查詢。
b. 調整具有NOT IN和NOT EXISTS子句的子查詢
具有NOT IN的子查詢:
BF:
          select emp_id from EMP
          where dep_id NOT IN ( select dep_id from DEP
where dep_no = ‘001’);
具有NOT EXISTS的子查詢:
BF:
select emp_id from EMP e
   where NOT EXISTS ( select dep_id from DEP d
where e dep_id = d. dep_id and .d.dep_no = ‘001’);
用外聯接調整具有NOT IN和NOT EXISTS的子查詢
RP:
   select e.emp_id from EMP e,DEP d
   where e.dep_id = d.dep_id(+)
        and d.dep_id is null
        and d.dep_no (+)= ‘001’;
c. 調整具有自連接的子查詢
所謂具有自連接的子查詢,實際上是在一種特殊需求下使用的具有IN子句的關聯子查詢。我們可以用連接的方式重寫該子查詢。例如以下的需求:
查詢每個部門中工資高於該部門平均工資的員工ID,名稱,工資,部門ID:
  BF:
select a.emp_id ,a.emp_name,a.emp_salary,a.dep_id from salary a
where a.emp_salary >
(select avg(b.emp_salary) from salary b where b.dep_id = a.dep_id );
   RP:
select a.emp_id ,a.emp_name,a.emp_salary,a.dep_id
from salary a,
(select dep_id,avg(emp_salary) avg_salary from salary group by dep_id) b
where a.dep_id = b.dep_id
and a.emp_salary > b.avg_salary;
使用綁定變量優化SQL
使用綁定變量可以提高Library Cache的Hit Ratio,減少SQL語句的重編譯,從而達到提高查詢效率的目的。
BF:
SQL> alter system flush shared_pool;
系統已更改。
SQL> declare
2    type rc is ref cursor;
3    l_rc rc;
4    l_dummy all_objects.object_name%type;
5    l_start number default dbms_utility.get_time;
6 begin
7    for i in 1..1000
8    loop
9       open l_rc for
10       'select object_name
11           from all_objects
12        where object_id ='|| i;
13        fetch l_rc into l_dummy;
14        close l_rc;
15     end loop;
16     dbms_output.put_line
17     (round((dbms_utility.get_time - l_start)/100,2)||' seconds...');
18 end;
19 /
18.36 seconds...
PL/SQL 過程已成功完成。
這是從all_objects進行的單條查詢,在循環1000次的情況下,Oracle對其進行的1000次編譯,需要18.36秒才能完成。
RP:
SQL> alter system flush shared_pool;
系統已更改。
SQL> declare
2    type rc is ref cursor;
3    l_rc rc;
4    l_dummy all_objects.object_name%type;
5    l_start number default dbms_utility.get_time;
6 begin
7    for i in 1..1000
8    loop
9       open l_rc for
10       'select object_name
11           from all_objects
12        where object_id =:x'
13        using i;
14        fetch l_rc into l_dummy;
15        close l_rc;
16     end loop;
17     dbms_output.put_line
18     (round((dbms_utility.get_time - l_start)/100,2)||' seconds...');
19 end;
20 /
.56 seconds...
PL/SQL 過程已成功完成。
這是改用綁定變量之後的結果。這時Oracle對該PL/SQL 進行1次編譯。執行時間明顯減少。我在分別提交這兩個PL/SQL 之前都執行了alter system flush shared_pool;命令,以保證對比結果的真實有效。
減少查詢的次數
在一次執行多條SQL時,考慮正確的SQL順序,減少查詢的次數。如:
BF:
IF NOT EXISTS(SELECT count(*) FROM Item WHERE fchrItemID=@chrItemID and fchrA=@chrA)
    INSERT INTO Item (fchrItemID ,fchrItem ,fchrItemName , fchrA , flotQ , flotMQ )
VALUES (@chrItemID , @chrItem , @chrItemName , @chrA , -1*@lotQ , -1*@lotMQ )
else
      UPDATE Item SET flotQ=flotQ-@lotQ,flotMQ=flotMQ-@lotMQ
      WHERE fchrItemID=@chrItemID and fchrA=@chrA
對於這個SQl來說,select和update對Item做了兩次查詢操作。實際上我們只需要一次查詢就可以實現功能。
RP:
UPDATE Item SET flotQ=flotQ-@lotQ,flotMQ=flotMQ-@lotMQ
WHERE fchrItemID=@chrItemID and fchrA=@chrA
IF @@rowcount = 0
    INSERT INTO @List (fchrItemID ,fchrItem ,fchrItemName , fchrA , flotQ , flotMQ )
    VALUES (@chrItemID , @chrItem , @chrItemName , @chrA , -1*@lotQ , -1*@lotMQ )
用union all 代替 union
數據庫執行union操作,首先先分別執行union兩端的查詢,將其放在臨時表中,然後在對其進行排序,過濾重複的記錄。
BF:select a1,b1,c1 from table1               ----query A
       union
       select a2,b2,c2 from table2              ----query B
  
當已知的業務邏輯決定query A和query B中不會有重複記錄時,應該用union all代替union,以提高查詢效率。
    RP: select a1,b1,c1 from table1              ----query A
      union all
select a2,b2,c2 from table2               ----query B

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/binger819623/archive/2009/08/07/4418501.aspx

發佈了111 篇原創文章 · 獲贊 13 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章