程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> SqlServer數據庫 >> 關於SqlServer >> SQL語句的優化

SQL語句的優化

編輯:關於SqlServer
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   &nbsp;    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順序,減少查詢的次數。如:
&nbsp;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
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved