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;
Ø &
nbsp; 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 =
style="FONT-SIZE: 10pt; COLOR: blue"> ‘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條件子句能有效的使用
black">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, nbs
p;
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 obje
ct_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