数据库编程规范
1.1??????????? ? 前言
目前在軟件圈內(nèi)有這么一個現(xiàn)象,就是:DBA 不太懂寫PL/SQL ,而開發(fā)人員寫的又是五花八門,而且效率不高。如此以來,造成諸多弊端:
1. 可讀性差。讀別人寫的一個程序花費的時間,比自己寫一個程序的花費時間還要長;非但別人看不懂,時間久了連自己也看不懂了。
2. 可維護性差。程序越寫越長,越改越爛,像懶婆娘的裹腳布,又臭又長。
3. 可移植性差。今天用oracle 寫一套,明天換成SQL Server 的時候再寫一套,眾多的數(shù)據(jù)庫開發(fā)人員在程序的苦海中重復著低級勞動……
4. 效率和性能差。一個存儲過程或SQL 執(zhí)行效率簡直可以讓你感覺到對時間的絕望,你很快就理解什么是相對論了。
1.2??????????? ? 編程規(guī)范概述
??? 事實上為了統(tǒng)一軟件開發(fā)過程中關于數(shù)據(jù)庫設計時的命名規(guī)范和編程規(guī)范,正規(guī)一些的IT 公司都會制定一些關于數(shù)據(jù)庫對象的命名和編程規(guī)范。
否則的話,你寫你的我寫我的,各自為戰(zhàn)不兼容,彼此看不懂,甚至到最后連自己都弄不明白了,這樣的經(jīng)歷相信每個人都曾經(jīng)遇到過。
例如下面這段簡單的代碼,您看的明白嗎?就算暫時明白,過個一年半載您還明白嗎?就算您記憶超群,想必也會忘記,不是么?
A:=1;b:=2;SeLeCT username from EmPLOyee where id=a aNd Type=b;
如果你改為下面這樣的寫法,相信稍微懂點數(shù)據(jù)庫的人都應該看的明白不是?
vID:=1;?? --A 為ID
vType=2;? --B 為類型
SELECT username FROM employee WHERE id=vID AND type=vType;
1.3??????????? ? 書寫規(guī)范
丑陋的書寫規(guī)范不僅可讀性較差,而且給人以敬而遠之的感覺,就是您是大俠也不行啊;而良好的書寫規(guī)范則給人以享受和藝術的體驗。
1.3.1 大小寫風格
規(guī)則1.3.1.1
所有數(shù)據(jù)庫關鍵字和保留字均使用大寫;關于字段、變量的大小寫風格在1.4 詳細介紹。
1.3.2 縮進風格
規(guī)則 1.3.2.1
程序塊嚴格采用縮進風格書寫,保證代碼清晰易讀,風格一致,縮進格數(shù)統(tǒng)一為2 /4 個。
必須使用空格,不允許使用TAB 鍵。以免用不同的編輯器閱讀程序時,因TAB 鍵所設置的空格數(shù)目不同而造成程序布局不整齊。
規(guī)則1.3.2.2
同一條語句需要占用多于一行時,每行的其它關鍵字與第一行的關鍵字進行右對齊。
IF flag=1 THEN
? SELECT username???????????? -- 同上一行相比縮進4 個空格
??? INTO vuserinfo??????????? --INTO 與SELECT 進行右對齊
??? FROM userinfo???????????? --FROM 與SELECT 進行右對齊
?? WHERE userid=:iuserid;???? --WHERE 與SELECT 進行右對齊
END IF;
1.3.3 空格及換行
規(guī)則1.3.3.1
不允許把多個語句寫在一行中,即一行只寫一條語句。
規(guī)則1.3.3.2
避免將復雜的SQL 語句寫到同一行,建議要在關鍵字和謂詞處換行。
規(guī)則1.3.3.3
相對獨立的程序塊之間必須加空行。
BEGIN 、END 獨立成行
規(guī)則1.3.3.4
太長的表達式應在低優(yōu)先級操作符處換行,操作符或關鍵字放在新行之首。劃分出新行應當適當?shù)乜s進,使排版整齊,語句可讀。
不同類型的操作符混合使用時,建議使用括號進行隔離,以使代碼清晰。
規(guī)則1.3.3.5
減少控制語句的檢查次數(shù),如在 IF…ELSE 控制語句中,對最常用符合條件,盡量前置以被檢查到。
DECLARE
? -- 定義局部變量
? vFlag VARCHAR2(10);? -- 判斷標志
? …
BEGIN??
? IF ((a=b AND a=c AND a=d) OR? -- 在OR 處斷行,可使得邏輯更為清晰
????? (a=e AND e=f)) THEN
--Process something
? IF vFlag=1 THEN????? --vFlag=1 為經(jīng)常出現(xiàn)之條件,可有效減少判斷檢查次數(shù)
--Process something
? ELSIF vFlag=2 THEN? --vFlag=2 為次之出現(xiàn)的條件
--Process something
? ELSE
?? --Process something
? END IF;
1.3.4 其它
規(guī)則 1.3.4.1
避免使用SELECT * 語句;不要用* 來代替所有字段,應給出字段列表,以避免表結構發(fā)生變化時應用程序出現(xiàn)無法識別的情況。
規(guī)則 1.3.4.2
INSERT 語句必須給出字段列表,以避免表結構發(fā)生變化時發(fā)生編譯錯誤。
規(guī)則 1.3.4.3
當一個PL/SQL 或SQL 語句中涉及到多個表時,始終使用別名來限定表名和字段名,這使其它人閱讀起來更方便,避免了含義模糊的引用,并能夠別名中清晰地判斷出表名和相關字段名。
規(guī)則 1.3.4.4
確保變量和參數(shù)在類型和長度與表數(shù)據(jù)列類型和長度相匹配。說明:如果與表數(shù)據(jù)列寬度不匹配,則當較寬或較大的數(shù)據(jù)傳進來時會產(chǎn)生運行異常。
DECLARE
? -- 定義相關表字段變量
? vDeptNo????? salary.Deptno%type;???? --not VARCHAR2(10) ,以適應變化
? vEmployeeNo? salary.EmployeeNo%type; --not VARCHAR2(10) ,以適應變化
? vSalary????? salary.Salary%type;???? --not NUMBER ,以適應變化
BEGIN
? --Process something
END;
1.4??????????? ? 命名規(guī)范
??? 一千個讀者就有一千個哈姆雷特,對于命名規(guī)范來說,想做到完全統(tǒng)一的確是不可能的任務。命名規(guī)范更多的是個人層面的愛好,就算有命名規(guī)范,也不過是體現(xiàn)制訂規(guī)范的相關人的愛好而已。
因此即使無法完全做到一致,但是我們?nèi)匀灰M量去遵守,必要的時候需要通過代碼檢查和專家評審來進行約束,因為一個不成熟的規(guī)范總會勝過沒有規(guī)范。
1.4.1 表和字段命名規(guī)范
在此僅提供幾種常見的命名方法( 表和字段的命名方式雷同) 。
以用戶權限字段/ 表為例:
| UserPrivilege | 適合那些英文比較好,并且喜歡抑揚頓挫和有藝術美感的人。 |
| userprivilege | 適合那些英文好,且比較嚴謹?shù)娜?#xff0c;畢竟全部小寫很容易與數(shù)據(jù)庫關鍵字相區(qū)別。 |
| tbl_user_privilege | 適合那些做開發(fā)的人,開發(fā)的人會習慣性的給變量加前綴。 ( 這里指表的命名,字段一般很少加前綴) |
| yhqx | 熱愛中文的人,前提是恐怕您得對這些縮寫先做好相關備注,等大家習慣了才行。 |
實際上這幾種命名規(guī)范各有千秋,很難去指責或否定哪種更好,完全取決于整個公司多數(shù)人的習慣,記住沒有十全十美的命名規(guī)范,只有絕大多數(shù)人心甘情愿的去遵從了,那就是好的命名規(guī)范。
就我個人而言,我更偏向于第一種命名習慣。
規(guī)則 1.4.1.1
不建議使用數(shù)據(jù)庫關鍵字和保留字(不建議并不意味著不能使用),只是為了避免不必要的沖突和麻煩;
例如name,id,level,remark,description 等等。
有興趣的話,大家可以參考下SELECT * FROM v$reserved_words WHERE reserved='Y'
實際上oracle 不建議大家使用v$reserved_words 表中所有的關鍵字,無奈這些關鍵字太多了;reserved='Y' 的關鍵字則是被完全禁止的。
規(guī)則 1.4.1.2
嚴禁使用帶空格的名稱來對字段和表命名;在產(chǎn)生數(shù)據(jù)庫腳本并重新加載的時候可能會出現(xiàn)意想不到的錯誤而被迫終止。
1.4.2 其它對象命名
用戶自定義的數(shù)據(jù)庫對象名包括表、視圖、主外鍵、索引、觸發(fā)器、函數(shù)、存儲過程、序列、同義詞、數(shù)據(jù)庫鏈接、包和包體等等。
規(guī)則 1.4.2.1
其它對象的命名也與表和字段的命名規(guī)則類似,風格保持一致即可
規(guī)則 1.4.2.2
除數(shù)據(jù)庫名稱長度為1 -8 個字符,其余為1 -30 個字符,database link 名稱也不要超過30 個字符;
命名只能使用英文字母,數(shù)字和下劃線
規(guī)則 1.4.2.3
除表外,其它各種對象的命名最好用不同的前綴加以區(qū)別。采用前綴的方式來命名對象則很容易通過排序對對象進行區(qū)別。
如在命名規(guī)范中各組成部分以_ 分割,則前綴建議也以_ 分割;反之則可加可不加
| 對象名 | 前綴 | 范例 |
| 表(table) | tbl_/t_ ( 或不加前綴) | userinfo/t_user_info/ tbl_user_info |
| 視圖(view) | v_/v | v_user_info/vuserinfo |
| 序列(sequence) | seq_ | seq_user_info |
| 簇(cluster) | c_ | c_user_info |
| 觸發(fā)器(trigger) | trg_ | trg_user_info |
| 存儲過程(procedure) | sp_/p_ | sp_user_info/p_user_info |
| 函數(shù)(function) | f_/fn_ | fn_user_info/f_user_info |
| 物化視圖 (materialized view) | mv_ | mv_user_info |
| 包和包體 (package & package body) | pkg_ | pkg_user_info |
| 類和類體 (type & type body) | typ_ | typ_user_info |
| 主鍵(primary key) | pk_ | pk_user_info |
| 外鍵(foreign key) | fk_ | fk_user_info_fieldname |
| 唯一索引(unique index) | uk_ | uk_user_info_fieldname |
| 普通索引(normal index) | idx_ | idx_user_info_fieldname |
| 位圖索引(bitmap index) | bk_ | bk_user_info_fieldname |
| 同義詞(synonym) | 依據(jù)于所 分配的表所屬模塊/ 模式 | |
| 數(shù)據(jù)庫鏈接(database link) | 無特殊要求 |
1.5??????????? ? 變量命名
規(guī)則1.5.1
所有PL/SQL 中的變量與對象命名規(guī)則相似
| 變量類型 | 前綴 | 范例 |
| 輸入變量 | i_/i | i_user_id/iuserid |
| 輸出變量 | o_/o | o_user_name/ousername |
| 輸出輸入變量 | io_/io | io_user_name/iousername |
| 普通變量 | v_/v | v_user_id/vuserid |
| 全局變量 | gv_/gv | gv_user_id/gvuserid |
| 常量 | 大寫 | PI |
| 游標 | cur_ | cur_userinfo |
| 用戶自定義類型 | type_ | type_user_info |
| 保存點(save point) | spt_ | spt_user_info |
規(guī)則1.5.2
命名不允許使用中文或者特殊字符。
命名中若使用特殊約定或縮寫,則要注釋說明。
規(guī)則1.5.3
使用有意義、易于記憶、描述性強、簡短及唯一的英文單詞/ 拼音縮寫。自己特有的命名風格,要自始自終保持一致,不可來回變化。
說明:個人命名風格,在符合所在項目組的命名規(guī)則的前提下,才可以使用。
規(guī)則1.5.4
對于變量命名,禁止取單個字符( 如i 、j … ) ,建議除了要有具體含義外,還能表明變量類型等。
說明:變量,尤其是局部變量,如果用單個字符表示,很容易敲錯( 如i 寫成j) ,而編譯時又檢查不出來,有可能為了這個小小的錯誤而花費大量的時間。
1.6??????????? ? 注釋規(guī)范
注 釋規(guī)范是判斷一個開發(fā)人員優(yōu)劣和成熟度的重要指標。一個優(yōu)秀的研發(fā)人員必然是經(jīng)過深思熟慮然后才洋洋灑灑妙筆生花的,注釋的書寫體現(xiàn)了一個人思考問題的全 過程和步驟;話又說回來,就算代碼寫的爛,只要注釋寫的好,至少也會給人以良好的感覺;同時也能造福后人,不是么?呵呵。
規(guī)則1.6. 1
一般情況下,源程序有效注釋量必須在30% 左右。
說明:注釋的原則是有助于對程序閱讀理解,在該加的地方都加了,注釋不宜太多也不能太少,注釋語言須準確、易懂、簡潔、精煉。
規(guī)則1.6. 2
統(tǒng)一文件頭的注釋.
主要是對相關過程、函數(shù)進行功能性描述、修訂記錄、以及入?yún)⒊鰠⒄f明
對存儲過程、函數(shù)的任何修改,都需要在注釋后添加修改人、修改日期及修改原因等修訂說明。
/***********************************************************
名稱: sp_xxx
功能描述:
修訂記錄:
版本號??? 編輯時間??? 編輯人? 修改描述
1.0.0???? 2010-01-01? John??? 1 、創(chuàng)建此存儲過程
1.0.1???? 2010-02-01? Sandy?? 2 、增加傳入?yún)?shù)
入?yún)⒊鰠⒚枋?#xff1a;
iparameter1??????? IN VARCHAR2(20)? 傳入?yún)?shù)1
iparameter2??????? IN VARCHAR2(20)? 傳入?yún)?shù)2
iparameter1??????? OUT VARCHAR2(20)? 傳入?yún)?shù)1
iparameter2??????? OUT VARCHAR2(20)? 傳入?yún)?shù)2
返回值描述:( 主要針對函數(shù))
? 0 - Success
? 1 - normal fail
***********************************************************/
規(guī)則1.6. 3
所有變量定義需要加注釋,說明該變量的用途和含義。
規(guī)則1.6. 4
注釋內(nèi)容要清晰、明了、含義準確,防止注釋二義性
在代碼的功能、意圖層次上進行注釋,提供有用、額外的信息。
避免在一行代碼或表達式的中間插入注釋。
盡量使用”-- ”進行注釋;行尾注釋須使用”-- ”。
規(guī)則1.6. 5
對程序分支必須書寫注釋。
說明:這些語句往往是程序實現(xiàn)某一特定功能的關鍵,對于維護人員來說,良好的注釋幫助更好的理解程序,有時甚至優(yōu)于看設計文檔。
在程序塊的結束行右方加注釋,以表明程序塊結束。
規(guī)則1.6. 6
注釋應與其描述的代碼相似,對代碼注釋應放在其上方或右方( 對單條語句的注釋) 相近位置,不可放在下面。
注釋與所描述的內(nèi)容進行同樣的縮排。
注釋上面的代碼應空行隔開。
建議1.6. 7
注釋用中文書寫
有一次,同事寫了一個900 行的存儲過程,里面定義了十幾個游標以進行遍歷,這個存儲過程缺乏注釋,執(zhí)行一次居然要一天一夜,已經(jīng)達到了無法容忍的地步。
??? 因為缺乏注釋,我花了整整一天的時間來對該存儲過程進行分析,然后用了半天時間來進行改寫和調(diào)試。
其實很簡單定義,我定義了一些對應的臨時表,把游標遍歷替換成SQL 的集合操作,把整個的一個大事務分割成若干小事務,只是修改了部分代碼,結果執(zhí)行時間就變成了短短的3 分鐘。
當然游標也并非不可觸及的,既然存在就有他存在的理由。
1.7??????????? ? 語法規(guī)范
??? 良好的語法規(guī)范有助于書寫出高效、完備的PL/SQL 程序,同時有助于提高系統(tǒng)的容錯性、健壯性、可追溯性。
規(guī)則1.7 .1
避免隱式的數(shù)據(jù)類型轉換。
說明:在書寫代碼時,必須確定表的結構和表中各個字段的數(shù)據(jù)類型,特別是書寫查詢條件時的字段就更要注意了。這個是導致SQL 性能不佳常犯的錯誤之一。
規(guī)則1.7 .2
為了方便不同的數(shù)據(jù)庫平臺的移植,盡量使用SQL99 標準,而不要使用Oracle 的方言。
例如:DECODE 函數(shù)完全可以用CASE WHEN 語句代替,而且可編程性更強。
(+)= 右關聯(lián)用RIGHT OUTER JOIN 語句代替。
=(+) 左關聯(lián)用 LEFT OUTER JOIN 語句代替。
規(guī)則1.7 .3
對于非常復雜的SQL( 特別是多層嵌套,帶子句或相關的查詢) ,應該先考慮是否設計不當引起的,對于復雜的一些SQL 可以考慮使用程序實現(xiàn),原則上遵循一句話只做一件事情。
關于處理的優(yōu)先級
1、? 靜態(tài)SQL> 動態(tài)SQL
2、? 綁定變量的SQL> 動態(tài)SQL (在OLTP 系統(tǒng)中建議這么做)
3、? SQL>PL/SQL 的過程,極端復雜的SQL 除外
4、? SQL> 游標遍歷
5、? Oracle 函數(shù)> 自定義函數(shù)
6 、盡量使用Oracle 分析函數(shù)代替同一個表多次的關聯(lián)。
規(guī)則1.7 .4
原則上不要使用動態(tài)SQL ,如果非得使用動態(tài)SQL ,建議使用綁定變量。
規(guī)則1.7 .5
一定要及時關閉和釋放游標
規(guī)則1.7 .6
建議在異常處理中,把收集到錯誤信息記入錯誤日志表,以備查詢和分析。
CREATE OR REPLACE PROCEDURE sp_increament_xxx
/***********************************************************
名稱: sp_increament_xxx
功能描述:xxx 表/ 模塊數(shù)據(jù)增量更新,錯誤原因分析通過tbl_task_table 日志表
?
修訂記錄:
版本號??? 編輯時間??? 編輯人? 修改描述
1.0.0???? 2010-05-01? John??? 1 、創(chuàng)建此存儲過程
1.0.1???? 2010-06-01? Sandy?? 2 、更新xxx 字段在xxx 處
?
入?yún)⒊鰠⒚枋?#xff1a;
? N/A
?
返回值描述:( 主要針對函數(shù))
? N/A?
***********************************************************/
AS
??? v_err_num NUMBER;
??? v_err_msg VARCHAR2(100);
??? v_begin_date DATE;
??? v_end_date DATE;
BEGIN
?
??? v_err_num:=0;
??? v_err_msg:='';
?
??? -- 某表增量更新步驟
??? BEGIN
??????? SAVEPOINT spt_xxx;
?
??????? -- 從任務表中獲取更新初始時間
??????? SELECT lasttime INTO v_begin_date FROM tbl_task_table T
???????? WHERE id='sp_increament_xxx';?
??????? -- 從源數(shù)據(jù)表中獲取更新最后時間
??????? SELECT MAX(oper_date) INTO v_end_date FROM tbl_table_source;??
???? ??? -- 為提高執(zhí)行效率,將增量數(shù)據(jù)寫入臨時表中
??????? INSERT INTO tmp_tbl_table_source
??????????? (fieldname1,fieldname2,fieldname3,
???????????? fieldname4,fieldname5,fieldname6)
??????? SELECT
???????????? fieldname1,fieldname2,fieldname3,
???????????? fieldname4,fieldname5,fieldname6
????????? FROM tbl_table_source sourcetable
???????? WHERE sourcetable.create_date > v_begin_date
?????????? AND sourcetable.create_date <= v_end_date;
??????? -- 再講增量數(shù)據(jù)從臨時表更新到最終目標表
??????? MERGE INTO tbl_table_original_dest desttable
????? ?? USING tmp_tbl_table_source tmptable
?????????? ON (desttable.primarykey = tmptable.primarykey) -- 匹配判斷標準,根據(jù)主鍵判斷
???????? WHEN MATCHED THEN????????????????????????????????? -- 如果已存在,更新原紀錄
???????? UPDATE SET desttable.fieldname1 = tmptable.fieldname1,
???? ??????????????? desttable.fieldname1 = tmptable.fieldname1,
??????????????????? desttable.fieldnamem = tmptable.fieldnamem,
??????????????????? desttable.fieldnamen = tmptable.fieldnamen
???????? WHEN NOT MATCHED THEN????????????????????????????? -- 如果不存在,插入新紀錄
???????? INSERT (fieldname1,fieldname2,fieldname3,
???????????????? fieldname4,fieldname5,fieldname6)
???????? VALUES (tmptable.fieldname1,tmptable.fieldname2,tmptable.fieldname3,
???????????????? tmptable.fieldname4,tmptable.fieldname5,tmptable.fieldname6);??
??????? -- 更新任務表相應的狀態(tài)、時間???????????
??????? UPDATE tbl_task_table
?????????? SET lasttime_=v_end_date,status='SUCCESS'
???????? WHERE id='sp_increament_xxx';
??????? COMMIT;
?
??? -- 異常處理,把錯誤記入相關日志表,可以及時找到錯誤原因并進行分析。
??? EXCEPTION
??????? WHEN OTHERS THEN
??????????? ROLLBACK TO SAVEPOINT spt_xxx;
??????????? v_err_num := SQLCODE;
??????????? v_err_msg := SUBSTR(SQLERRM, 1, 100);
??????????? UPDATE tbl_task_table
?????????????? SET lasttime_=v_begin_date,status='FAIL'
???????????? WHERE id='sp_increament_xxx';
??????????? COMMIT;
??? END;
?
??? -- 某表增量更新步驟
??? BEGIN
??????? ...
??? END;
?
END;
本例為數(shù)據(jù)庫定時調(diào)用存儲過程,同時也只是為了演示存儲過程的全部過程;對于常用的由客戶端調(diào)用的存儲過程,建議不要捕獲異常,而由客戶程序進行直接處理。
規(guī)則1.7 .7
不要將空的變量值直接與比較運算符( 符號) 比較。如果變量可能為空,應使用IS NULL 或IS NOT NULL 或NVL 函數(shù)進行比較。
規(guī)則1.7 .8
盡可能地使用相關表字段的類型定義,形如%TYPE 、%ROWTYPE 。這樣做當表結構發(fā)生變動的時候,能夠最大程度的做到容錯性和健壯性。
規(guī)則1.7 .9
存儲過程中變量的聲明應集中在AS 和BEGIN 關鍵字之間,不允許在代碼中隨意定義變量,定義變量時,完成相同功能模塊的變量應放在一起,與不同模塊的變量應空行隔開,增加代碼的可讀性。
1.8??????????? ? 腳本規(guī)范
??? 腳本規(guī)范有助于進行版本基線的管理、版本控制,也有助于系統(tǒng)的自動部署、定位和解決部署過程中出現(xiàn)的問題。
規(guī)則 1.8.1
所有腳本按分類或內(nèi)容分開存放,并按以下順序存儲:
1.?????? 創(chuàng)建數(shù)據(jù)庫角色、用戶腳本
2.?????? 創(chuàng)建數(shù)據(jù)庫表空間、數(shù)據(jù)文件腳本
3.?????? 創(chuàng)建數(shù)據(jù)類型腳本,自定義的數(shù)據(jù)類型
4.?????? 創(chuàng)建業(yè)務表腳本,表是其他依賴關系的基礎
5.?????? 創(chuàng)建臨時表腳本,可能會在過程腳本中用到
6.?????? 創(chuàng)建視圖腳本
7.?????? 創(chuàng)建主外鍵腳本
8.?????? 創(chuàng)建索引腳本
9.?????? 創(chuàng)建觸發(fā)器腳本
10.??? 創(chuàng)建函數(shù)、存儲過程腳本
11.??? 初始化數(shù)據(jù)腳本
12.??? 創(chuàng)建作業(yè)腳本
規(guī)則1.8.2
創(chuàng)建每個對象代碼的首部應該有對象注釋
規(guī)則1.8.3
每個函數(shù)、存儲過程應單獨創(chuàng)建腳本,在配置庫上按照功能模塊存放到不同的目錄下。
并在相應的目錄下,創(chuàng)建一個運行所有腳本的總腳本轉載于:https://www.cnblogs.com/hanmos/archive/2011/03/27/1997025.html
總結
- 上一篇: 怎么在泰山上体验不同的登山路线?
- 下一篇: 三种 SQL 执行语句