PLSQL最佳實踐

  • 存在性檢查
--正確姿勢:
SELECT COUNT(*) FROM dual WHERE EXISTS (SELECT 1 FROM yxxc_gzb);
--不正確姿勢:
SELECT COUNT(*) FROM yxxc_gzb;
  • 提防 DDL 提交事務(wù)
DDL 語句的第一步就是 COMMIT,然后才是執(zhí)行 DDL 本身物蝙,無任命令是否執(zhí)行成功院仿,
都已經(jīng)提交。所以不要在事務(wù)中使用 DDL 語句
  • 減少對 sysdate 的調(diào)用
sysdate 函數(shù)在經(jīng)常會被使用,但它是函數(shù)熟吏,一定要注意將 sysdate 移出循環(huán)拐揭,先賦予
變量,然后在循環(huán)中引用變量火脉。
  • 把靜態(tài)表達(dá)式遷出循環(huán)和 SQL 語句
PROCEDURE show_customers(prefix_in IN VARCHAR2, state_in IN VARCHAR2) IS 
c_state CONSTANT mfe_customers.state%TYPE := upper(state_in);
c_output_prefix CONSTANT VARCHAR2(32767) := to_char(SYSDATE, 'Mon DD, YYYY') || ' ' ||
                                            upper(prefix_in) || ' ' ||
                                            c_state;
CURSOR customers_cur IS
  SELECT last_name, first_name, city
    FROM mfe_customers
   WHERE state = c_state;
BEGIN
  FOR customer_rec IN customers_cur LOOP
    dbms_output.put_line(c_output_prefix || ' ' || customer_rec.first_name || ' ' ||
                         customer_rec.last_name);
  END LOOP;
END show_customers;
  • 與NULL值進(jìn)行比較或邏輯運(yùn)算的時候千萬不要使用“=”牵舵,“<>”操作符,要用IS NULL操作符
    (NULL值并不等于其它的所有值倦挂,甚至不等于另外的一個NULL值畸颅,程序代碼應(yīng)該顯式的處理NULL值)
--假設(shè)業(yè)務(wù):兩個為空的字符相等
DECLARE
  v_first_name  VARCHAR2(100);
  v_second_name VARCHAR2(100);
BEGIN
  v_first_name  := '';
  v_second_name := '';
  --不正確姿勢
  IF v_first_name = v_second_name THEN
    dbms_output.put_line('v_first_name和v_second_name相等');
  ELSE
    dbms_output.put_line('v_first_name和v_second_name不相等');
  END IF;
  --正確姿勢
  IF v_first_name = v_second_name THEN
    dbms_output.put_line('v_first_name和v_second_name相等');
  ELSIF v_first_name IS NULL AND v_second_name IS NULL THEN
    dbms_output.put_line('v_first_name和v_second_name相等');
  ELSE
    dbms_output.put_line('v_first_name和v_second_name不相等');
  END IF;
END;
--IF模版
DECLARE
 v_action VARCHAR2(100);
BEGIN
 IF v_action = 'ADD' THEN
   --增加操作
 ELSIF v_action = 'UPDATE' THEN
   --修改操作
 ELSIF v_action IS NULL THEN
   --處理NULL值
 ELSE 
   --處理其它情況
 ENF IF;
END;
  • 避免在聲明部分指定默認(rèn)值,特別這些值是函數(shù)調(diào)用返回的值
塊的異常部分只會處理塊的執(zhí)行部分拋出的異常方援,假如聲明塊中拋出異常没炒,是不會被異常處理部分捕獲的,而會往調(diào)用方拋出犯戏。
  • 清理PLSQL塊中的數(shù)據(jù)
比如:動態(tài)游標(biāo)使用完記得關(guān)閉送火,很多情況是出現(xiàn)異常沒關(guān)閉。
  • 最好不要在包說明中定義變量先匪,每個對包有執(zhí)行權(quán)限的人都可以看到甚至修改
如果有需要种吸,建議在包體定義變量,并且通過set/get控制對包數(shù)據(jù)的訪問(意味著在過程或函數(shù)中胚鸯,不要直接引用變量骨稿,而是通過get方法)
  • 在復(fù)雜的IF ELSIF ELSE 語句中,可以建議使用case語句來替代(不要忘記else語句),增強(qiáng)可讀性坦冠。

  • PL/SQL的異常處理機(jī)制

image
  • 避免在異常部分中嵌入應(yīng)用程序邏輯
  • 避免通過錯誤編碼引用異常形耗,若Oracle沒給該異常命名,自己給它命一個
    • 前:


      image
    • 后:


      image
  • PL/SQL查詢
    • 把查詢的值寫入記錄中辙浑,即使用%rowtype
    • 只有當(dāng)需要檢索多行數(shù)據(jù)時激涤,才使用游標(biāo)for循環(huán)
  • PL/SQL修改
    • 要始終顯式列出INSERT語句中所使用的數(shù)據(jù)庫表的列
    • SQL%屬性總是會話中最近執(zhí)行的隱式游標(biāo)
注意:當(dāng)更新或者刪除那些“本應(yīng)該”存在的數(shù)據(jù)時,要檢查SQL%ROWCOUNT屬性判呕,以驗證所完成動作的正確性倦踢。

SQL%ROWCOUNT:受DML語句影響的數(shù)據(jù)行的數(shù)量
SQL%FOUND:該語句影響至少一行數(shù)據(jù)時,返回TRUE
SQL%NOTFOUND:該語句影響至少一行數(shù)據(jù)時侠草,返回FALSE
  • 動態(tài)SQL

    • 把動態(tài)SQL字符串賦給一個變量辱挥,需要時盡可能用占位符,即綁定變量
    • 避免SQL串聯(lián)边涕,防止SQL注入
    • 對于存在動態(tài)SQL語句的程序(模式級別或者包內(nèi)過程)來說晤碘,最好使用AUTHID CURRENT_USER定義為調(diào)用者模式,可最大程度降低風(fēng)險
  • 函數(shù)的要點就是能夠返回一個值(標(biāo)量功蜓、集合园爷、記錄),如果通過使用OUT式撼、IN OUT參數(shù)列表返回值的話童社,則函數(shù)的功能用途顯得不明顯了,此時可以考慮使用過程著隆。

如果需要同時返回多項信息扰楼,可采用如下方式:
1、返回一個記錄或集合
2美浦、將函數(shù)變成過程
3灭抑、將獨(dú)立的函數(shù)分解成復(fù)合函數(shù),并全部返回標(biāo)量值抵代??
  • 對于子程序而言忘嫉,參數(shù)列表應(yīng)該明確地描述程序?qū)崿F(xiàn)其功能時需要的參數(shù)荤牍,以及有可能返回的值。
當(dāng)在一個主過程包含子過程或子函數(shù)時庆冕,如果子過程等需要的參數(shù)包括主過程傳遞進(jìn)來的某些參數(shù)時康吵,
最好在子過程的參數(shù)中也進(jìn)行定義下,而不要說直接拿主過程的參數(shù)访递,
之前寫代碼時一直在考慮是否要定義形參晦嵌,猶豫不決,導(dǎo)致有些子過程有定義,有些則沒有惭载,不太規(guī)范旱函。
  • 如果需要往已有的程序增加參數(shù)時,要考慮原有調(diào)用程序能夠不受影響描滔,可使用以下方式:
1棒妨、確保新增的IN參數(shù)都有默認(rèn)值,當(dāng)然含长,如果參數(shù)是OUT或IN OUY的話券腔,則不行
2、增加重載函數(shù)/過程拘泞,這時要注意纷纫,千萬不要將原有函數(shù)/過程的代碼COPY過來,而應(yīng)創(chuàng)建另一個“核心”程序供其調(diào)用
  • 當(dāng)一個過程實現(xiàn)邏輯比較復(fù)雜時陪腌,最好對其進(jìn)行功能拆分(逐步求精法辱魁、自頂向下設(shè)計法),使用本地子程序偷厦,使主程序執(zhí)行部分可進(jìn)行調(diào)用商叹,使得邏輯更清晰,把實現(xiàn)細(xì)節(jié)進(jìn)行封裝只泼。
寫代碼時剖笙,可以先進(jìn)行程序接口設(shè)計,將實現(xiàn)邏輯進(jìn)行拆分请唱,跟JAVA的接口設(shè)計類似弥咪,
最后再對其具體實現(xiàn)詳細(xì)編碼,讓編碼者或者他人看到程序執(zhí)行部分時十绑,能夠很好的理解其實現(xiàn)意圖
  • 盡可能把更多的業(yè)務(wù)規(guī)則封裝聚至、隱藏在函數(shù)中,供其它過程調(diào)用本橙。
所有軟件項目的一個特性是:永遠(yuǎn)不會變化的是事物一直在變化扳躬,如業(yè)務(wù)需求、數(shù)據(jù)結(jié)構(gòu)或用戶界面等等甚亭。
我們最好能夠編寫出能夠容易適應(yīng)這些變化的代碼贷币。
  • 不要在函數(shù)中出現(xiàn)多個出口,遵循“一個入口只能有一個出口”亏狰,另外役纹,在異常處理部分也要保留RETURN語句。
  • 返回布爾值的函數(shù)絕對不能返回NULL(確保返回TRUE或者FALSE)暇唾,這點在進(jìn)行條件判斷的時候尤其要注意促脉。
-- 是否包含指定數(shù)字(6或者8)

--不好的函數(shù):
CREATE OR REPLACE FUNCTION is_contains_special_number(i_str IN VARCHAR2)
  RETURN BOOLEAN IS
BEGIN
  RETURN instr(i_str, '6') > 0 OR instr(i_str, '6') > 0;
END is_contains_special_number;
--當(dāng)傳入NULL時辰斋,輸出contains special number,明顯有問題
BEGIN
  IF NOT is_contains_special_number('') THEN
    dbms_output.put_line('not contains special number');
  ELSE
    dbms_output.put_line('contains special number');
  END IF;
END;
--姿勢換一下瘸味,調(diào)用方作處理宫仗,但治標(biāo)不治本,不能確保所有調(diào)用方都這么處理:
BEGIN
  IF NOT NVL(is_contains_special_number(''),FALSE) THEN
    dbms_output.put_line('not contains special number');
  ELSE
    dbms_output.put_line('contains special number');
  END IF;
END;
--修改函數(shù)(確保返回TRUE或者FALSE):
CREATE OR REPLACE FUNCTION is_contains_special_number(i_str IN VARCHAR2)
  RETURN BOOLEAN IS
  v_result BOOLEAN;
BEGIN
  IF instr(i_str, '6') > 0 OR instr(i_str, '6') > 0 THEN
    v_result:=TRUE;
  ELSE --當(dāng)i_str為空時硫戈,也返回FALSE
    v_result:=FALSE;
  END IF;
  RETURN v_result;
END is_contains_special_number;
  • 包的最佳實踐:

    • 使用程序包組合功能相關(guān)的程序锰什,避免編寫模式別的過程或函數(shù)
    總是從程序包開始,即時此時包中僅僅只有一個程序丁逝,將來也很有可能會有很多程序
    
    • 讓程序包的關(guān)注面范圍小而窄
  • 盡可能使用bull colect和forall進(jìn)行批量提取數(shù)據(jù)汁胆,減少上下文切換

    • 關(guān)于bull colect的建議

      • 更多內(nèi)存,空間換時間
      注意:
      PL/SQL集合消耗的內(nèi)存來自于PGA霜幼,而不是SGA嫩码,即內(nèi)存消耗是以每個會話為基礎(chǔ)的。
      假如程序中bull collect需要耗費(fèi)5M內(nèi)存罪既,而并發(fā)連接有1000個铸题,則你的應(yīng)用程序需要耗費(fèi)5G內(nèi)存,這不容忽視琢感。
      
      • LIMIT子句
      當(dāng)使用bull colect時丢间,應(yīng)該要同時使用LIMIT子句,生產(chǎn)環(huán)境的數(shù)據(jù)量可能會大幅度增長驹针。
      可嘗試不同的LIMIT值烘挫,以平衡優(yōu)化的性能和內(nèi)存消耗的關(guān)系。
      
      • 沒有游標(biāo)屬性
      當(dāng)與顯式游標(biāo)和循環(huán)一起使用LIMIT子句時柬甥,不要使用SQL%NOTFOUND和NO_DATA_FOUND等來終止循環(huán)饮六。
      而應(yīng)該要檢查bull colect填充的集合,假如沒有數(shù)據(jù)返回了苛蒲,集合數(shù)量為0卤橄。
      
      • 非字符串索引的集合,集合索引值始終從1開始
    • 關(guān)于forall的建議

      • 異常臂外,考慮添加save exception
      默認(rèn)情況下窟扑,當(dāng)循環(huán)中紅的DML語句出現(xiàn)問題時導(dǎo)致SQL引擎拋出異常時,
      會把這個異常傳回PL/SQL引擎漏健,此時會終止程序辜膝,
      可給forall頭部增加save exception子句,保存執(zhí)行過程中拋出的異常漾肮,并在結(jié)束時返回所有的異常。
      
      • 每個forall語句中茎毁,只可以執(zhí)行一條DML語句
  • 緩存實踐(盡可能在最快的內(nèi)存位置克懊,緩存靜態(tài)數(shù)據(jù))

    備注:
    SGA:數(shù)據(jù)庫實例保留一份忱辅,所有會話均可使用
    PGA:每個會話保留一份
    
    • 包緩存,保存在PGA谭溉,消耗內(nèi)存大
    1墙懂、一般后臺應(yīng)用程序會使用連接池技術(shù),需要確保的是:緩沖池中每個連接都包含相同的被緩沖數(shù)據(jù)扮念,
    避免前端應(yīng)用程序在不同的會話間切換時损搬,操作不同的數(shù)據(jù)集。
    
    2柜与、應(yīng)該在包體內(nèi)部聲明數(shù)據(jù)結(jié)構(gòu)(私有巧勤,只能在包內(nèi)使用),以便于管理其內(nèi)容弄匕,保證完整性颅悉。
    
    CREATE OR REPLACE PACKAGE BODY emplu2
    IS
       TYPE tab_tabtype IS TABLE OF employee%ROWTYPE INDEX BY BINARY_INTEGER;
       loadtab tab_tabtype;
    
       FUNCTION onerow (
          employee_id_in IN employee.employee_id%TYPE
          )
       RETURN employee%ROWTYPE
       IS
       BEGIN
          RETURN loadtab (employee_id_in);
       EXCEPTION
          WHEN NO_DATA_FOUND
          THEN
             RETURN emplu1.onerow (employee_id_in);
       END;
    
    BEGIN
       FOR rec IN (SELECT * FROM employee)
       LOOP
          loadtab (rec.employee_id) := rec;
       END LOOP;
    END;
    
    • 結(jié)果緩存,保存在SGA迁匠,消耗內(nèi)存小
  • 管道函數(shù)

  • 當(dāng)集合和記錄是OUT或IN OUT參數(shù)時剩瓶,可以考慮使用NOCOPY降低負(fù)載

函數(shù)或過程參數(shù)模式:
1、IN:讀(傳引用)
2城丧、OUT:寫延曙,初始值默認(rèn)null(傳值)
3、IN OUT:讀寫(傳值)

傳值:復(fù)制進(jìn)亡哄,復(fù)制出枝缔。參數(shù)的值會被復(fù)制到應(yīng)用程序的數(shù)據(jù)結(jié)構(gòu),
傳引用:程序中參數(shù)變量指向值所在內(nèi)存區(qū)域磺平。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魂仍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拣挪,更是在濱河造成了極大的恐慌擦酌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菠劝,死亡現(xiàn)場離奇詭異赊舶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赶诊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門笼平,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舔痪,你說我怎么就攤上這事寓调。” “怎么了锄码?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵夺英,是天一觀的道長晌涕。 經(jīng)常有香客問我,道長痛悯,這世上最難降的妖魔是什么余黎? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮载萌,結(jié)果婚禮上惧财,老公的妹妹穿的比我還像新娘。我一直安慰自己扭仁,他們只是感情好垮衷,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斋枢,像睡著了一般帘靡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓤帚,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天描姚,我揣著相機(jī)與錄音,去河邊找鬼戈次。 笑死轩勘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怯邪。 我是一名探鬼主播绊寻,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼悬秉!你這毒婦竟也來了澄步?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤和泌,失蹤者是張志新(化名)和其女友劉穎村缸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體武氓,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梯皿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了县恕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片东羹。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖忠烛,靈堂內(nèi)的尸體忽然破棺而出属提,到底是詐尸還是另有隱情,我是刑警寧澤美尸,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布垒拢,位于F島的核電站旬迹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏求类。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一屹耐、第九天 我趴在偏房一處隱蔽的房頂上張望尸疆。 院中可真熱鬧,春花似錦惶岭、人聲如沸寿弱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽症革。三九已至,卻和暖如春鸯旁,著一層夾襖步出監(jiān)牢的瞬間噪矛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工铺罢, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留艇挨,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓韭赘,卻偏偏與公主長得像缩滨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泉瞻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容