1. 程式人生 > 實用技巧 >oracle系列學習----複雜查詢的優化

oracle系列學習----複雜查詢的優化

實驗資料和表

create  table  test1
(
    FID  NUMBER(6) NOT NULL,
    FBillNo VARCHAR2(10),
    FDate  DATE  DEFAULT to_date(to_char(SYSDATE,'yyyy-mm-dd'),'yyyy-mm-dd')
)
create sequence seq_test1
       minvalue 1  --最小值
       nomaxvalue --最大值
       start with 1 --起始值
       increment by 1  --增長基數
       nocycle  --
不迴圈,一直增加 nocache ; create trigger tri_test1_ins before insert on test1 for each row when (new.FID is null) begin select seq_test1.nextval into:new.FID from dual; end; insert into test1(FBillNo , FDate) values ('FB001' ,to_date('2018-6-1','yyyy-mm-dd')); insert
into test1(FBillNo , FDate) values ('FB002' , to_date('2018-6-3','yyyy-mm-dd')); insert into test1(FBillNo , FDate) values ('FB003' , to_date('2018-2-1','yyyy-mm-dd')); insert into test1(FBillNo , FDate) values ('FB003' , to_date('2018-2-3','yyyy-mm-dd')); DROP TABLE test2 create table test4 ( FID
NUMBER(6), FBillNo VARCHAR2(10), FDate DATE DEFAULT to_date(to_char(SYSDATE,'yyyy-mm-dd'),'yyyy-mm-dd') ) create sequence seq_test4 minvalue 1 --最小值 nomaxvalue --最大值 start with 1 --起始值 increment by 1 --增長基數 nocycle --不迴圈,一直增加 nocache ; create trigger tri_test4_ins before insert on test1 for each row when (new.FID is null) begin select seq_test4.nextval into:new.FID from dual; end; insert into test4(FBillNo , FDate) values ('FB2018001' , to_date('2018-6-1','yyyy-mm-dd')); insert into test4(FBillNo , FDate) values ('FB2018002' , to_date('2018-6-3','yyyy-mm-dd')); insert into test4(FBillNo , FDate) values ('FB003' , to_date('2018-2-1','yyyy-mm-dd')); insert into test4(FBillNo , FDate) values ('FB004' , to_date('2018-2-3','yyyy-mm-dd')); update test1 set FBillNo='FB004' where FDate=to_date('2018-2-3','yyyy-mm-dd') update test1 t1 set t1.FBillNo=(SELECT t2.FBillNo FROM t1 left join test4 t2 on FID=t2.FID where t2.FDate>=to_date('2018-6-1','yyyy-mm-dd')) SELECT * FROM test1 truncate table test4 SELECT * FROM test4 select sex as 性別 , count(*) as 人數 from student group by sex select * from ( select t2.name as 姓名 , count(*) as 不及格數 from exam t1 left join student t2 on t1.StudentID=t2.id where t1.score<60 group by t2.name ) t select t2.name as 姓名 , count(*) as 不及格數 from exam t1 left join student t2 on t1.StudentID=t2.id where t1.score<60 group by t2.name having count(*)>=1 create table cust ( numbers VARCHAR2(10), city VARCHAR2(10) ) create table ORDER_test ( id NUMBER(6), custno varchar(100) ) INSERT INTO cust(numbers , city) VALUES('163' , '杭州'); INSERT INTO cust(numbers , city) VALUES('九遊' , '上海'); INSERT INTO cust(numbers , city) VALUES('騰迅' , '杭州'); INSERT INTO cust(numbers , city) VALUES('百度' , '杭州'); INSERT INTO order_test (id , custno) VALUES(1 , '163'); INSERT INTO order_test ( id , custno) VALUES(2 , '163'); INSERT INTO order_test ( id , custno) VALUES(3 , '九遊'); INSERT INTO order_test ( id , custno) VALUES(4 , '九遊'); INSERT INTO order_test ( id , custno) VALUES(5 , '九遊'); INSERT INTO order_test ( id , custno) VALUES(6 , '騰迅'); INSERT INTO order_test ( id , custno) VALUES(7 , NULL); SELECT t1.numbers , COUNT(t2.id) as countno FROM cust t1 LEFT JOIN order_test t2 ON t1.numbers = t2.custno WHERE t1.city = '杭州' GROUP BY t1.numbers HAVING count(t2.id) < 2 ORDER BY countno DESC; create table order1 ( id NUMBER(6) NOT NULL primary key, billno varchar(50), empid int, deptid int ) create sequence seq_order1 minvalue 1 --最小值 nomaxvalue --最大值 start with 1 --起始值 increment by 1 --增長基數 nocycle --不迴圈,一直增加 nocache ; create trigger tri_order1_ins before insert on order1 for each row when (new.ID is null) begin select seq_order1.nextval into:new.ID from dual; end; --新建職員表 create table emp1 ( id int, name varchar(50) ) create table dept1 ( id int, name varchar(50) ) insert into dept1 select '1' , '銷售部' FROM dual union all select '2' , '財務部' FROM dual union all select '3' , '工程部' FROM dual union all select '4' , '資材部' FROM dual union all select '5' , '採購部' FROM dual union all select '6' , '生產部' FROM dual union all select '7' , '計劃部' FROM dual union all select '8' , '總經辦' FROM dual union all select '9' , '技術部' FROM dual union all select '10' , '行政部' FROM dual --插入職員表,有5條記錄 insert into emp1 select '1' , '李明' FROM dual union all select '2' , '陳家傑' FROM dual union all select '3' , '楊生' FROM dual union all select '4' , '李建木' FROM dual union all select '5' , '常青' FROM dual BEGIN declare cid NUMBER(8):=1; cno NUMBER(8):=1000000; empid INT :=1; deptid INT:=1; BEGIN while cid<=cno LOOP if empid>5 THEN empid:=1; END IF; if deptid>10 THEN deptid:=1; END IF; insert into order1 (BILLNO,empid,deptid) VALUES( (SELECT 'order'|| to_char(cid,'0000000') FROM dual) ,empid ,deptid ); cid:=cid+1; empid:=empid+1; deptid:=deptid+1; COMMIT; end loop; end; select COUNT(*)/*t1.billno as 單據編號, t2.name as 職員, t3.name as 部門*/ from order1 t1 left join emp1 t2 on t1.empid=t2.id left join dept1 t3 on t1.deptid=t3.id select COUNT(*)/*t1.billno as 單據編號, t2.name as 職員, t3.name as 部門*/ from order1 t1 left join dept1 t3 on t1.deptid=t3.id left join emp1 t2 on t1.empid=t2.id

1、多個左連線效能實驗 語法1:以職員表排前面 select t1.billno as 單據編號, t2.name as 職員, t3.name as 部門 from order1 t1 left join emp1 t2 on t1.empid=t2.id left join dept1 t3 on t1.deptid=t3.id

語法2:以部門表排前面 select t1.billno as 單據編號, t2.name as 職員, t3.name as 部門 from order1 t1 left join dept1 t3 on t1.deptid=t3.id left join emp1 t2 on t1.empid=t2.id 實驗結果如下: 語法1 語法2 97秒 87秒 93秒 85秒 85秒 86秒 85秒 85秒 92秒 91秒 86秒 83秒 84秒 85秒 83秒 88秒 平均值:88.125 平均值:86.25 結論:效能上差不多。

理論分析: 語法1 語法2 執行1(from):order1 和 emp1笛卡爾積VT1表為100000005=5千萬行資料。 執行1(from):order1和dept1笛卡爾積VT1表為1000000010=10千萬行資料。 執行2(on):篩選後VT2表為1千萬行資料。 執行2(on):篩選後VT2表為1千萬行資料。 執行3(left join):VT3表為1千萬行資料。 執行3(left join):VT3表為1千萬行資料。 執行4(from):VT3和dept1笛卡爾積VT4表為1000000010=10千萬行資料。 執行4(from):order1 和 emp1笛卡爾積VT4表為100000005=5千萬行資料。 執行5(on):篩選後VT5表為1千萬行資料。 執行5(on):篩選後VT5表為1千萬行資料。 執行6(left join):VT6表為1千萬行資料。 執行6(left join):VT6表為1千萬行資料。 理論結論:左連線效能差不多。

2、內連線放置位置效能試驗 實驗之前,我們先刪除4條職員資料: delete from emp1 where id in(2 , 3 , 4 , 5)

語法1:以內聯職員表放置前面 select t1.billno as 單據編號, t2.name as 職員, t3.name as 部門 from order1 t1 inner join emp1 t2 on t1.empid=t2.id left join dept1 t3 on t1.deptid=t3.id

語法2:以內聯職員表放置後面 select t1.billno as 單據編號, t2.name as 職員, t3.name as 部門 from order1 t1 left join dept1 t3 on t1.deptid=t3.id inner join emp1 t2 on t1.empid=t2.id

實驗結果如下: 語法1 語法2 43 35 35 35 35 36 36 39 35 35 35 35 平均值:36.5 平均值:35.83 結論:效能上差不多。

理論分析: 語法1 語法2 執行1(from):order1 和 emp1笛卡爾積VT1表為100000001=1千萬行資料。 執行1(from):order1和dept1笛卡爾積VT1表為1000000010=10千萬行資料。 執行2(on):篩選後VT2表為2百萬行資料。 執行2(on):篩選後VT2表為1千萬行資料。 執行3(from):VT2和dept1笛卡爾積VT3表為200000010=2千萬行資料。 執行3(left join):VT3表為1千萬行資料。 執行4(on):篩選後VT4表為2百萬行資料。 執行4(from):order1 和 emp1笛卡爾積VT4表為100000001=1千萬行資料。 執行5(left join):篩選後VT5表為2百萬行資料。 執行5(on):篩選後VT5表為2百萬行資料。 理論結論:語法1效能優於語法2 此處發現理論結論與實驗結果不同。這是什麼情況?

3、查詢優化器自動優化最佳結果 查詢優化器(簡稱優化器)是負責生成 SQL 語句的有效執行計劃的 SQL Server 資料庫引擎元件,具體地說,查詢優化器是SQL Server針對使用者的請求進行內部優化,生成(或重用)執行計劃並傳輸給儲存引擎來操作資料,最終返回結果給使用者的元件。 它是關係型資料庫管理系統的核心之一,決定對特定的查詢使用哪些索引、哪些關聯演算法、從而使其高效執行,它是優化器中最重要的元件之一。

檢視語法1的實際執行計劃:

檢視語法2的實際執行計劃:

結論:對比語法1和語法2的執行計劃一模一樣,所以實驗結果的效能差不多。

由於我們本次試驗的查詢相對較簡單,故執行計劃一模一樣。但是,在實際情況下 可能查詢語句很複雜,資料量很龐大的情況下,執行計劃有時候也不靈驗,需要我們對連線順序進行調整優化,如早期寫的一個SQL語句: select t1.FBillNo , t1.FInterID , t2.FEntryID , t1.FCustID , t6.FBillNo as FOutBillNo, t7.fbillno as FWWBillNo_New , t8.FItemID , t4.FErpClsID , t8.FBatchNo , t2.FAuxQty , t8.FAuxPrice , t2.FAuxQty * t8.FAuxPrice as FAmount , 2 as CBType from SEOrder t1 inner join SEOrderEntry t2 on t1.FInterID=t2.FInterID inner join t_ICItem t4 on t2.FItemID=t4.FItemID inner join t_Organization t5 on t5.FItemID=t1.FCustID left join ( select t061.FOrderInterID , t061.FOrderEntryID , t062.FBillNo from ICStockBillEntry t061 inner join ICStockBill t062 on t061.FInterID=t062.FInterID where t062.FTranType=21 and t061.FAuxPrice<>0 ) t6 on t2.FInterID=t6.FOrderInterID and t2.FEntryID=t6.FOrderEntryID inner join #tmp1 t7 on t7.fitemid =t2.FItemID inner join ( select t081.FItemID , t081.FBatchNo , t082.FBillNo , t081.FAuxPrice from ICStockBillEntry t081 inner join ICStockBill t082 on t081.FInterID=t082.FInterID where t082.FTranType=5 ) t8 on t8.FBillNo=t7.FBillNo and t8.FItemID=t7.FItemID and t8.FBatchNo=t7.FBatchNo where (1=1) and t1.FStatus>0 and t1.FCancellation=0 and t1.FBillNo >= @FBillNo1 and t1.FBillNo <= case when @FBillNo2='' then (select MAX(FBillNo) from SEOrder) else @FBillNo2 end and t4.FNumber >= @FNumber1 and t4.FNumber <= case when @FNumber2='' then (select MAX(FNumber) from t_ICItem) else @FNumber2 end and t5.FNumber >= @FCustID1 and t5.FNumber <= case when @FCustID2='' then (select MAX(FNumber) from t_Organization) else @FCustID2 end and t1.FDate >= @FDate1 and t1.FDate <= case when @FDate2='' then '2100-1-1' else @FDate2 end and t6.FBillNo is null

4、為什麼理論的原理沒效果呢? 絕大多數情況下,在MS SQL Server或Oracle這兩個最主流的大型資料庫中,在近幾年的版本中,單條語句中,Where條件中,各條件的順序對查詢的效率、速度,沒有明顯影響。MS SQL Server或Oracle已經考慮到了Where條件的優化,它們會對這些條件進行智慧分析,進行最佳的方式查詢。

網路上所傳Where對效能有影響的說法,要麼是針對過往老舊的資料庫系統而言,要麼就是想當然,要麼就是對一些特定的小資料庫系統或設計不夠好的資料庫系統而言的,至少,對當前主流版本的MS SQL Server或Oracle,這種說法是錯的。

但是,在多語句,特別是使用臨時表進行查詢的情況下,每條語句的結果是需要設計者認真分析的,要儘可能減少中間資料量,以達到最大效率。

如果你不相信,可以自已親自去MS SQL Server或Oracle中試試,建議MS SQL Server 2008 R2以上版本 或 Oracle 10G 以上版本中,資料量大於10萬條記錄中,以兩個會帶來較大記錄條數差別的條件,改變它們在where中的順序去查,你會發現,查詢所需時間基本上是一樣的(請多次重複,並最好在沒有干擾的情況下)。

最後想說,IT技術發展到現在,這些問題,大型公司早就考慮到了的。

5、百萬級資料庫優化方案 1.對查詢進行優化,要儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。

2.應儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: select id from t where num is null 最好不要給資料庫留NULL,儘可能的使用 NOT NULL填充資料庫。 備註、描述、評論之類的可以設定為 NULL,其他的,最好不要使用NULL。

不要以為 NULL 不需要空間,比如:char(100) 型,在欄位建立時,空間就固定了, 不管是否插入值(NULL也包含在內),都是佔用 100個字元的空間的,如果是varchar這樣的變長欄位, null 不佔用空間。 可以在num上設定預設值0,確保表中num列沒有null值,然後這樣查詢: select id from t where num = 0

3.應儘量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進行全表掃描。

4.應儘量避免在 where 子句中使用 or 來連線條件,如果一個欄位有索引,一個欄位沒有索引,將導致引擎放棄使用索引而進行全表掃描,如: select id from t where num=10 or Name = 'admin' 可以這樣查詢: select id from t where num = 10 union all select id from t where Name = 'admin'

5.in 和 not in 也要慎用,否則會導致全表掃描,如: select id from t where num in(1 , 2 , 3)

對於連續的數值,能用 between 就不要用 in 了: select id from t where num between 1 and 3

很多時候用 exists 代替 in 是一個好的選擇: select num from a where num in(select num from b) 用下面的語句替換: select num from a where exists(select 1 from b where num=a.num)

6.下面的查詢也將導致全表掃描: select id from t where name like '%abc%' 若要提高效率,可以考慮SQL的全文檢索技術。 另一種做法:根據輸入條件,先查詢和確定符合條件的結果,並把相關記錄儲存在一個臨時表中,然後再用臨時表去做複雜關聯。

7.如果在 where 子句中使用引數,也會導致全表掃描。因為SQL只有在執行時才會解析區域性變數,但優化程式不能將訪問計劃的選擇推遲到執行時;它必須在編譯時進行選擇。然 而,如果在編譯時建立訪問計劃,變數的值還是未知的,因而無法作為索引選擇的輸入項。如下面語句將進行全表掃描: select id from t where num = @num 可以改為強制查詢使用索引: select id from t with(index(索引名)) where num = @num

8.應儘量避免在 where 子句中對欄位進行表示式操作,這將導致引擎放棄使用索引而進行全表掃描。如: select id from t where num/2 = 100 應改為: select id from t where num = 100*2

9.應儘量避免在where子句中對欄位進行函式操作,這將導致引擎放棄使用索引而進行全表掃描。如: select id from t where substring(name , 1 , 3) = ’abc’ --name以abc開頭的id

select id from t where datediff(day , createdate , ’2005-11-30') = 0 --'2005-11-30’ --生成的id

應改為: select id from t where name like 'abc%' select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'

10.不要在 where 子句中的“=”左邊進行函式、算術運算或其他表示式運算,否則系統將可能無法正確使用索引。

11.在使用索引欄位作為條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個欄位作為條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓欄位順序與索引順序相一致。

12.不要寫一些沒有意義的查詢,如需要生成一個空表結構: select col1 , col2 into #t from t where 1=0 這類程式碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣: create table #t(…)

13.Update 語句,如果只更改1、2個欄位,不要Update全部欄位,否則頻繁呼叫會引起明顯的效能消耗,同時帶來大量日誌。

14.對於多張大資料量(這裡幾百條就算大了)的表JOIN,要先TOP分頁再JOIN,否則邏輯讀會很高,效能很差。

  1. select count(*) from table;這樣不帶任何條件的count會引起全表掃描,並且沒有任何業務意義,是一定要杜絕的。

16.索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。

17.應儘可能的避免更新 clustered 索引資料列,因為 clustered 索引資料列的順序就是表記錄的物理儲存順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引資料列,那麼需要考慮是否應將該索引建為 clustered 索引。

18.儘量使用數字型欄位,若只含數值資訊的欄位儘量不要設計為字元型,這會降低查詢和連線的效能,並會增加儲存開銷。這是因為引擎在處理查詢和連 接時會逐個比較字串中每一個字元,而對於數字型而言只需要比較一次就夠了。

19.儘可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長欄位儲存空間小,可以節省儲存空間,其次對於查詢來說,在一個相對較小的欄位內搜尋效率顯然要高些。

20.任何地方都不要使用 select * from t,用具體的欄位列表代替“*”,不要返回用不到的任何欄位。

21.儘量使用表變數來代替臨時表。如果表變數包含大量資料,請注意索引非常有限(只有主鍵索引)。

  1. 避免頻繁建立和刪除臨時表,以減少系統表資源的消耗。臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重複引用大型表或常用表中的某個資料集時。但是,對於一次性事件, 最好使用匯出表。

23.在新建臨時表時,如果一次性插入資料量很大,那麼可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果資料量不大,為了緩和系統表的資源,應先create table,然後insert。

24.如果使用到了臨時表,在儲存過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。

25.儘量避免使用遊標,因為遊標的效率較差,如果遊標操作的資料超過1萬行,那麼就應該考慮改寫。

26.使用基於遊標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。

27.與臨時表一樣,遊標並不是不可使用。對小型資料集使用 FAST_FORWARD 遊標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的資料時。在結果集中包括“合計”的例程通常要比使用遊標執行的速度快。如果開發時 間允許,基於遊標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。

28.在所有的儲存過程和觸發器的開始處設定 SET NOCOUNT ON ,在結束時設定 SET NOCOUNT OFF 。無需在執行儲存過程和觸發器的每個語句後向客戶端傳送 DONE_IN_PROC 訊息。 29.儘量避免大事務操作,提高系統併發能力。

30.儘量避免向客戶端返回大資料量,若資料量過大,應該考慮相應需求是否合理。

實際案例分析:拆分大的 DELETE 或INSERT 語句,批量提交SQL語句   如果你需要在一個線上的網站上去執行一個大的 DELETE 或 INSERT 查詢,你需要非常小心,要避免你的操作讓你的整個網站停止相應。因為這兩個操作是會鎖表的,表一鎖住了,別的操作都進不來了。   Apache 會有很多的子程序或執行緒。所以,其工作起來相當有效率,而我們的伺服器也不希望有太多的子程序,執行緒和資料庫連結,這是極大的佔伺服器資源的事情,尤其是記憶體。   如果你把你的表鎖上一段時間,比如30秒鐘,那麼對於一個有很高訪問量的站點來說,這30秒所積累的訪問程序/執行緒,資料庫連結,開啟的檔案數,可能不僅僅會讓你的WEB服務崩潰,還可能會讓你的整臺伺服器馬上掛了。   所以,如果你有一個大的處理,你一定把其拆分,使用 LIMIT oracle(rownum), sqlserver(top)條件是一個好的方法。下面是一個mysql示例: while(1){   //每次只做1000條    mysql_query(“delete from logs where log_date <= ’2012-11-01’ limit 1000”);   if(mysql_affected_rows() == 0){      //刪除完成,退出!      break;   } //每次暫停一段時間,釋放表讓其他程序/執行緒訪問。 usleep(50000) }