資料庫設計時的一些細節的東西如何處理?
如題.最近發現資料庫設計上的一些小細節的不同,對編程來講會有或大或小的影響.
那麼大家又是如何處理這裡小細節的呢??..例如,一些可為NULL的欄位,你會偏向於選擇 default null,還是設置一個默認非空值?或者根據具體的場景做謹慎思考後選擇?不同的選擇會造成程序不同的處理.甚至於影響到庫的查詢和優化,那麼針對哪些場景需要如何選取呢??一些其他的細節問題.
一..類型選取..目前接觸到幾個小資料庫設計,,基本上使用到的類型也就那麼幾種,varchar,int bigint,date,等常見的,但是其他的其實還是有很多類型的,不可否認有些情況多個類型都是可選的,多個選擇之間如何衡量?類型的長度又有何影響?
二...PK問題,以前剛學資料庫時,記得書上說盡量使用與邏輯無關的主鍵id,但是不知道為什麼,會有什麼影響?接觸資料庫設計過程中,發現這個問題也不不是被太注意.
三..關於表設計問題,,最近項目突然發現一個問題,,目前用查詢中,所有的查詢都是單表查詢,設計資料庫時,寧願數據冗餘(小量),也不讓出現多表聯查...當時就覺得很有意思..
其他的暫時沒想到,上面也算是接觸資料庫過程中遇到的一些疑惑.希望大家能夠談一談資料庫設計時的一些小技巧或者注意點..以及這些小地方對整體的影響....表示剛剛入門程序猿一名,各種小疑惑..
之前給團隊編製過一份資料庫設計規範,詳細總結了資料庫設計、使用過程中的種種細節問題,並給出了相應的解決方案。編寫制定本文檔的過程中,從網路上翻閱了數百份資料庫設計相關的文章、帖子,敢說是目前網路中流傳的最認真用心全面的一份關係型資料庫設計相關的文檔。
一 文檔說明
1、引言
「文檔說明」部分將給出本資料庫設計規範的編寫背景,並從全局介紹資料庫設計過程中各環節、各模塊的規範要求。
2、編寫背景
「HEBSMJYPT」的項目告一段落後,著手開始「JCFGTGYLJRDSJ」項目。所在單位所領團隊之前的資料庫設計工作是按業務模塊劃分後交由不同的開發人員負責,沒有統一規範,組合在一起的完整設計相對雜亂。資料庫設計的完整性、統一性被破壞後,程序部分自然也會受到影響,進而影響整個項目的穩定性、協調性。即便是過去獨立負責設計的資料庫,在細節上也有諸多不合理的地方,其中一個重要的誘導因素就是不統一——設計理念、欄位類型、命名方式、通用表通用欄位的處理方法等等因為沒有規範的約束而導致的不統一。
所以在開始「JCFGTGYLJRDSJ」項目的設計之前,著手制定出一套標準合理的資料庫設計規範,至此之後的項目,資料庫設計工作無論由一人負責還是多人協作,都嚴格依此規範進行,籍此避免再次出現早前的許多問題。
在制定本規範之前,從各種渠道梳理匯總了數百份資料庫設計規範相關的文檔,也回看了過去負責或參與項目的資料庫結構圖,把好的設計方案加以總結沉澱、把尚有問題的地方列出並給出相應的解決方法,根據以往經驗和現實需要將這些種種去其糟粕取其精華,匯成一家。
3、文檔概述
整個規範的制定可以分成兩部分,第一部分是設計規範,第二部分是命名規範。命名規範也屬於設計規範的模塊,但因其比較重要,且涉及的內容較多,故將其剝離單獨介紹。本來文檔編輯之初還想寫一個操作規範模塊,操作規範用來約束資料庫設計完成之後的工作,比如程序編寫過程中SQL語句對數據的增刪改查,開發過程中對錶結構的增刪改,維護過程中對資料庫的備份遷移等等。但因為時間和個人緣故,最終決定拿掉此模塊,此模塊中的一些涉及點融合到了設計規範和命名規範中進行介紹。在文檔的最後,也會給出此規範編寫過程中的參考文獻,多是網路上個人撰寫的資料庫設計規範相關的非正式文章。
中小型的Web項目,資料庫以MySQL、SQLServer及Oralce為主,非關係型資料庫不在本規範的考慮範圍之內,而不同的關係型資料庫設計理念大同小異,不過具體到細節上,又可以說差異巨大。鑒於當前開發面臨的實際情況,本規範的制定主要針對(但不限於)MySQL,對於特別部分,會對比不同的關係型資料庫具體說明,如無特別說明,則默認表示MySQL資料庫。
此為1.0版本,制定基本規範,後面會持續更新,根據不同的項目、不同的資料庫、不同的程序架構、不同的開發模式、不同的數據量並發訪問量等實際情況,在現有規範的基礎上完善修正,越是後期的版本,規範會愈加全面、愈加靈活、愈加合理、適應性愈加更廣闊。
二 設計規範
1、引言
設計規範部分目前八個子模塊:設計理念、基本原則、欄位設置、相關注釋、約束控制、索引添加、特別說明及梳理總結部分,命名規範部分單獨列出一個大模塊,在後面的章節介紹。本章節是文檔的核心部分,詳細說明資料庫設計步驟、設計原理、設計過程中各環節要遵守的原則、注意事項等。
目前的設計規範並不全面,存儲過程、觸發器、函數、視圖、事件等高級功能的設計規範都未特別說明,原因後面會講。在後續版本的規範文檔中,可根據實際情況修改擴充。
2、設計理念
A.設計流程
從項目啟動之時,資料庫設計工作就已經開始,貫穿於項目前期階段的需求調研、分析、確認、業務梳理過程,只不過這時的設計大都停留在腦海中,正式的設計過程起始於最終的需求確認完成、業務梳理清晰之後。
就目前來看,最好的關係型資料庫設計工具還是PowerDesigner(以後簡稱PD),我們要求正式資料庫設計過程中必須使用此工具,先出CDM(Concept Data Model,概念數據模型),再根據實際的資料庫類型由CDM導出PDM(Physical Data Model,物理數據模型),最後由PDM將設計成果直接導入到資料庫中。同時導出相應的資料庫文檔,以供項目組開發人員查閱。
CDM設計過程中只做最簡單必要的設計,約束、關係、主鍵、外鍵、命名規則等在轉換成PDM過程中PD可自行處理的部分均交由工具自行處理。就是說資料庫設計者只負責工具無法處理的少許部分,工具可完成的部分則用工具統一處理,這樣設計工作會更高效省時,最終的設計成果也會更規範合理。
初版資料庫設計完成之後進入項目開發階段,如果前期的需求調研分析、業務梳理沒問題,後期資料庫結構發生大規模改動的情況不應該頻繁出現的。但需求總是在變,意外才是唯一的法則,即便設計者在前期調研、了解、分析、設計的過程中再過謹慎,怕只能減少這種情況出現的風險,卻不能完全避免。還有隨著開發的深入,資料庫結構發生局部變動,比如增刪改些表欄位等也是再正常不過的了。針對這些,都應該有相應的對策,才能以不變應萬變。
第一版的資料庫設計完成,由PDM生成相應的SQL腳本在資料庫中執行之後,隨開發深入而再進行的資料庫改動分兩種情況:一種是大改,比如原有的業務有變動,或因在設計時考慮不周、對需求了解不清導致設計出錯,表及引用關係都要發生重大變化,剛也說了這種情況不應該出現;另一種是小改,比如有新增業務的情況要新建些表,有拿掉部分業務的情況可能會刪除些表,還有更常出現的是增刪改部分表的部分欄位。
對於以上兩種,有增加表或大規模業務變動的情況,建議是在PD中修改PDM,然後重新生成SQL腳本在資料庫中執行,當然只生成新增表或發生業務變動部分的即可,而對於簡單的增刪改些欄位或業務變動不大的情況,建議直接在資料庫中對錶進行修改。然後通過PD菜單中的Database——Update Model from Database……連接資料庫逆向更新PDM,使PDM和最新的資料庫結構保持一致,而後再生成最新的資料庫文檔。但要注意的是,使用此功能逆向更新PDM,只會更新修改或新增的表欄位,而不更新刪除的欄位。就是說,如果在資料庫中對一個表的欄位進行了修改,或者新增了一些欄位,PD會同步在PDM中的相應表中做出相應的修改或新增,但如果在資料庫中刪除了表中的某個欄位,PD是不會刪除PDM中相應表的相應欄位的。為什麼這般處理,自己也覺得很奇怪。
後期的資料庫改動,一般發生在程序開發啟動後,如果主程序可自動生成,第二種小改的情況還是比較容易處理的,重新生成下主程序,而手寫的部分(非自動生成部分)一般不會受到太大影響。但如果是第一種情況,業務發生了變動,那可能意味著手寫的程序(非自動生成部分)要重寫。之前的文章中也有提及,一定要注意前期的需求調研了解分析系統設計,後期的問題幾乎都是由前期的不慎造成,有經驗的項目經理可以在前期預料到後面可能的問題而提前採取相應的預防措施。防之於未有,治之於未亂。項目想要做的出色,有太多不可測因素,但如果手底下的項目都很穩當,其能力必是值得肯定的。
龔鼎孳.mp4youku.com視頻這裡還要提一點,後期資料庫表小範圍的修改一般是由開發人員發現,比如在開發過程中發現少了一些欄位,或局部業務有些問題等等,應該禁止開發人員擅自直接更改資料庫,所有更改無論大小必須經過資料庫主設計師的審核同意,以避免可能影響到全局的更改出現。
資料庫的設計工作雖然集中在項目的業務梳理清晰之後、正式開發之前,但相關細枝末節的工作卻不止於此,很可能會貫穿於整個項目的起始流程。
B.三種關係
有人說資料庫難以設計,其實難的並不是資料庫的設計,而是業務流程的梳理。再複雜的業務,只要理得清,表現在資料庫中,無外乎是表與表間的三種關係:一對一(one-to-one)、一對多(one-to-many )以及多對多(many-to-many)。更進一步的,many-to-many實際上就是兩個one-to-many。
在Java中萬事萬物皆對象,在關係型資料庫中萬事萬物皆是二維表,而事物之間的聯繫系就是表與表間的這三種關係。
後面還會多次提及,我們的設計原則是儘可能讓粒度小、容忍度高,比如在「設計規範」——「欄位設置」——「通用欄位處理」中有關於日期時間類型設置的說明,要求日期時間類型的欄位,儘可能用datetime類型,精確到時分秒,而不要用date類型。表現在這裡,處理業務關係時,對於核心業務部分尚不能明確表與表關係的,能一對多就不要一對一,能多對多就不要一對多。這樣開發的複雜度會增加,卻消除了後面可能的修改擴展的隱患。對於非核心業務也不能明確關係的,可根據實際情況,綜合考量開發實現的煩瑣程度及未來的可變性再做決定。
PD細化了這三種關係的表述,表現在CDM關係中Cardinalities選項卡的Cardinality選項中、表現在PDM引用中Integrity選項卡的Cardinality選項中。Cardinality,基數,在CDM的選項中表示另一方對於當前方的每個實例,可能擁有的實例的最少和最多數;在PDM的選項中表示父表中的每個實例,子表中可能擁有的實例的最少和最多數。
比如「病人」與「會診單」兩個實體之間的聯繫是one-to-many聯繫,換個方向說「會診單」和「病人」之間的聯繫是many-to-one聯繫。而且一個會診單必須屬於一個病人,並且只能屬於一個病人,不能屬於零個病人,所以從「會診單」實體至「病人」實體的基數為「1,1」;從聯繫的另一方向考慮,一個病人可以擁有多個會診單,也可以沒有任何會診單,即零個會診單,所以該方向聯繫的基數就為「0,n」。CDM中的表示如下圖所示:
在構建CDM、選擇兩個實體之間的關係時,這部分會自動賦值的。
類似的,一個品牌必需且只能屬於一個企業,一個企業卻可以有一個或多個品牌,又或者一個也沒有。在PDM的選項中表示父表為企業、子表為品牌,父表(企業)中的每個實例,子表(品牌)中可能擁有的實例的最少和最多數。如下圖所示:
PD的細化在於,one-to-one關係中到底是must have one and only one還是may have at most one,one-to-many關係中到底是must have one or more還是may have one or more。many-to-many關係需要藉助中間表實現,分解成兩個one-to-many組合,類似的,也可被細分成嚴格的多對多還是模糊的多對多。可以拿用戶和角色的例子說明,嚴格的多對多是一個用戶至少有一個角色、一個角色至少被一個用戶擁有,模糊的多對多是一個用戶可以沒有任何角色、一個角色可以不被任何用戶擁有。
個人認為對one-to-many和many-to-many兩種關係進行細化的意義不大,因為對於one-to-many、many-to-one,無論是否嚴格,都要在many方加入引用one方主鍵的外鍵;而對於many-to-many,無論是否嚴格,中間表都是必須的。所以對於這兩種關係,無論是否為嚴格的,設計方案是確定的。
但對於one-to-one,嚴格的和非嚴格關係之間設計上是有區別的。比如訂單和取消原因,一個訂單可以有一個或零個取消原因(may have at most one ),本來如果是嚴格的一對一關係,取消原因是可以整合到訂單表中的,而無需單建表存儲。 再比如班級與班長,一個班級只有一個正班長, 一個班長只在一個班中任職,兩邊都是must have one and only one,那班長表完全可以省略,而直接在班級表中加入相應描述班長信息的欄位即可,比如班長名稱、性別等等,又或者直接有個班長的外鍵欄位指向學生表。
C.如何設計
拿到項目後,完成早期的需求調研,在分析設計的過程中,先考慮的是要實現所需功能、需要的實體有哪些。比如要實現登錄功能,那必需要有用戶實體。繼續擴展思路,登錄往往伴隨著角色劃分,為此要有角色、許可權相關的實體。為了記錄登錄請求,又要有登錄日誌實體。這些實體表現在資料庫即為相應的表,實體確定,同步考慮實體與實體間的關係。一個用戶可以有多個角色,一個角色可以被多個用戶擁有,所以用戶和角色是多對多關係,多對多關係意味著出現中間表。一個用戶可以有多條登錄日誌,一條登錄日誌有且只能對應一個用戶,所以用戶和登錄日誌是一對多的關係,登錄日誌表中要有外鍵引用用戶表。這就是資料庫設計的過程。
資料庫的設計往往和前端界面的設計並行,前者稍晚於後者,兩者的進行伴隨在業務梳理的過程中、前期需求確認之後,為確保對需求理解的準確性、設計的準確性,此過程中應該繼續和需求提出者保持溝通。如果在設計資料庫時不考慮前端界面設計、或者是在設計界面時不考慮資料庫設計,且不說兩邊對業務需求的理解可能有偏差,即便完全沒有,最終怕也不能完好無誤的進行融合,這也是為什麼覺得項目經理、產品經理、技術經理的角色一人扮演要比三人分飾更好的原因。成熟的社會體制下,人與人間的分工應該更加明確,這無可厚非,問題就出在很多公司分工提前明確了,協作體制卻不完善。如此這般,做一件事情參與的人越多,不但不會節省工時,反而導致的問題越多,大大降低了工作效率。
負責資料庫設計工作的人應該是最懂項目、最懂業務需求、最有設計經驗的人,此人必須跟蹤整個項目的設計開發過程,產品界面的設計、程序的開發都要和其溝通確認方可。項目設計開發實施過程中,很多決定,不是參與決定的人越多越好,而是由一個最懂項目的人決定最好。有時為了某個決定組織集體討論,最終的決定往往來自於主導討論者,而非是最懂項目者。做項目和帶兵打仗一樣,最怕飯桶主導局勢。而做為項目主負責人,你可以不是最懂項目的,但一定要清楚的是這個項目安排給誰去懂最合適,他有沒有這個能力、是否能聽從安排。一旦讓他去懂,在項目推進過程中觀察他是否能勝任、各子環節最懂的那個人又是誰。明白誰最懂、誰肯聽眾安排,則把相應部分的決定權交到相應最懂、最聽從安排的人手中。而後除非出現特殊情況,自己不要去干涉、也不要讓其他人去干涉。
D.主設計師
原則上講,百張表內的資料庫最好由一人來設計。一百張表,即使每張有一百個欄位,總共也不過一萬個,如果用工具生成,三五天內足夠。當然資料庫設計工作的難易並非是由表個數、欄位個數決定的,而更多在於業務的複雜程度。表個數、欄位個數在一定程度上可以反應出業務的複雜程度,但卻非決定性因素。這裡講百張表內的設計工作量並不大,是說業務理清後將設計具體化成CDM、PDM的體力工作。要求由一人來設計是為了確保最終資料庫的統一性、完整性、協調性,如果不能保證統一,最終項目的穩定性絕對得不到保障。不僅資料庫的設計如此,架構、程序、前端、樣式、腳本、UI都一樣。在項目設計階段,通常情況下,局部模塊設計的優良並不會提升整個項目的質量,然而局部模塊設計出現的問題最終卻可能撼動整個項目的穩定。千里之堤毀於蟻穴,務必要確保設計工作的謹慎協調統一。再說大部分企業內部應用系統,項目規模有限,一個人主設計足夠。
特殊情況下,如果項目大到一定程度,所有資料庫設計工作交到一人手上著實過量,不得不安排多人參與其中,那主設計師也必須只有一位,且所有參與人員都要嚴格遵守相應的資料庫設計規範。要利用PD的版本控制功能協調統一,最終由主設計師匯總校驗所有人的設計,最終的資料庫設計應該看起來像一個人的傑作,這也是程序、腳本、樣式、UI設計開發所追求的目標。
主設計師匯總校驗之後,還應再組織會議對設計成果校驗,包括所有參與設計的人員、程序開發人員等,一起討論。查找可能出現的不合理的地方,比如有部分可能和需求業務不合、可能會影響到開發實現等等。資料庫的校驗審核工作,參與人員儘可能多點,鼓勵提問,有助於查缺補漏,發現問題。
E.關於DBA
有公司規定所有項目的資料庫設計,DBA必須參與,但資料庫設計工作重在對業務的把控、了解,其次才是對資料庫本身的了解,這兩項缺一不可,且前者更為重要。DBA的專業技能或許會更好一點,但是對業務的了解呢?還有公司的運維團隊中,DBA會脫離項目實際,設置一些不必要的資料庫規範,強加給開發團隊。
在做項目時,一方面非常討厭外部強加的規範,另一方面又不停的給自己、給團隊設定規範。討厭外部強加的規範是因為這些規範大都是些PMO指定的人憑空設想出來、脫離項目實際的,這些規範只會給項目帶來更多的麻煩。而給自己和團隊設定規範,則是為了約束設計和開發行為,確保項目最終實現的合理統一協調,這在後面的「命名規範」——「引言」中還有進一步的說明。
大的項目、大的團隊可能會有多個專業DBA負責資料庫維護工作,但就自己接觸,即便公司、團隊本身有DBA,絕大部分規模的項目中資料庫維護工作還是多由程序開發人員兼顧。再者,真正優秀的DBA少之又少,有時不夠專業的DBA過多介入反而會阻礙項目的正常開發。
DBA出現的時機應該是在開發人員無法解決資料庫出現的問題時,比如當數據量大到一定程度,項目運行緩慢,僅憑程序優化已遇到瓶頸,這時可以向公司申請DBA介入,優化資料庫設計、SQL語句等。再比如遇到數據備份問題、出現數據丟失問題等,也可申請DBA協助。如果項目規模大到一定程度,出現性能瓶頸問題是很正常的,這時專業DBA的作用才開始突顯,且會在其中扮演一個非常重要的角色。
總之,DBA應該是在被需要時出現,而不應該被強制需要。
我的項目開發思路非常明確,關於團隊,人越少越優秀越好,人員明確分工;關於開發模式,前端、後端、資料庫明確分工。在項目規模、工作量允許的範圍內人越少越好、涉及合作的部門越少越好,以便統一管理控制,節約溝通協調的成本。開發過程中的規範和約定採用大一統的方式,嚴格限制脫離中央管控的腳本或代碼出現。
3、基本原則
A.數據編碼
這裡建議將MySQL資料庫編碼設置為utf8, SQLServer、Oralce可先採用默認設置,有需要則根據實際情況做相應變動。如果是MySQL在安裝時就應注意,下圖是當前電腦中資料庫的編碼設置,安裝時沒有改過,是默認的,所以顯示的是latin1。
編碼問題設置不妥當,資料庫導入導出備份很容易出現亂碼,尤其是舊版本的MySQL資料庫。日常的資料庫維護操作推薦使用其官方工具MySQL Workbench,Oracle的話推薦SQL Developer,也是官方工具。如果使用MySQL Workbench創建新的資料庫,會要求選擇Collation,默認即可。Collation的字面意思是字元序,用於指定數據集如何排序,以及字元串的比對規則。這地方的設置會影響到數據查詢時大小寫敏感的問題,這裡簡單一提,具體介紹的會在命名規範中給出。
如果是MySQL資料庫,表類型統一使用InnoDB。InnoDB和MyISAM是在使用MySQL最常用的兩個表類型,各有優缺點,視具體應用而定。其區別在於MyISAM類型不支持事務處理等高級處理,而InnoDB類型支持。MyISAM類型的表強調的是性能,其執行數度比InnoDB類型更快,但是不提供事務支持,而InnoDB提供事務支持及外部鍵等高級資料庫功能。沒有進行過額外設置,資料庫默認安裝的就是InnoDB類型。
B.高級功能
在本章節的引言中提到過,存儲過程、觸發器、函數、視圖、事件等高級功能的設計規範都未給出詳細說明,因為本規範中要求,在當前階段的所有項目中,禁止使用存儲過程、觸發器、函數、視圖、事件等高級功能,資料庫中只有表,資料庫只做數據存儲、標明業務關係。而具體業務邏輯相關的處理均由程序實現,比如分頁、日誌記錄等等。
某些情況下,一些業務邏輯放在資料庫中處理比放在程序中處理要簡單許多,但這會額外增加資料庫的維護工作,從全局來看,也就是增加增個項目的維護工作。尤其是項目要求支持多資料庫時,不同資料庫存儲過程、觸發器、函數等的編寫方式差異巨大,很難統一控制。業務邏輯下壓到資料庫的部分越多,這種維護就愈加困難,到一定程度必然要求有專業的DBA配合,額外增加了不必要的麻煩。視圖的添加更是沒有必要,連接查詢可以實現的功能,用視圖只會從全局上擾亂原有的資料庫表關係,讓程序多維護一套查詢程序,徒增麻煩,而所得收益完全可由單純的程序實現。
前面已提到所堅持的開發模式,給前端、程序、資料庫明確的分工:前端只負責數展示、人機交互,由前端腳本處理展示交互相關的邏輯;後台程序負責橋接前端界面和資料庫,處理核心業務邏輯;資料庫部分只用來數據存儲、標明業務關係。三部分各司其職,不要在資料庫中直接處理業務邏輯,不要在後台程序中直接處理前端展示或人機交互的邏輯。
當項目龐大到一定程度、業務邏輯複雜到一定程度、數據量並發訪問量增加到一定程度,單純的從程序、前端中優化效果有限,需要多管齊下時,再來動資料庫。即便到那時,最好也是在PD中統一設計這些功能,可以自由導入。否則,如果是多資料庫支持的項目,每種資料庫都得分別手動編寫不同的代碼,這樣配置會很麻煩,也很難統一控制。而對於絕大部分中小型項目,這些麻煩本是沒有必要出現的。
C.遵守範式
目前關係資料庫有六種範式:
第一範式(1NF)屬性不可分。是指在關係模型中,對域添加的一個規範要求,所有的域都應該是原子性的,即資料庫表的每一列都是不可分割的原子數據項。
第二範式(2NF)在1NF的基礎上,非碼屬性必須完全依賴於候選碼(在1NF基礎上消除非主屬性對主碼的部分函數依賴)
第三範式(3NF)在1NF基礎上,任何非主屬性不依賴於其它非主屬性(在2NF基礎上消除傳遞依賴)
巴斯-科德範式(BCNF)Boyce-Codd Normal Form(巴斯-科德範式)在1NF基礎上,任何非主屬性不能對主鍵子集依賴(在3NF基礎上消除對主碼子集的依賴)
第四範式(4NF),要求把同一表內的多對多關係刪除。
第五範式(5NF),又稱完美範式,從最終結構重新建立原始結構。
這些範式仔細講起來很耗文筆,關於範式的詳細介紹可以參考文檔末尾的參考文獻。在設計資料庫時,儘可能做到前四個即可。但範式並非硬性要求,而是設計時儘可能遵守的大原則。對於這些範式也沒必要刻意去記,關係型資料庫說到底只是二維的行列組合,參與的資料庫設計多了,設計理念自然成型,會不自覺的去向這些範式中靠攏,設計過程中可以以此為依據校驗自己的設計是否合理,及時修正不當設計。
特殊地方,為了操作方便,在不影響核心業務的情況下,允許小範圍的不遵守範式。平時的多圖片上傳功能,可能只設計一個欄位存儲圖片名稱,這樣欄位值中就會包含多個圖片的名稱,裡面用|或其它符號分隔,這樣屬性值就可分了,也就違反了第一範式——屬性不可分;還有地區表中的,地區編碼欄位也隱含有等級信息;再就是菜單相關表,裡面分等級互相引用等等;嚴格來講這些都是不符合規範的。
在小範圍內,不影響到核心業務流程,為了設計開發的方便,允許一些特殊的設計出現。規範是用來約束設計的,約束的目的是為了最終整體設計的合理性、為了最終項目的穩定性,如果這種全局的規範約束在局部影響到開發實現,可以適當調整,但前提是設計者要有足夠的經驗和能力駕馭住這種不當調整。
D.連接查詢
因為不允許使用視圖,所以在表設計時要儘可能的考慮到後面的連接查詢,目前所接觸項目的數據量都尚在可控範圍之內,一般的業務系統,允許三張表以內的連接查詢,五張表以內的要限制,超過五張的禁止。禁止使用子查詢,子查詢轉換成關聯查詢。
如果要儘可能避免多表連接查詢,那在設計資料庫時,有關聯關係的地方,一般從表中除了有引用主表主鍵的外鍵欄位外,還要有一個或多個欄位存放主表中的關鍵信息,比如病人表中有所屬醫院所屬科室主鍵的外鍵欄位,但還可能會有所屬醫院名稱所屬科室名稱的欄位。因為嫌數據冗餘、維護不易,之前自己一般不設計除外鍵外存放主表信息的其它欄位,但這樣查詢時又會多添好些麻煩。可如果加上這些欄位,不單單數據冗餘、維護麻煩,也不好保證數據的準確協調統一性。比如如果醫院的名稱被修改了,那按常理病人表中的醫院名稱也得做相應的修改才可以,這樣,如果醫院表被許多表引用,那就得對所有的表執行修改動作,很是麻煩,實現起來也不現實。如果都加上引用約束,依賴資料庫自己的關聯自動更新,覺得也不是很好,影響程序執行速度且不易維護。可如果是在會診單表(類似於訂單表的功能)中出現類似的情況,就無須有這種顧慮,如果這個會診單在產生時醫院是這個名稱,後面名稱有了更改,那會診單中的醫院名稱還是顯示早前的即可,無須做相應更改,這也是符合邏輯的。
類似情況經常出現在字典相關信息的存取中,平時只在表中存字典編碼,但查詢時又往往要求同時提供字典文本,核心業務表中的字典欄位往往比較多,暫時沒有好的方法一次高效提取完整信息。後面在做類似設計時,核心業務表,字典欄位比較多的,根據實際情況,考慮同時存入字典編碼和字典文本,這樣可避免部分連接查詢。但同時,還是會出現上面提到的問題,一方面是數據冗餘,一方面業務表中的字典文本有可能會和字典表中的文本不一致——如果字典信息有更改的話。
這裡很難找到一種兩全其美的辦法,既能避免數據冗餘,又能讓程序在執行讀和寫動作時都方便高效,如何在各方面之間拿捏均衡是個人經驗問題。通常情況下,是在數據的協調性、準確性允許,跨表查詢又不容易(從表外鍵較多)的業務模塊,採用在從表中附加額外欄位的處理方式;在對數據顯示的同步性、準確性有嚴格要求,跨表查詢也相對容易(從表外鍵較少)的業務模塊,採用從表中不設外鍵以外的附加欄位、而使用關聯查詢的方式獲取完整數據信息。
在普通的業務系統之外,還有一種情況,可能不得不大量使用函數、子查詢、嵌套查詢。和可視化部門合作過的一些項目,系統前端引入ECharts,大量的環形圖、柱狀圖、折線圖等用於展現數據統計分析結果。有些圖形需要的數據很難用簡單的SQL一次提取出來,可如果多次提取後再由程序組裝處理,又太過麻煩,最後還得考驗SQL,此種情況是允許子查詢、嵌套查詢、多表連接等複雜SQL出現的。
過去曾藉助外部框架設計出一套異常靈活的架構,封裝了查詢對象,消除掉了SQL語句,應付通用的業務系統足夠,但在做類似這種統計分析功能時卻變得各種不自在,到後來還是覺得直接寫SQL更方便。此處只能在設計的架構上放開一個口,讓開發人員可以自由編寫SQL語句提取數據,最終的查詢結果統一封裝成一個List&
E.主鍵外鍵
每張表必須有唯一主鍵,此主鍵只用來標識記錄唯一,沒有其它任何功能,業務表中其它任何的惟一欄位均不能做主鍵,比如用戶表中的身份證號、地區表中的編碼,雖然也是唯一,但均不可做主鍵。主鍵為UUID,統一命名為id,統一為char(32)類型。相較於自增主鍵類型,使用UUID會導致查詢速度稍慢,可用自增主鍵的話,如果同時支持多資料庫就比較麻煩了,比如Oracle實現自增功能必須用序列輔助處理,還有,備份合併不同資料庫中相同表數據也會出現衝突,設計分散式架構、橫向拆分表也會出現類似問題等等。關於主鍵,還有一點要強調,原則上講,多對多關係中間表外的任何其它表不允許出現複合主鍵。
外鍵,多和主鍵對應,多是引用另外一個表的主鍵,那麼這個外鍵及所有表的主鍵的類型也應該是一樣的,都規定成char(32)類型。再就是外鍵的命名也要統一,這個也可以通過PD由CDM轉換成PDM時統一處理,使用「主表名_主鍵名」的命名方式,比如user_id,如果一個從表中有多個欄位同時引用主表的同一個欄位,那再用其它標識,比如在會診單申請表中會診發起醫院(sender_hopital_id)和會診接收醫院(receiver_hospital_id)。
主鍵和外鍵是資料庫設計中非常關鍵的兩個地方,在後面的約束控制和命名規範中還有更詳細的介紹。
三種資料庫關係中,多對多關係是最麻煩的,中間表往往使用複合主鍵,在程序架構上對這部分業務的處理不太容易,設計程序架構時要特別注意。
F.邏輯刪除
關於針對資料庫的刪除動作,仔細考慮後還是決定,現階段暫時仍舊使用物理刪除。嚴格意義上來講,所有的軟體項目都不應該有物理刪除動作,目的很明確,就是為了數據的安全性完整性。如果使用邏輯刪除,給每張資料庫表加上一個is_active欄位,數據的安全性完整性雖然可以得到保障,但卻跟程序編寫帶來許多麻煩。比如所有的查詢方法都需要額外加上is_active=』0』的判斷條件,在涉及多表連接查詢時就很不方便。再有,執行假刪除動作時的級聯處理也會相對麻煩。還有,如果數據表的某個欄位要求唯一,並強制約束,比如用戶表中的登錄用戶名欄位,設計為邏輯刪除的話,一旦有新的同用戶名記錄就無法插入。但如果不將該欄位設置為唯一性約束的,那麼在每次插入數據的時候,都需先進行一次查詢,看看有無未(邏輯)刪除的同名記錄存在,低效率是一回事,而且在高並發的系統中,很難保證其正確性。
此外,使用邏輯刪除,隨著項目應用時間的增加,將會有大量無效數據產生,如何處理?如果數據量大到影響查詢速度,程序如何做優化?像日誌表,很容易就到上百萬。如果定期將邏輯刪除的數據轉移到另外的表中,覺得處理起來也不簡單,還可能會有附加問題出現。如果使用腳本定期徹底刪除邏輯刪除的數據,如何疊加備份這些刪除的數據?
綜合考慮,對於現階段的項目,邏輯刪除所帶來的好處有限,但帶來的問題卻很多。如果平時做好數據備份工作,還是可以預防物理刪除隱患的。但心裡應該清除,當項目大到一定程度,對數據安全性的要求高到一定程度,使用邏輯刪除代替物理刪除是必然的,在後面的資料庫設計中,可以先小範圍的嘗試使用邏輯刪除,一旦開發模式成熟,就全面使用邏輯刪除代替物理刪除。
G.性能安全
對於中小型項目,資料庫設計過程中過多的考慮性能問題往往弊大於利,程序部分的設計開發也是如此。不是不考慮,而是不要過多考慮。性能及安全問題太大,這些問題的重要性都是隨著項目規模增長的,在中小型項目中過分的考慮只會給開發帶來不必要的麻煩。中小型團隊,時間人力成本有限,要用在關鍵環節上,用最短的時間做出成型的項目是最重要的。至於設計開發,儘可能保證好其協調統一靈活穩定性,一旦出現問題或需求調整,可以隨時靈活的更改就好。
4、欄位設置
A.欄位類型介紹
不同的關係型資料庫在欄位類型的具體化上差異較多,這裡無法一一詳述,但具體化的欄位類型再多,無外乎幾種:字元、數字、日期、二進位。
下面有三張截圖,前兩張是一張典型的用戶表結構,上面的是PD中的PDM,下面的是導入到MySQL的具體表結構,第三張截圖是SQLServer資料庫中的一張用戶表的結構圖:
個資料庫的某個具體類型,這裡無法詳述其適用情況,本模塊只制定通用的欄位設置規範。
B.不為空默認值
表中應該儘可能避免可為NULL的列,且儘可能顯示設置默認值,尤其是被索引的列。
在MySQL資料庫中,空值是不佔用空間的,而NULL其實是佔用空間的。再者,MySQL表的列中包含NULL的話,該列就不會包含在索引中,也就是說使用索引是無效的,現在不確定其它資料庫是否也是如此。 所以考慮今後可能會使用索引的欄位,就要設置欄位屬性是NOT NULL。比如,如果某個欄位後面可能會作為查詢關鍵字使用LIKE的形式進行搜索,就要將該欄位定義成索引以提高查詢速度,那這個欄位屬性就是NOT NULL的。
除以下數據類型的欄位外:timestamp、image、datetime、smalldatetime、uniqueidentifier、 binary、sql_variant、binary 、varbinary,表欄位應儘可能顯示設置默認值。建議數值型的默認值為數值0,布爾型的默認值為數值1(通常情況下,系統中所有邏輯型中數值0表示為「真」、「正常的」;數值1表示為「假」、「異常的」,這種編碼後面還會有介紹),datetime、smalldatetime類型的欄位沒有默認值,必須為NULL。
如果資料庫中某個欄位有默認值,那麼覺得在程序開發過程中,對應實體類的屬性應該設置同樣的初始化值才合理,記得動軟代碼生成工具中的框架就是這樣設置。之前自己的程序設計中沒有注意到這點,自動生成的所有實體類的屬性都沒有默認值。
注意區分NULL和空字元串是不同的,數值型欄位中NULL和0更是兩碼事。如果在資料庫設計過程中不允許出現NULL欄位還好,但如果有允許NULL而沒有設置默認值的字元型欄位,程序對記錄執行了寫空字元串動作和壓根未執行寫動作是兩碼事;如果有允許NULL且設置默認值為空字元串的欄位,則無法做這種區分。當然,通常情況下,我們認為文本框中空字元串的提交動作等同於未執行寫入。有些類似的情況是,在程序開發中,一個空的List對象,或者一個new出來的空對象,和NULL也是不同的,要注意。
像訂單(會診單)這種表,取消、退回、安排這些欄位的信息都不是必須有的,可以分流到子表中存儲,放在一張表中會導致出現很多可為NULL或空值的列。之前並不贊成這種過分分流的方法,因為這會另信息的維護變得麻煩,如再有類似情況,應該根據實際綜合判斷取捨。也要在設計時儘可能遵守第二、三範式,非主屬性完全依賴於碼(主鍵)、消除傳遞依賴,不要讓某張表過分臃腫。
C.類型長度設置
當欄位定義為字元串類型時建議使用varchar而不用nvarchar以節省空間,通常情況下,都要用盡量少的存儲空間來存儲一個欄位的數據,能用int類型的就不用char類型,能用char類型就不用varchar類型,能用varchar(20)的就不用varchar(25) 。char和varchar長度設計需要根據業務實際需要進行長度控制,禁止預留過長空間。比如主鍵要求用UUID,那就統一為char(32),可以固定的部分就都固定下來。varchar類型雖然根據實際長度進行存儲,但內存分配則是根據指定長度,不合理的長度設計會導致內存的不合理佔用。
varchar是變長存儲,欄位長度是資料庫一種約束,定義合理的長度也可以讓人容易理解欄位的用途。MySQL中定義的長度如果小於255,欄位長度用1個位元組表示,如果超過255,欄位的長度將固定用2個位元組表示。Oracle沒有這樣的問題。欄位定義的長度對索引也有較大影響,MySQL資料庫索引存儲的長度都是定義的長度,不是實際字元的長度,這是一個非常大的問題,估計主要原因是為了實現簡單,所以MySQL在索引上會浪費大量的空間保存字元串。
前台、程序以及資料庫各部分之間對欄位大小的限制務必處理恰當,為了節省存儲空間,選取的資料庫欄位容量在一定範圍內應該儘可能小,而為了對程序提供更好的擴展支持,又應該儘可能的設置大些,具體欄位類型、欄位長度如何設置,根據實際情況取得均衡。而後台程序部分,對數值大小長度應該做好校驗處理,確保插入資料庫的值大小長度不要超過限制。同時前端也應該給出明確的校驗提醒,讓用戶按提示輸入,決不允許不提醒用戶而擅自把數據處理後插入資料庫中(這種錯誤真有人犯過)。這樣,前端、程序、資料庫全方位校驗處理,自然可以保證數值的準備性、存取的合理性。
除非要保存文章內容, text欄位盡量少用,如果要用能拆到冗餘表中最好。禁止使用blob類型保存大文本、附件、圖片等,對於圖片、文檔等附件資料庫中只保留原始文件名和存儲路徑。網上也有建議使用其他存儲方式的,比如TFS、SFS等,可以參考。
禁止使用float、double類型,建議使用decimal替代。decimal(a,b) ,a指定指定小數點左邊和右邊可以存儲的十進位數字的最大個數,最大精度38。 b指定小數點右邊可以存儲的十進位數字的最大個數。小數位數必須是從 0 到 a之間的值。默認小數位數是 0。比如decimal(5,2)規定了存儲的值將不會超過5位數字,並且小數點後面有2位數字。
D.Oracle的CLOB
在Oracle中,CHAR 為定長字元串,最長2000位元組 。VARCHAR2 為變長字元串,最長4000位元組。NCHAR和NVARCHAR2分別與CHAR和VARCHAR2相對應,但存儲的數據為 NLS字元 。
目前VARCHAR是VARCHAR2的同義詞。工業標準的VARCHAR類型可以存儲空字元串,但是Oracle不這樣做,儘管它保留以後這樣做的權利。Oracle自己開發了一個數據類型VARCHAR2,這個類型不是一個標準的VARCHAR,它將資料庫中VARCHAR類型的列可以存儲空字元串的特性改為存儲NULL值。如果你想有向後兼容的能力,Oracle建議使用VARCHAR2而不是VARCHAR。
在Oracle中沒有TEXT類型,但有用於大文本存儲的CLOB類型。Clob是指大字元對象,也就是英文Character Large Object的縮寫;Blob是指二進位大對象,也就是英文Binary Large Object的縮寫;由此可見這兩個類型都是用來存儲大量數據而設計的。
LONG 最大存儲2G字元數據,但現在已不推薦使用(改用CLOB);CLOB 在Oracle 9i 及以前,最大可存儲4G字元數據 ,在Oracle10g及以後,最大可存儲4G*資料庫塊大小的字元數據;NCLOB 基本同CLOB,就是存儲的數據為NLS。
在Oracle資料庫表中使用CLOB類型欄位,最大的問題是備份數據時不好處理。在有些情況下,給政府、企業做項目,只給你Oracle的訪問許可權,而不給你Oracle所在伺服器的操作許可權,也就是說自己無法操作Oracle服務端工具。但Oracle的客戶端中又沒有exp、expdp命令,這樣備份導出資料庫就不好弄了(此處不提溝通協調甲方處理)。SQL Developer是Oracle的官方工具,用其導出資料庫,如果導出的是SQL格式,那CLOB類型欄位的數據將直接被忽略——這絕對是無法接受的。官網上有文章說可以將資料庫導出為loader或pdf格式,自己嘗試導出這兩種格式,發現不能導出成單個文件,會導出很多的文件。而且導入時也需要用到額外的工具——Oracle服務端的sqlldr.exe,這樣只藉助客戶端也是不行的。
在SQL Developer「工具」菜單下,還有兩個選項:「資料庫Diff」及「資料庫複製」,如果所處網路可同時訪問源資料庫和目標資料庫,可用這種方法互相拷貝數據,但是同樣的問題,這種資料庫複製方法,仍然是不能處理COLB、BLOB的欄位。而且我發現,凡是帶有這兩種欄位的表,在複製時都沒有數據,不是相應欄位沒有數據、是整個表的數據都沒有複製,其它沒有BLOB、CLOB欄位的表,數據拷貝都正常。
也曾想使用的PL/SQL Developer工具進行備份,導出了PL/SQL Developer自己的格式(pde)。可是卻提示stream read error,到網上一查,原來PL/SQL Developer自己的格式也是不支持COLB 、BLOB類型欄位的導出的。
之前同事介紹過Navicat for Oralce工具,但其在導出CLOB、BLOB類型的欄位時,如果欄位中的數據過長,也是無法再正常導入的。這個小工具看似簡單輕巧,在執行一些操作時問題卻很多,不宜作為一款常用的Oracle管理工具。
這樣看來,只有expdp命令才能有效導出clob、blob格式的欄位了。
cd D:appscproduct12.1.0dbhome_1BIN
exp username/password@tnsname file=C:UsersAdministratorDesktop20170323.dmp owner=username
imp username/password @tnsname file=C:UsersAdministratorDesktop nsname.dmp ignore=y full=y statistics=none
不過,如果你雖然沒有源資料庫服務端的訪問許可權,卻有目標資料庫服務端的訪問許可權,且兩個庫可在一個網路中訪問,也是有辦法用EXP命令備份源資料庫的。就是讓目標資料庫服務端的TNS監聽源資料庫的實例,再利用目標資料庫Oracle服務端的exp.exe工具遠程導出源資料庫,導出導入命令和上面類似:
cd D:appscproduct12.1.0dbhome_1BIN
exp origin_username/origin_password@origin_tnsname file=C:UsersAdministratorDesktop20150508.dmp owner=origin_username
imp target_username/target_password@target_tnsname file=C:UsersAdministratorDesktop20150508.dmp ignore=y full=y statistics=none
此外,用PL/SQL Developer工具備份數據時,Export User Objects菜單命令導出的是SQL文件,在這裡你可以將建表、序列、觸發器、存儲過程等的SQL語句全部導出成一個文件,但是這裡面並不包括數據。要想導出數據,必須用Export Tables……菜單命令,導出DMP文件。當然也可以導出其它格式的文件(SQL、PDE),但建議用DMP格式,因為前面已經說過,如果表中有CLOB類型欄位的話,用其它格式的導出方式恐怕有問題。
如果一個表不存在,而這個表中沒有CLOB、BLOB這種特殊數據類型的欄位,用DMP導入數據時PL/SQL會自動建立這個表。但如果一個表不存在,而這個表中又有CLOB、BLOB這種特殊欄位,直接導入DMP格式的文件會報錯 IMP-00003: 遇到 ORACLE 錯誤 959。所以在Oracle中導入資料庫時應該先執行用Export User Objects導出的SQL文件,這樣相關的序列、觸發器、表結構都已經建好了,再導入用Export Tables…… 導出的DMP文件,也就是導入其中的數據,就萬全了。
E.數值類型選擇
float:浮點型,含位元組數為4,32bit,數值範圍為-3.4E38~3.4E38(7個有效位)
double:雙精度實型,含位元組數為8,64bit數值範圍-1.7E308~1.7E308(15個有效位)
decimal:數字型,128bit,不存在精度損失,常用於銀行帳目計算。(28個有效位)
//結果顯示為345.9876,只顯示7個有效位,對最後一位數四捨五入。
float f = 345.98756f;
//結果顯示為345.975423578631,只顯示15個有效位,對最後一位四捨五入。
double d=345.975423578631442d;
//可以支持28位,對最後一位四捨五入。
decimal dd=345.545454879……
float和double的相乘操作,數字溢出不會報錯,會有精度的損失。當對decimal類型進行操作時,數值會因溢出而且報錯。
Oracle中的數值類型,Oracle只是在語法上支持decimal類型,但是在底層實際上它就是NUMBER類型,支持decimal類型是為了能把數據從Oracle資料庫移到其他資料庫中(如MySQL、DB2等)。Oracle的NUMBER數據類型的精度:NUMBER(P,S) ,P: 1---38, S:-84---127。S代表的是小數位數,P代表的是總位數(整數位數和小數位數)。所以,平時如果在Oracle中用自增主鍵,長度設為NUMBER(10)的話,相當於NUMBER(10,0),表示最高可記錄到十億級的數據量。
下圖是MySQL中的整數型數值類型詳述:
F.通用欄位處理
日期時間類型欄位,網上有建議,採用int來記錄unix_timestamp,自己還是習慣用datetime。不過設計原則是粒度越小越好,所以這裡要求日期時間類型的欄位,儘可能精確到時分秒,用datetime類型。即便是像生日(birth_date)這種欄位,一般只存儲到年月日,但在選擇欄位類型時建議還是用datetime而非date,以防萬一。如有部分時間欄位著實無須記錄到時分秒,則用date類型。嚴禁使用varchar等字元串類型記錄日期時間,更不要把時間猜分,年在單獨的欄位、月在單獨的欄位、日又是單獨欄位,老實講TM想不明白這種人的設計思路是什麼樣的。
網路IP欄位,網上有建議,除特殊情況一律用bigint來記錄inet_aton值,但這種存儲方式貌似只在MySQL中適用,這裡要求還是用varchar存儲。關於inet_aton想了解的話可以看下參考文獻中的「MySQL的IP處理函數inet_aton()和inet_ntoa() 」。
字典編碼欄位,之前在SQLServer中設計資料庫時統一使用char(2)類型,Oracle資料庫中統一使用number(2),在MySQL中統一使用tinyint(2)。現在想來最合理的還是設置為tinyint(2),以後資料庫字典編碼欄位統一按此設置。就是Oracle中沒有tinyint類型,不知道如果在PD中設置此種類型,導入到Oracle時會自動轉換處理還是直接報錯。
備註欄位,儘可能在所有表中都保留這個欄位,也是給前端信息錄入預留一個可擴展部分。統一命名為remark,欄位類型為varchar(200),最多100個中文字元。再多的話說明有額外信息,就不適合放在備註欄位中了,要再加新欄位存儲。
排序欄位,不是每個表中都需要額外的排序欄位,但有些表這必須有,比如記錄菜單信息的表、門戶網站中存放文章內容的表等。這裡推薦統一使用int(10)做為所有表中的排序欄位類型。
欄位設置部分撰述內容較多,相對詳細,這是比較重要的一部分。以後的資料庫設計,欄位類型選擇、欄位長度設置部分都要以此為依據。
5、相關注釋
資料庫是整個項目的地基,後面程序架構設計及具體開發工作都要在此基礎上進行,如果沒有注釋或者注釋混亂,會給開發者帶來很多困擾,無形中影響項目進度。
在相對成熟的技術團隊中,所用開發模式、架構、框架也都已經成型,如果接手的項目類型不超出平時常規的範疇,以往的開發模式、架構及所用框架等都是無須做大的變動即可拿來複用的。這樣,在資料庫設計過程中或完成之後,程序的具體設計開始,再到進入正式開發過程中,需要頻繁組織開發人員參與的會議。會議討論的都是具體的開發工作,資料庫文檔是參考依據、必須的會議資料。
資料庫文檔一般根據PD中的資料庫文檔模板生成,PD自帶的資料庫文檔模板太雜亂,所以最好單獨設計一套適合自己團隊使用的模板。比如平時在資料庫設計過程中不用存觸過程、觸發器、序列、函數等高級功能,那我設計的模板自然不包括展示這些信息,只顯示錶結構、約束、表注釋、欄位注釋等信息,生成的文檔也會簡單明了。如果不想單獨設計,網上也有一些他人整理的模板,或可根據實際情況拿來使用。
團隊內部使用的資料庫文檔一般是HTML格式,查看比較方便,而對外提供的,向PMO或客戶提交留存的,一般是RTF或DOC格式的。
用PD完成設計之後,將PDM導入到資料庫,同時生成資料庫文檔,後期所有針對資料庫的討論都會以這份資料庫文檔和PDM圖為依據。後面資料庫表結構如有變化,文檔會同步更新。而這份文檔中的文字說明內容,就來自於資料庫設計過程中的相關注釋。再有,現在的程序設計好之後,具體代碼大都是由工具生成,程序中的實體類、方法說明等等最終也都是由資料庫中的相關注釋演化而成。所以注釋說明的語氣,甚至標點符號都要注意,只有資料庫注釋規範了,資料庫文檔、程序注釋、程序文檔、對外介面文檔才能規範,因為這些的起始點都是在資料庫中的注釋上面。
資料庫中的關鍵注釋其實就兩部分,一部分是表注釋,一部分是表欄位注釋。表注釋對應程序中實體類的注釋,欄位對應程序中實體類屬性的注釋。
A.表注釋
對於表的注釋,要求簡單明了,先說明表中文名稱,句號分隔,然後跟功能說明,無需特別說明的,則只說明中文名稱即可。同時,為了最終生成代碼注釋的可讀性,不允許在說明中出現「表」字眼。比如訂單表,直接注釋「訂單」即可,如核心業務表需要其它特殊詳細的功能說明,要考慮到最終生成程序實體類注釋的可讀性。再有,如果資料庫由多人協作設計,那表注釋中必須標明創建者和創建時間信息,示例如下:
創建者:孟憲志。創建時間:2016-09-10 13:30。說明:系統日誌。用於記錄用戶對系統的所有操作。
資料庫設計完成進入開發階段,後續擴展新增的表也都要按如上格式注釋,同時說明是後續新增表,及新增原因等。
B.主鍵欄位
主鍵欄位,無須特別注釋,注釋就兩個字:主鍵。
C.外鍵欄位
外鍵欄位,注釋先說明欄位中文名稱,句號分隔,後面要特別說明是外鍵,然後說明引用哪個表的哪個欄位,再句號分隔,如還有其它說明,則繼續跟後面。比如:
` applicant_doctor_id` char(32) DEFAULT NULL COMMENT "申請醫生。外鍵,引用醫生表(doctor)的主鍵(id)。理論上可填多個,以「|」分隔"。
前面說過為了避免多表連接查詢,有關聯關係的地方,一般從表中除了有引用主表主鍵的外鍵欄位外,還要有一個或多個擴展欄位存放主表中的關鍵信息,這個擴展欄位通常都是和對應外鍵欄位並列存放,注釋時先說明欄位中文名稱,句號分隔,後面要特別說明是名稱(或其它),然後說明和哪個表的哪個欄位對應,再句號分隔,如還有其它說明,則繼續跟後面。格式如下:
` applicant_doctor_name` char(32) DEFAULT NULL COMMENT "申請醫生。名稱,和醫生表(doctor)的醫生名稱欄位(name)對應。理論上可填多個,以「|」分隔"。
D.字典欄位
字典欄位在資料庫表中出現非常頻繁,要求所有表中的字典欄位注釋也要統一格式。注釋先說明欄位中文名稱,句號分隔,後面要特別說明是編碼,對應哪個字典表的哪個欄位,再句號分隔,後面跟詳細說明,如果字典項不多的話,要求先枚舉說明每種字典編碼代表的不同意義,句號分隔,如還有其它說明,則繼續跟後面。比如:
`status_code` varchar(5) DEFAULT NULL COMMENT "轉診單狀態。編碼,對應數據字典表(dictionary)中的編碼欄位(code)。目前先定義6個狀態:01 已提交,02 已取消,03 已審請,04已安排,05 已拒絕,06 已結束。03是向HIS系統執行審請,04是HIS系統已安排入院日期,下轉診時這兩個欄位沒有區別。"
如果有字典文本擴展欄位(類似於外鍵的擴展信息欄位),格式與上類似:
`status_text` varchar(50) DEFAULT NULL COMMENT "轉診單狀態。文本,對應數據字典表(dictionary)中的字典項名稱欄位(name)。目前先定義6個狀態:01 已提交,02 已取消,03 已審請,04已安排,05 已拒絕,06 已結束。03是向HIS系統執行審請,04是HIS系統已安排入院日期,下轉診時這兩個欄位沒有區別。"
字典表細分的話也有好多種類型,後面特別說明中還會細講。除了資料庫中的通用字典表,還有一些常見表,比如地區表、ICD字典等,也是一種字典表,其共性是:表結構設計完成後、程序開發前即完成數據填充,且後期很少改動;主鍵外至少要有編碼和文本兩個非空欄位,如果字典表中有細分子項的話,還會有字典項所屬類型欄位。
E.普通欄位
除以上三種常見類型的欄位外,其它欄位,都可用統一的方式注釋。先說明欄位中文名稱,句號分隔,後面跟詳細說明。比如:
`bed_id` varchar(50) DEFAULT NULL COMMENT "入院床位號。HIS系統安排並反饋的入院床位號,如果是下轉診,此部分由醫生手動填寫"。
還要特別說明的是,如果項目進入開發階段後需要對現有表新增某些欄位,或對現有表的某些欄位進行修改,和新增資料庫表一樣,也必須特別說明。
創建者:孟憲志。創建時間:2016-09-10 15:38 。說明:會員職務。交易平台新增欄位,個人用戶此欄位為空。
相關注釋部分,先說明了資料庫中注釋對項目開發的重要性,然後分五個子模塊:表注釋、主鍵欄位、外鍵欄位、字典欄位、普通欄位,詳細介紹。如今後所有的資料庫設計都能嚴格按此規範要求、按給出的格式添加註釋,則相關資料庫文檔、程序文檔、介面文檔都可隨之走向正規。
6、約束控制
建立約束的目的是為了防止資料庫中出現不符合語義規定、不符合業務邏輯的數據,為了確保數據的完整性、合法性。如果數據具有完整性,則表示數據有效——正確並且準確、資料庫的關係結構是完整的。完整性約束可以加強資料庫的關係結構,這些規則使數據在各個表之間保持一致。
總的來說有五種約束類型:唯一性和主鍵約束、外鍵約束、檢查約束、空值約束、默認值約束,對應五大關鍵詞,UNIQUE和Primary Key, Foreign Key, CHECK, NOT NULL, DEFAULT。這五種約束類型,除檢查約束外,其餘的在前面的章節中都有過介紹,比如主鍵、外鍵欄位類型的選擇,欄位長度的設計,注釋的規範等等,在後面的命名規範中也還會提到。
檢查約束(CHECK),用於檢查列的類型和範圍,語法:CONSTRAINT [constraint_name] CHECK (condition); 比如:check(Age &>2)。過去沒有在資料庫中添加檢查約束的習慣,在本規範中,也暫不做此要求,這會增加資料庫設計過程中顧慮點,還是交由程序和前端來控制這種細節上的數據合法性。
本規範中要求,除檢查約束外,其餘四種都要在資料庫中加強約束,同時,程序也會根據資料庫中約束的設置做相應處理。
主鍵不用說,一般情況下都會加約束的。要注意的是唯一欄位,比如用戶表中的電話、郵箱、登錄賬號等等明顯唯一的信息,都要加強約束,還有多對多關係中間表裡的複合主鍵,也要加強約束。再就是,前面也提到過,本規範中所有主鍵欄位統一使用UUID,char(32)類型,具體命名方式後面也會講。
外鍵部分,類型自然要和主鍵一樣統一為char(32),除了外鍵欄位,為了保存主表其它信息的擴展欄位類型也要和主表中的相應欄位類型統一。過去參與的項目中有的在資料庫中加了外鍵的強約束,有的則沒有,有的是在用PD設計時加上了主從引用關係,但在導入到庫中時去掉了外鍵的強約束,而只保留表結構。
表面看,資料庫中加上外鍵、非空、唯一等強約束會給程序編寫帶來麻煩,實則不然。約束是為了保證數據的合理性,如果資料庫設計的約束本身沒問題,那程序編寫中因約束而照成的不便就多是程序本身的不合理造成的。萬千世界,所有約束規範都是為了確保被約束規範的對象將所參與的事情做的更好,這種最終的好不是只針對某個對象的,而是針對所有參與其中的。假如某種規範不是這樣,那它本身就是不合理的;假如它是這樣的,而被約束規範的對象覺得在這種約束下做事情不便,那就是他自己做事方法的問題了,要由他自己根據規範修正。
所以強約束帶來的不便只幻象,這種不便不但不是壞事,還會倒逼程序開發更加趨於合理,修正開發中的錯誤。而不加強約束,程序可以肆無忌憚的對資料庫進行任何操作時,如果架構設計師及開發人員的經驗、技能較強還好,否則的話,程序很可能會因有意無意讀寫錯誤信息,造成針對資料庫的無效或錯誤操作,破壞掉數據的完整性、合理性。
有些情況下,加不加強約束設計者是決定不了的,比如公司有通用的許可權管理系統對所有子項目統一管理,那在設計子項目的資料庫時就不能添加和用戶相關的外鍵約束了。還有其它類似的情況:兩個項目之間有邏輯交差,但資料庫相互隔離,只能通過互相調用介面處理業務。這種情況下,如果想保證好數據的完整性準確性,只能由程序在業務邏輯層控制,必須設計開發足夠精密才能做好。
空值約束和默認值約束不再多說,在前面欄位設置中的NOT NULL子模塊講的很詳細了,只要記得設置欄位為NOT NULL、並給欄位設置默認值可以提高查詢速度、節省表空間就可以了。
7、索引添加
索引是一個單獨的、物理的資料庫結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。資料庫中索引的概念與書索引的概念非常類似,不同之處在於資料庫索引用來在表中查找特定的行。
在各種類型的資料庫中,索引又可細分為好多類型,比如之前系統整理過Oracle中的索引分類:非唯一索引(最常用)、唯一索引、點陣圖索引、局部有前綴分區索引、局部無前綴分區索引、全局有前綴分區索引、 散列分區索引、基於函數的索引。Oracle存儲索引的數據結構是B*樹,點陣圖索引也是如此,只不過是葉子節點不同B*數索引。
並未做過嚴格對比,不清楚添加索引後對查詢速度的提升到底有多少,對插入刪除修改的速度影響又有多少。就過去的經驗,對於Oracle來講,百萬級數據量一般不會出現明顯性能問題的,無需做特別的優化;MySQL的話,超過五十萬條數據就有點卡了,超過一百萬就得做優化了。對於諸多中小型的企業內部應用系統,如果數據量未達到這個級別而出現查詢性能問題的,多是因為程序,開發模式、架構、框架、編碼細節或SQL語句中肯定有特別不合理的地方。所以對於當前階段的項目,在出現查詢性能問題之前,不建議在資料庫設計過程中手動添加索引,而且大多數資料庫是會自動給主鍵和有唯一約束的欄位添加索引的。
當數據量達到一定程度,僅通過程序優化對性能提升有限時,可以考慮同時對資料庫進行調校,這時會優先考慮建立索引。
在前面的欄位設置中的NOT NULL子模塊提到過NULL值對索引的影響,下面再列出一些索引使用注意事項,大都是從網路上整理出、自己認為比較合理的,如果要建立索引,儘可能遵循這些規範(第一條沒大看明白):
a. 邏輯主鍵使用唯一的成組索引,對系統鍵(作為存儲過程)採用唯一的非成組索引,對任何外鍵列採用非成組索引。考慮資料庫的空間有多大,表如何進行訪問,還有這些訪問是否主要用作讀寫。
b. 大多數資料庫都索引自動創建的主鍵欄位,但是可別忘了索引外鍵,它們也是經常使用的鍵,比如運行查詢顯示主表和所有關聯表的某條記錄就用得上。在經常進行連接,但是沒有指定為外鍵的列上也要建立索引,而不經常連接的欄位則由優化器自動生成索引。
c. 在頻繁進行排序或分組(即進行 GROUP BY 或 ORDER BY 操作)的列上建立索引。
d. 不要索引blob/text 等欄位,不要索引大型欄位(有很多字元),這樣作會讓索引佔用太多的存儲空間。
e. 不要索引常用的小型表,不要為小型數據表設置任何鍵,假如它們經常有插入和刪除操作就更別這樣做了,因為對這些插入和刪除操作的索引維護可能比掃描表空間消耗更多的時間。
f. 在條件表達式中經常用到的不同值較多的列上建立檢索,在不同值少的列上不要建立索引。比如在僱員表的「性別」列上只有「男」與「女」兩個不同值,因此就無必要建立索引,因為建立索引不但不會提高查詢效率,反而會嚴重降低更新速度。
g. 如果待排序的列有多個,可以在這些列上建立複合索引(Compound Index)。
h. 索引不是越多越好,添加或刪除表記錄時,必須花費額外的時間來更新該行的索引。讀的方便,寫的就會有麻煩,反之,亦然。
8、特別說明
A.數據字典表
通用字典表的表結構如下:
/*==============================================================*/
/* Table: Dictionary */
/*==============================================================*/
CREATE TABLE [dbo].[Dictionary](
[dictId] [varchar](50) NOT NULL,
[dictTypeId] [varchar](50) NOT NULL,
[code] [varchar](50) NULL,
[name] [varchar](200) NULL,
[remark] [varchar](1024) NULL
)
/*==============================================================*/
/* Table: DictionaryType */
/*==============================================================*/
CREATE TABLE [dbo].[DictionaryType](
[dictTypeId] [varchar](50) NOT NULL,
[name] [varchar](50) NULL,
[remark] [varchar](1024) NULL
)
過去接手項目的字典表大都是這樣設計,在找到更優設計之前,字典部分延續使用此結構。不建議在通用字典表中分等級,如果有要分級的字典數據,建議額外建表處理,比如地區字典,或者是左側菜單(嚴格來講,左側菜單已經算不上字典)。
網上有建議將字典項放到XML文件中維護,如果是某些特定的字典項,比如剛提到的菜單項,數據量較少還可以,超過一定量就不現實了,對於大多數業務系統,通用字典表是必須有的。
自己對於字典數據的定義是,可枚舉,系統運行前即被錄入、運行後除非有特別許可權出現特別情況不可隨意更改添加刪除。除了通用字典表外,全國省市縣地區信息表、醫療系統中存放ICD數據的表等,原則上來講也是一種字典表。
關於布爾型欄位,比如男女、是否、對錯等,過去的設計中沒有使用資料庫特定布爾類型定義的習慣,都是放在字典表中統一定義,後面的設計中也要求如此。對於編碼,習慣用0和1,編碼規則通常是:0是多數的、常見的、正確的、主要的、正常的,1是少數的、少見的、不正確的、次要的、不正常的。比如是否中0代表是、1代表否,對錯中0代表對、1代表錯,男女中0代表男、1代表女,建議布爾型欄位的字典編碼延續此編碼方式。
雖然在業務表中的相關字典欄位存編碼已經成為自己的一種習慣,但和同事協作時發現還有人直接存文本,有必要申明這一點,字典欄位絕不能只存文本,這關係到整個項目的統一性協調性完整性。
在前面的「設計規範」——「基本原則」——「連接查詢」部分專門提到過字典欄位,有時候,在較為核心的業務表中可能出現五個甚至更多的字典欄位,比如訂單表,如果只存字典編碼,如何一次查詢出核心業務表中字典欄位對應的文本信息?全部都使用對字典表連接查詢的話效率太低了,這種情況下,通常將字典編碼和對應文本一併存入,至今未找到既可以只存字典編碼又可高效查詢的方法。
在程序開發過程中,具體到某一常用字典類型、字典項的編碼和文本信息需要即時獲取,以往的習慣在程序中建立一個常量類,所有用到的字典數據在裡面標明。前端部分也會有一個常量JavaScript文件,和後台程序中的常量類功能一樣,記錄常用字典類型、字典項編碼信息。比如:
public static final Integer DICTORY_ITEM_ID_USERTYPE_PERSONAL = 1;
public static final Integer DICTORY_ITEM_ID_USERTYPE_ENTERPRISE = 2;
public static final String DICTORY_ITEM_TEXT_USERTYPE_PERSONAL = "個人用戶";
public static final String DICTORY_ITEM_TEXT_USERTYPE_ENTERPRISE = "企業用戶";
JavaScript文件:
var DICTORY_ITEM_ID_USERTYPE_PERSONAL = 1;
var DICTORY_ITEM_ID_USERTYPE_ENTERPRISE = 2;
var DICTORY_ITEM_TEXT_USERTYPE_PERSONAL = "個人用戶";
var DICTORY_ITEM_TEXT_USERTYPE_ENTERPRISE = "企業用戶";
這裡單獨強調一點,目前使用的region地區字典表,存放全國省市縣的信息,裡面有必要加入一個是否可見欄位(is_display )和一個備註(remark )欄位。另外,通用地區字典表應該考慮到世界各國家、鎮、鄉、縣級市、市下面區、市下面縣的問題,中國一級行政區使用國家統計局定義的編碼,世界國家使用ISO-3166-1標準中的Alpha3編碼。當前的設計在存儲、處理世界上國家的編碼、中英文名稱等信息時已經不適,而要新增對鎮、鄉、縣級市、市下面區、市下面縣的存儲,編碼規則也要重新整理才可以滿足,屆時可以參考網路中的標準數據,不要隨意編製。
相信以上關於字典的處理還有更好的方法,在後續的開發中會探索留意總結。
B.系統日誌表
以往設計的項目中日誌分三部分:一部分是系統登錄日誌,一部分是用戶操作日誌,一部分是運行異常日誌。如果系統上線前測試沒問題,運行時異常出現的概率較小,一旦出現多是外部因素所致,比如網路中斷資料庫失聯等等,這部分通常記錄在本地日誌文件中的。系統登錄和用戶操作則要記錄在資料庫中,要有相應表存儲,下面給出之前設計的表結構:
/*==============================================================*/
/* Table: LoginLog */
/*==============================================================*/
CREATE TABLE [dbo].[LoginLog](
[loginLogId] [varchar](50) NOT NULL,
[userId] [varchar](50) NULL,
[loginTime] [datetime] NULL,
[logoutTime] [datetime] NULL,
[durationTime] [bigint] NULL,
[ip] [varchar](50) NULL,
[status] [varchar](5) NULL,
[browser] [varchar](50) NULL,
[resolution] [varchar](50) NULL,
[ipAddress] [varchar](50) NULL,
[operatingSystem] [varchar](50) NULL,
[userName] [varchar](50) NULL,
[userPassword] [varchar](50) NULL
)
/*==============================================================*/
/* Table: OperationLog */
/*==============================================================*/
CREATE TABLE [dbo].[OperationLog](
[operationLogId] [varchar](50) NOT NULL,
[loginLogId] [varchar](50) NULL,
[userId] [varchar](50) NULL,
[operationTypeCode] [varchar](2) NULL,
[operationTable] [varchar](50) NULL,
[operationContent] [varchar](500) NULL,
[isSuccessId] [varchar](2) NULL,
[resultCode] [int] NULL,
[objectRecordId] [varchar](50) NULL,
[resultMessage] [varchar](1000) NULL,
[totalCount] [int] NULL,
[addTime] [datetime] NULL
)
過去的設計中,幾乎所有表中都設有create_time、create_user、update_time、update_user四個欄位,如果操作日誌表設計的合理、又有相應程序配合足夠嚴密的話,除核心業務表外這四個欄位完全可以拿掉, 所有針對資料庫的增刪改查動作都會在日誌表中有跡可查。
但現在的系統登錄日誌及操作日誌部分仍有很多問題未處理好。
第一個是登錄日誌中對於登錄時長的記錄,因為目前寫的程序無法確定用戶離線時間,所以這個欄位的數據無法計算。
第二個是操作日誌中對非登錄用戶操作的記錄。接觸的大都是Web業務系統,非登錄用戶是不能對系統進行操作的,但如果項目中有對外部分(Public)呢,比如網站,那這些查詢動作要不要記錄呢?非登錄用戶的查詢動作記錄的意義不大,除非是搜索動作——如果有這個功能的話,有注於分析用戶習慣。但如果業務系統較大,搜索部分比較重要,應該有單獨的搜索日誌記錄功能才好。
第三個是操作日誌中對於增刪改查動作的細分。查詢操作較為頻繁,無論用戶是否登錄,其在系統中的每個動作都有可能觸發對資料庫的查詢,對這部分的記錄處理的不是很好。不是不好記錄,而是不好對查詢記錄進行分類。通常情況下,增刪改這類寫操作都要記錄日誌的,不過有些查詢操作卻沒有記錄日誌,比如當用戶在登錄系統時,也是調用的查詢方法,但有登錄日誌功能專門對這個操作進行記錄,那這個查詢操作實際並無必要記錄在操作日誌中。再比如,用戶在新增卡片信息時,系統可能要先判斷一下這個卡號是否已經存在於資料庫中,並給出相應的提示,那這個系統業務自身執行的操作還要不要記錄在操作日誌中呢?過去沒有對這些查詢進行記錄,但是不是可以完整記錄,然後對操作記錄進行分類,哪些是用戶直接解觸發的、哪些是間接觸發的由系統業務執行?可不可以把操作來自於哪一個頁面哪一個控制項、執行的哪一個動作都進行分類記錄,以方便後期查閱呢?
過去接手的項目,未登錄用戶多是沒有寫許可權的。現在的程序架構,日誌記錄功能放在了Service層,如果對查詢動作細分其來源的話,則當前架構需要調整。如果僅僅區分是否為登錄用戶的操作還好,如果要記錄哪些是用戶直接操作、哪些是由系統業務執行、甚至細分動作來源就不容易了,這些都是要做參數由Controller傳入Service的,或者將日誌記錄由Service中去除,分散到Controller中,真這樣做內聚性就差了。目前的架構,可做的就是,對於未登錄用戶的操作及用戶間接觸發的動作不做記錄,只記錄登錄用戶直接觸發的動作。
第四個是操作日誌中對於增刪改查詳細內容的記錄。比如增加的話增加了什麼、查詢的話查到了什麼、刪除的話刪除了什麼,修改的話修改了哪些屬性、修改前後屬性值各是什麼。把這些操作結果序列化成某種格式存儲在日誌表中到也不難,問題是都存入數據量太大了,想不到可以精確存入少量數據的方法。
第五個是操作日誌記錄的備份。當前階段的資料庫中,操作日誌表是數據量增長最塊的,過去並無有效方法處理,只能用腳本定期刪除歷史數據。網上有建議,日誌類數據不要直接存儲在MySQL上,優先考慮Hbase或OB,如需要存儲請找DBA評估使用壓縮表存儲。也有建議,採用活動庫與歷史庫分開設計,提高系統訪問即時數據的效率。認為合理又相對簡單的辦法還是後者,定期遷移過期日誌數據到備份庫中。此過程應該也可以用腳本處理,或者嘗試尋找用工具自動處理的方法。
對於日誌記錄存取相關的問題,都未能找到特別好的方法處理,以上解決方案只是建議,也會在後續的開發中繼續探索留意總結。
C.用戶許可權表
對於許可權的控制,由高到底大致分這幾個級別:系統級、模塊級、菜單級、頁面級、控制項級、數據級。菜單、頁面、控制項的落腳點都在請求鏈接地址上,數據級的許可權最難控制,不同的角色對不同的數據有不同的操作許可權,要對數據進行分類,自己過去負責的、接觸的項目中很少有對數據級許可權做到靈活控制的。系統級的許可權表示可以控制不同項目,平時的單一項目無須考慮。就是說在現階段只要控制好中間幾項就可以了,相關設計都是可復用的。
如果對用戶、角色、許可權相關的內容詳述的話,再寫這樣一份文檔也講不完,這裡只簡單一提,具體設計不講。業務系統的許可權控制功能必不可少,但真正設計好的卻不多,這部分的根本解決方案還是要開發通用的許可權管理系統,對所有項目統一管理。對於軟體研發公司、團隊,這是一勞永逸的工作,但必須要有足夠經驗能力的人牽頭做。
D.並發編輯處理
在設計程序架構時應該考慮這樣的問題,比如當一條會診記錄的狀態是剛提交時,那麼會診申請醫院和會診接收醫院都可以對這條記錄執行操作,會診申請醫院可以取消修改會診信息、會診接收醫院可以拒絕或安排專家。如果兩個用戶在同一時間段操作的話,很可能出現衝突。比如會診申請醫院有可能正在取消頁面中填寫取消會診的原因,而會診接收醫院恰好也進入了會診處理頁面正給同一會診申請記錄安排專家。類似的情況還很多,比如當系統用戶位於修改頁面中正在編輯某條數據時,恰巧有另外的用戶對此條數據執行了刪除動作,這時修改完後的提交動作就會出現異常,如何避免這一類的問題?
有個想法是,可以給資料庫表加個布爾型的鎖欄位,當有用戶可能對此表中的某一記錄執行寫動作時,比如進入修改頁面,標識鎖欄位(加鎖),任何用戶在想對此條記錄執行其它的修改動作前,要先查看鎖欄位是否處於加鎖定狀態。可問題也有,BS的系統中程序如何判斷用戶已經停止可能的更改動作,而恢復此標誌為解鎖狀態呢?比如用戶可能進入修改頁面後,並未執行修改動作,而是直接在瀏覽器中關閉了此頁面,但此時記錄卻還處於被鎖狀態。
此種類似悲觀鎖的處理方式之所以不可行,因為以上設計思路中妄想用資料庫中的表欄位標識用戶進出頁面的動作,可在BS的系統中,程序是無法監聽到客戶端頁面釋放的。再有,此設計中鎖的資源其實質是資料庫中的表記錄而非頁面,而在實現時卻想用其去鎖頁面,也不甚合理。由此,對於鎖,首先應該明確鎖的內容,其次應該明確添加、釋放動作是否可被捕獲。
那換種思路,不修改資料庫表設計,不加額外的鎖欄位。後台程序在執行修改動作時先做判斷,注意此處說的是後台程序而不是用戶,是在後台程序執行針對資料庫的寫操作時而非用戶進入修改頁面時。先根據主鍵對相應記錄執行一次查詢,如果有早前的操作導致本次操作無效,則給出提示,比如該記錄已被刪除、該會診申請已被取消不能安排專家等等。但此種解決方案只能解決記錄被刪除、會診單狀態被修改這種特定性的情況,無法處理該記錄和提交修改前的初始記錄不同的廣泛型問題,因為程序不好在執行寫操作前,逐個判斷整條記錄的每個具體欄位。有可能此條記錄已被刪除、有可能是會診單狀態有更改、也有可能是病情描述有修改。如果申請醫院修改的是病情描述,那接受會診醫院再做寫操作提交,就會把之前的病情描述覆蓋掉。
所以對於並發編輯的問題,目前唯一可行的方法,還是在資料庫中加入一個標識欄位。不過此欄位不能是布爾型,而是數值型或日期時間型。程序在執行修改前先執行一次查詢,檢查此欄位當前的值(時間)和本修改提交前獲取的原始值(時間)是否一致。如果一致,則此欄位的數值發生變化,同時正式執行針對記錄的修改動作;如果不一致,說明此條記錄在當前的修改期間被其他人修改過,此修改動作作廢,給出前端錯誤提示。但這樣的不好之處是,用戶有可能在操作完之後才發現,自己的之前的操作是無效的。比如在修改頁面都已經填寫完要修改的信息了,點擊提交才給提示此條記錄已被刪除。
理想的悲觀鎖解決方案應該是:既然有人正在編輯此條記錄,那在這段時間,其他人則不能進入針對這條記錄寫動作的頁面、不能執行針對這條記錄的寫動作。但前面也說過了,在BS的程序中,前端頁面的釋放動作無法捕獲,註定此方案不好實現。
嚴格來講,所有數據可修改的表中都應該有這樣的鎖欄位來處理並發,可以在程序的業務邏輯層對修改方法進行統一控制,校對鎖欄位。以往的資料庫設計、程序設計中,對並發編輯的處理考慮欠缺,很多處理也不甚得當,要引起注意。
E.歷史版本設計
在資料庫設計過程中,經常會遇到一個需求,就是希望把操作之前的數據保留下來,能夠看到操作之前是什麼數據,操作之後是什麼數據。對於這種需求,我們可以使用保留歷史數據或者使用版本來實現。
版本號是一種常見的版本設計方案,就是在要進行歷史數據保留的表上面增加一個版本號欄位,該欄位可以是datetime類型,也可以是int類型,每進行數據操作時,都是創建一個新的版本,版本是只增不減的,所以只需要拿到最大一個版本號,就能得到最新的業務數據。
版本號和上面針對並發編輯加鎖的解決方案類似,兩者表現在在資料庫中的設計完全一樣,都是加一個標識欄位,可以是數值類型或時間類型。不過程序的處理不一樣:版本號的設計中程序會在同一張表中存下記錄修改的歷史;標識鎖的設計,只是為了給某一記錄加鎖,處理並發編輯問題。
個人不建議將修改的歷史記錄和業務數據混淆在一起存放於業務表中,因為程序讀取數據時要先對版本號欄位進行判斷、程序的修改動作在程序中實質也變成了插入動作,多了很多邏輯處理,況且一起存放容易使業務數據表的數據量因歷史版本記錄而膨脹。
歷史版本記錄可以單獨建歷史表存放,其實就是建立相同Schema的表(當然也可以添加更多的欄位用於記錄額外的歷史版本信息),該表只保留歷史版本的數據。這有點像一個歸檔邏輯,所有歷史版本我們認為都應該是不經常訪問的,可以扔到單獨的表,對於現有生效的版本,仍然保留在原表中,如果需要查詢歷史版本,那麼就從歷史表中查詢。使用歷史表記錄歷史版本,就是在程序對數據進行增刪改操作時,先在歷史表中留痕。
對於會診單這種核心的業務表,保存歷史修改記錄是很有必要的,但並非所有的表都一定要保存歷史記錄,對於其它一些非核心表,可根據實際情況斟酌是否建立對應歷史表。不過合理的設計應該是一套完整的體系,沒有重要不重要之分,只要存在的都是重要的,都要用嚴格統一的設計方案。為此或可以創建額外的歷史版資料庫,裡面單獨保存所有的歷史表。
這裡有必要再次提下前面系統日誌表設計部分的介紹,裡面說可以把每次的寫操作序列化成某種格式存儲在操作日誌表中,如果操作日誌的設計可以記錄的足夠詳細靈活——增加的話增加了什麼、刪除的話刪除了什麼、修改的話修改了哪些屬性、修改前後屬性值各是什麼,具體到某張錶的歷史記錄或可不用再單獨處理。
以往的資料庫設計中,多沒有考慮詳細的歷史記錄功能,後面應該注意,再仔細考慮下操作日誌(OperationLog)表的設計和存儲(優先考慮Hbase或OB),看看可否將其設計的足夠詳細靈活。
F.大數據可視化
在「設計規範」——「基本原則」——「連接查詢」部分提到存有大量可視化圖形展現的系統,對於這種系統如何提取通用部分,以便高效、嚴密、靈活而又統一的控制,尚未有好的解決方案。業務、約束在這樣的系統中不是核心;前台只有讀而沒有寫,對讀的要求很高,對寫卻不在意;只要統計分析的結果,而不要源數據。按理沒有源數據,自然沒有統計分析,但有時這是個大數據平台,這些源數據根本拿不到,它來自於整個市場、整個互聯網。但是統計分析的結果可以從網上搜到相關,跨過源數據而直接顯示。
對市場和行業進行大數據分析是非常困難的,不是說分析困難,而是拿到數據困難,單憑爬蟲團隊從網上爬取的那點亂七八糟的數據,用來分析根本不現實。互聯網之外,行業內部的數據又都掌握在政府或企業手裡,除非有非常親密的合作關係,這些數據也是拿不到的。而且手裡有大數據的機構或企業,往往自己就有能力進行數據分析,有大數據卻沒能力分析又找到我們做,是最理想而少見的情況,這就無需擔心數據源,項目也就是純粹的技術問題了。
做大數據分析平台,當然首先得有大數據,否則出來的系統就是無源之水、無本之木。問題是現在沒有,還想做數據分析可視化展現,該當如何?
首先是需求提出者那邊要立足於實際,不要憑空的去想要一個東西。沒有數據也不知道數據具體從何而來,卻妄想做大數據分析,這是一切的癥結所在,所接觸項目的痛點根源就在這裡。但另一方面我們可控制的又很少,有人願意付錢,無論痛不痛到你這裡都要做的,沒得選擇。
其次是產品經理那邊,在接到客戶的模糊需求後開始設計產品,先考慮的問題是實現,要設計這個功能數據源從哪裡來,可否實現?這些客戶那邊不考慮沒辦法,產品這邊如果也不考慮而天馬行空的設計,最後到開發那邊就全懵逼了。如果沒有源數據,那要展示的統計分析結果,互聯網上可否獲取明確的數據?這裡說考慮實現不是泛泛的,而是對於設計的系統中的每一處,所需的數據都要有明確的數據來源方可。
按常理,產品經理在設計前應該先過項目經理這邊,詳細溝通之後再著手設計,而且設計過程要和項目經理實時溝通。但當前所在公司的情況就是產品經理在拿到需求後直接設計,很容易脫離於業務,且其沒有能力考慮到後期具體的實現問題。如果是純粹的後台業務系統,交給產品經理設計問題會更多。等到完成設計再交付到項目經理這邊組織人員開發,有不合理的地方項目經理很難再強力左右,總不能讓他再重新去設計,只能默許當前的設計、提些細微處的修改意見後著手開發。再說,工作流程有問題,前期及設計過程中不和項目經理實時碰面,返回重新設計多少次還是會有問題。這樣設計和實現就脫節了,一個爛的系統在前期就已經註定。
最後到開發這邊,應該先確定系統涉及哪些業務、自己可以拿到哪些真實的源數據、哪些真實的數據處理析結果,對於有源數據的部分可以先理清其中的關係,對其進行關係化存儲,而對於沒有源數據只有數據處理分析結果的部分則要另當處理。
自己以往做的這種項目,都是先理清業務關係,然後結構化設計資料庫,如果最終只有數據處理分析結果而沒有源數據,則製造模擬源數據,以這些模擬的源數據計算出準確的分析結果為最終目標。在製造模擬源數據時非常不便,因為結構化的存儲、業務關係,表與表間互相干涉,影響著SQL查詢出的最終分析結果。
也就是在這時開始想,對於這種前台界面中只展示數據處理分析結果的項目,結構化的數據、關係型資料庫可能不是最好的選擇,用非關係型資料庫會不會更靈活一點?
只有分析結果或只有源數據都還好說,問題是大部分項目都介入兩者間,有部分有源數據,有部分只有分析結果沒有源數據,這些有的源數據和有的分析結果間可能還有些業務上的聯繫。比如酒企信息這些源數據本可以獲取一部分的,但各省份的所有酒企的具體信息卻是拿不到的,但同時各省份擁有的酒企數量排名這個統計分析結果卻是從網上可以搜到的。這TM就尷尬了~~~本來在關係型資料庫中有酒企和地區表,多對一,但為了得到這個分析結果,我要在酒企表中製造大量模擬數據,來平衡得到下面的分析結果。如果不這樣做,除非針對這種只有分析結果沒有源數據的部分單獨設計表存儲,但這樣關係型資料庫就全亂了,也發揮不了他應有的作用。
現在想到的一個辦法是針對這種只有分析結果沒有源數據的部分單獨存儲成XML數據,或者就是用非關係資料庫存儲,對那些有源數據而又涉及業務的部分還是用關係型資料庫存儲,在程序中對這兩種數據分別處理。以往沒有試過這種方法,後面可以嘗試下,是否會讓後期的開發及數據維護更簡單些?
自己後期這種糾結的根源,還是在前面的需求提出和產品設計中,如果這兩個步驟自己可以強力左右,定然不會讓後期開發出現這種進退不得的情況。過去一些項目,項目經理、產品經理、技術經理的角色都是一人獨攬,反而是效率最高、對項目最為有益的。個人是敏捷開發的極力倡導者,在項目調研、設計、開發、實施的整套流程中主張獨裁,極其厭惡帶龐大臃腫的團隊、極其厭惡使用低效的傳統開發模式。
G.其它注意事項
在建表時的欄位排列順序也要注意,雖然在使用相應的資料庫管理工具時,一般可以按名稱對錶欄位進行排序顯示,但建表時默認排序最好和欄位的重要程度有一定關聯,這樣開發人員在查看錶結構時會一目了然。而且表一旦建完,在一些資料庫中想要再修改其欄位默認順序就不容易了。一般是基本的、核心的欄位在前,次要的、擴展的欄位在後。這個默認的順序,約是你在建本張表時,腦海中先後浮現出的欄位的順序。
在資料庫表設計過程中,不建議提前加擴展欄位,當前想到有幾個欄位則就有幾個。加擴展欄位主要是為了方便後面開發過程中需要新增欄位的情況,但是,最後需要增加的欄位可能和你設定的欄位的類型不一樣,這樣擴展欄位還是沒用的,需要手動更改,再說欄位名稱也都要改,那還不如不加擴展,有需要時再添加呢。
設計完成後可以用工具添加些測試數據,不過工具添加的數據多不規則,這個根據情況而定吧。
9、梳理總結
「設計規範」幾乎把資料庫設計過程中的各個注意點都提到了,且詳細講述了各注意點涉及的知識面,並將自己的設計理念灌入其中。現把此部分的要點梳理如下:
a. 資料庫設計過程中使用PD工具。
b. 百張表內的資料庫由一人來設計。
c. DBA應該是在被需要時出現,而不應該被強制需要。
d. 開發過程中的規範和約定採用大一統的方式,嚴格限制脫離中央管控的腳本或代碼出現。
e. 設計原則是儘可能讓粒度小、容忍度高。
f. 負責資料庫設計工作的人應該是最懂項目、最懂業務需求、最有設計經驗的人
g. 把項目相應部分的決定權交到相應最懂的人手中,其他人不要干涉。
h. 這裡建議將MySQL資料庫編碼設置為utf8, SQLServer、Oralce可先採用默認設置,有需要則根據實際情況做相應變動。
i. 禁止使用存儲過程、觸發器、函數、視圖、事件等高級功能。
j. 資料庫設計儘可能的遵循關係資料庫範式。
k. 設計過程中要考慮儘可能減少後面程序編寫時查詢的複雜度
l. 每張表必須有只用來標識唯一的主鍵,主鍵為UUID,統一命名為id,統一為char(32)類型。
m. 現階段暫時不考慮邏輯刪除,仍舊使用物理刪除。
n. 對於中小型項目,資料庫設計過程中無需過多的考慮性能、安全的問題。
o. 不同的關係型資料庫在欄位類型的具體化上差異較多,但具體化的欄位類型再多,無外乎這幾種:字元、數字、日期、二進位。
p. 表中應該儘可能避免可為NULL的列,且儘可能顯示設置默認值,尤其是被索引的列。
q. 如果資料庫中某個欄位有默認值,在程序開發過程中,對應實體類的屬性應該設置同樣的初始化值才合理。
r. 禁止使用float、double類型,建議使用decimal替代。
s. float:浮點型,含位元組數為4,32bit,數值範圍為-3.4E38~3.4E38(7個有效位);double:雙精度實型,含位元組數為8,64bit數值範圍-1.7E308~1.7E308(15個有效位);decimal:數字型,128bit,不存在精度損失,常用於銀行帳目計算。(28個有效位)
t. decimal(a,b) ,a指定指定小數點左邊和右邊可以存儲的十進位數字的最大個數,b指定小數點右邊可以存儲的十進位數字的最大個數。最大精度38。
u. MySQL的decimal對應於Oracle的number,數據類型的精度:number(p,s) ,p: 1---38, s:-84---127。s代表的是小數位數,p代表的是總位數(整數位數和小數位數)。
v. 資料庫Oracle服務端的exp.exe工具遠程導出源資料庫,導出導入命令:
cd D:appscproduct12.1.0dbhome_1BIN
exp origin_username/origin_password@origin_tnsname file=C:UsersAdministratorDesktop20150508.dmp owner=origin_username
imp target_username/target_password@target_tnsname file=C:UsersAdministratorDesktop20150508.dmp ignore=y full=y statistics=none
w. 要求日期時間類型的欄位,儘可能精確到時分秒,用datetime類型。
x. 字典編碼欄位,之前在SQLServer中設計資料庫時統一使用char(2)類型,Oracle資料庫中統一使用number(2),在MySQL中統一使用tinyint(2)。
y. 統一命名為remark,欄位類型為varchar(200),最多100個中文字元。
z. 推薦統一使用int(10)做為所有表中的排序欄位類型。
aa. 對於表的注釋,要求簡單明了,先說明表中文名稱,句號分隔,然後跟功能說明,無需特別說明的,則只說明中文名稱即可。同時,為了最終生成代碼注釋的可讀性,不允許在說明中出現「表」字眼。
bb. 主鍵欄位,無須特別注釋,注釋就兩個字:主鍵。
cc. 外鍵欄位注釋:
` applicant_doctor_id` char(32) DEFAULT NULL COMMENT "申請醫生。外鍵,引用醫生表(doctor)的主鍵(id)。理論上可填多個,以「|」分隔"。
` applicant_doctor_name` char(32) DEFAULT NULL COMMENT "申請醫生。名稱,和醫生表(doctor)的醫生名稱欄位(name)對應。理論上可填多個,以「|」分隔"。
dd. 字典欄位注釋:
`status_code` varchar(5) DEFAULT NULL COMMENT "轉診單狀態。編碼,對應數據字典表(dictionary)中的編碼欄位(code)。目前先定義6個狀態:01 已提交,02 已取消,03 已審請,04已安排,05 已拒絕,06 已結束。03是向HIS系統執行審請,04是HIS系統已安排入院日期,下轉診時這兩個欄位沒有區別。"
`status_text` varchar(50) DEFAULT NULL COMMENT "轉診單狀態。文本,對應數據字典表(dictionary)中的字典項名稱欄位(name)。目前先定義6個狀態:01 已提交,02 已取消,03 已審請,04已安排,05 已拒絕,06 已結束。03是向HIS系統執行審請,04是HIS系統已安排入院日期,下轉診時這兩個欄位沒有區別。"
ee. 普通欄位注釋:
`bed_id` varchar(50) DEFAULT NULL COMMENT "入院床位號。HIS系統安排並反饋的入院床位號,如果是下轉診,此部分由醫生手動填寫"。
ff. 五種約束類型:唯一性和主鍵約束、外鍵約束、檢查約束、空值約束、默認值約束,對應五大關鍵詞,UNIQUE和Primary Key, Foreign Key, CHECK, NOT NULL, DEFAULT。本規範中要求,除檢查約束外,其餘四種都要在資料庫中加強約束,同時,程序也會根據資料庫中約束的設置做相應處理。
gg. 當數據量達到一定程度,僅通過程序優化對性能提升有限時,可以考慮同時對資料庫進行調校,這時會優先考慮建立索引。提到的幾個注意點:索引外鍵、索引經常GROUP BY、ORDER BY的欄位、不要索引blob/text欄位、不要索引小型表、索引多了影響插入刪除速度、在條件表達式中經常用到的不同值較多的列上建立檢索。
hh. 對於並發編輯目前唯一可行的方法,還是在資料庫中加入一個鎖標識欄位。
ii. 優化操作日誌(OperationLog)表的設計和存儲(優先考慮Hbase或OB),來代替單獨歷史表的設計。
jj. 大數據分析可視化平台,針對只有分析結果沒有源數據的部分可嘗試單獨存儲成XML數據,或者就是用非關係資料庫存儲。
kk. 在建表時的欄位默認排列順序也要注意。
三 命名規範
1、引言
在前面提到過,資料庫設計過程中表、欄位等的命名規範也算是設計規範的一部分,但因為牽涉的內容較多,所以將其單獨列出。不過設計規範更多的是為了確保資料庫設計的合理性、為了項目最終的協調穩定性,而命名規範則更多的是為了確保設計的正式和統一。公正的講,資料庫中表欄位等等以什麼樣的方式命名、取具體什麼名字,並不會直接影響到項目的穩定性,不是說叫黑貓項目就是正常的,叫白貓就運行異常了。
制定規範的直接目的是約束設計行為,最終目的是確保設計的合理統一。規範雖然是有豐富項目經驗的人制定的,但維護的卻不是某個人的意志,而是項目的意志,因為遵守此規範對項目是好的有利的,此規範才有意義。所以規範是為了項目利益最大化而在團隊人員中形成的一種約定(貌似約定的英文單詞Convention本身就有規範的意思),所有參與設計的人員都要遵守此約定,所有參與開發的人員都會依此約定解讀設計。我們約定,所有的主鍵統一命名為id,結果有設計人員違反約定將一個非主鍵欄位命名為id,約定被打破,共識也就被打破,設計人員之間、開發人員與設計人員之間的溝通就出現了隔閡。
設計規範更多的是為了合理,命名規範更多的是為了統一,團隊協作中,統一在某種程度上比局部設計開發的好壞更重要。違反了約定,局部設計開發的再好,反而可能影響到項目整體的穩定協調。
約定優先於配置(Convention Over Configuration)。
在「設計規範」中提到過一些命名規範,也詳細講述了表、欄位的類型、注釋等屬性的設置,為什麼要求主鍵統一命名為id、統一為char(32)類型,為什麼要求浮點型數值統一為decimal類型?我們希望團隊中所有人看到設計成果,一眼就可以明白這個欄位是做什麼的、代表的含義是什麼,可以但不止於見名知意。再者,當前的開發模式,前後端代碼及資料庫文檔、程序文檔、介面文檔等等大都是由工具生成,而其最底層的依據就是資料庫,表、欄位的命名注釋同時會影響到工具生成的文檔、代碼中的類屬性方法甚至是前台頁面的命名注釋,資料庫設計命名的規範關係到整個項目的規範。
命名規範會分四個大模塊來介紹:基本規範、名大小寫、具體規範、特別說明,各大模塊下面有的會有子模塊特別說明。
2、基本規範
A.可用字元
資料庫、表、欄位等所有名稱的可用字元範圍為:A-Z,a-z, 0-9 和_下劃線,除此外不允許使用其它字元作為名稱。資料庫及表名均不允許出現數字,欄位名除非特殊情況不允許出現數字。
在前面介紹關係範式時曾提到過一個破壞範式的例子:平時的多圖片上傳功能,可能只設計一個欄位存儲圖片名稱,這樣欄位值中就會包含多個圖片的名稱,裡面用|或其它符號分隔。像這種情況,其實也可以設計成三五個欄位image_name1、image_name2、image_name3……分別存儲,然後限制可上傳圖片個數,這就是欄位名中可出現數字的特殊情況——雖然也不建議這樣設計或取名。
B.命名方式
資料庫、表、欄位等所有名稱使用英文單詞或英文短語或相應縮寫,禁止使用漢語拼音,且均使用單數名,例如:對存儲客人信息的表命名為customer而不是customers。名稱應該清晰明了,能夠準確表達事物的含義,遵循見名知意的原則。
Oracle表、欄位等名稱統一使用大寫,單詞間用_下劃線分隔;SQLServer資料庫、表等名稱採用Pascal命名法,欄位名稱採用Camel命名法,大小寫字母混排;MySQL資料庫、表、欄位等名稱統一使用小寫,單詞間用_下劃線分隔。至於為何這樣規定,下一個模塊會有詳細介紹。
Oracle相對特殊,通常的操作順序是,先創建資料庫實例,然後創建表空間,然後創建用戶並設定此用戶的默認表空間,最後在此用戶下建表。多數情況下我們都是只建一個實例,然後在此實例下建不同的表空間、不同的用戶,根據不同的用戶來區分不同的庫。關於實例、表空間及用戶的命名方式並無限制,可以採用大小寫混排,也可以只用大寫或小寫,但對於表和欄位,我們要求統一為大寫。
我們要求統一為大寫或小寫的名稱,兩個單詞間用_下劃線分隔,SQLServer使用Pascal或Camel方式命名。這些不僅僅是為了資料庫設計的可讀性,也是為了最終生成代碼的可讀性。這裡簡單介紹下編程中常用的三種類、變數、函數等的命名方式:
a. 匈牙利命名法。由微軟的一位匈牙利程序員Charles Simonyi 提出,相對複雜,首字母小寫,基本原則是:變數名=屬性+類型+對象描述,其中每一對象的名稱都要求有明確含義,可以取對象名字全稱或名字的一部分。匈牙利命名法主要在C或C++這種面向過程的程序語言中使用,如果用在Java、C#這種面向過程的語言中就很彆扭。
不過自己在寫Web前端頁面或腳本時,借用了這種命名方式,form表單中涉及的常用HTML標籤不外乎如下幾種:label、text、button、submit、password、textarea、radio、checkbox、select等,那我在給表單元素命名或者說是給id或name賦值時,就會將元素類型做前綴,例如用戶名輸入框為textName、性別單選按鈕名為radioGender。這樣做的好處是我在編寫腳本時,根據id或name名稱一眼就可以看出這個表單元素是什麼類型。在修改頁面中初始化表單數據時我可以直接遍歷表單元素、根據元素名稱判斷出元素的類型進而採用適當的賦值動作,而不用逐個選擇元素去賦值。
在http://ASP.NET編程中,如果使用微軟的伺服器控制項,在命名時我會用控制項類型做名稱後綴,例如Name_TextBox、Gender_RadioButtonList等。之所以不再將類型做前綴,一來是VisualStudio本身默認的伺服器控制項命名方式即時如此,控制項類型做後綴;二來是因為伺服器控制項的類型名稱太長,而自己又不願用縮寫,因為沒必要,VisualStudio的提示功能強大,後綴的長度不會影響到編程速度。
b. Camel命名法。即駱駝式命名法,首字母小寫,採用該命名法的名稱看起來就像駱駝的駝峰一樣高低起伏。Camel命名法有兩種形式:
第一種是混合使用大小寫字母,例如englishName、fartherCode。在Java中,屬性名和方法名一般都採用這種命名方式,在C#中只有屬性名採用這種命名方式,我們在前面也規定,SQLServer中欄位的命名也採用這種方式。
第二種是單詞之間加下劃線,例如english_name、farther_code。我們在前面規定,Oracel和MySQL表、欄位的命名都採用這種方式,不過我們要求Oracle全部使用大寫字母,MySQL全部使用小寫字母。再者,無論是在Java還是C#,甚至是在JavaScript中,所有的常量,都使用這種命名方式,但和Oracle表欄位的命名方式一樣要全部使用大寫字母,比如前面的設計規範中介紹數據字典表時,字典類型、字典項的編碼和文本信息需要即時獲取,以往的習慣在程序中建立一個常量類,所有用到的字典數據在裡面用常量標明,這時常量的命名方式即是如此。
c. Pascal命名法。即帕斯卡命名法,與Camel命名法類似,不過是首字母大寫。在C#中,類名和方法名一般採用這種命名方式,在Java中類名一般採用這種方式。在前面也規定,SQLServer中資料庫、表的命名也採用這種方式。
除資料庫的設計外,不同編程語言、前端HTML標籤、JavaScript腳本、樣式等等部分都會涉及命名的問題,如果細細整理,項目開發中每個子模塊的命名規範都夠再出一份長篇文檔的。這裡只簡單介紹下三種常用的命名方式,其它部分的命名方式只是一提,重點還是在資料庫的命名規範上。前面說過多次,程序、文檔甚至前端頁面有大部分通過工具自動生成,只有資料庫嚴格按要求來命名,才能根據不同的編程語言編寫不同的代碼模板,統一控制生成部分各處的命名方式。比如,我們要求在MySQL資料庫中,表名都使用小寫,單詞間用下劃線分隔,交易記錄表名稱為trade_log,那可以設定生成規則,對應生成的實體類名就是TradeLog,對應生成的Dal層就是TradeLogDal,對應的Service名就是TradeLogService,等等。可如果設計沒有規範、不統一,那文檔生成規則、代碼生成規則、程序編寫規則等等也就無法統一制定了。
C.長度限制
關於各種資料庫管理系統(DBMS,Database Management System)本身對錶、欄位等名稱的長度限制如下:
以上是從網路整理而來,Oracle、SQLServer及MySQL的限制長度親自測試過。但也有說因為資料庫和表的名字可能對應於目錄和文件名,故而伺服器運行的操作系統可能強加額外的限制。不過除了Oracle的限制長度過短外,其它的一般不會被超出。我們希望名稱儘可能詳細準確的表達事物含義,但如果過於冗長,就會給操作及後面的程序編寫帶來諸多不便。
D.單詞縮寫
自己以往設計資料庫時,經常頭疼於表、欄位的命名,一來找不到好的單詞去表述,二來有時可能涉及多個單詞,導致名稱過長。欄位名過長帶來的不便有限,最終影響的不過是程序實體類中的一個屬性,可如果表名也過長,就比較麻煩了,生成的程序各層間針對此表的類名、變數名都可能受到影響,給後期的編寫帶來很大不便。使用單詞縮寫又拿不準,找不到合適的縮寫方式。這裡建議當表名超過15個字元、欄位名超過20個字元時就應該嘗試用單詞縮寫重新命名,如果名稱長度在此之內,原則上講則儘可能不用縮寫以使表述具體清晰,表、欄位最終的名稱長度要嚴格控制在30個字元以內。關於單詞縮寫規則如下:
a. 如果可以在字典里找到一個詞的縮寫,就用這個做為縮寫,比如:Monday=Mon、December=Dec ,可在此網站下查找到一些英文單詞的縮寫:http://shortof.com/;
b. 可以刪除單詞母音(詞首字母除外)和每個單詞的重複字母來縮寫一個單詞。比如:Current = Crnt、Address = Adr、Error = Err、Average = Avg;
c. 對於主從表,如果主表名稱沒有縮寫而從表的名稱需要縮寫,則從表名稱從第二個單詞開始縮寫,第一個名詞儘可能和主表保持一致。比如企業基本信息表名稱為enterprise,則企業訴訟表enterprise_litigation可簡寫為enterprise_ltg,企業證書表enterprise_certificate可簡寫為enterprise_crt。最終的資料庫表及由資料庫表生成的程序在集成開發環境(IDE,Integrated Development Environment )中是按名稱排列的,這樣做是為了讓相似功能的表、類文件排列在一起,方便開發者操作。
更詳細的單詞縮寫規則介紹可以參考文檔末尾的參考文獻。
3、名大小寫
理想情況下所有關係型資料庫對於表名、欄位名、欄位內容等大小寫的處理會有個大一統的方式,比如要求所有都是大小寫敏感的,可實際的情況卻是,不同的資料庫及同一資料庫在不同的操作系統下對大小寫的處理都是不同的。以往筆記中記錄的第一次遇到資料庫處理大小寫的問題是,做的一個登錄頁功能,測試人員發現輸入用戶名MengXianzhi或mengxianzhi均可以正常登錄,但資料庫用戶表裡只有一條用戶名為MengXianzhi的記錄,當時用的是SQLServer資料庫。
A.編碼和字元序的介紹
在前面介紹資料庫編碼時曾經提到,如果使用MySQL Workbench創建新的資料庫,會要求選擇Collation。Collation的字面意思是字元序,用於指定數據集如何排序、及字元串間的比對規則。可字元本來是不分大小的,這樣對字元的&>、=、&< 操作就需要有個字元序的規則。Collation做的就是這個事情,你可以對錶進行字元序的設置,也可以單獨對某個欄位進行字元序的設置,優先順序從高到底可分為四種:伺服器層、資料庫層、表層、欄位層,真正決定性因素是在欄位層,如果沒有指定則默認從上一層繼承過來:欄位層繼承表層,表繼承資料庫層,資料庫層繼承伺服器層,伺服器層則需要設置,如果不設置默認為latin1_general_ci。
平時我們說的設置MySQL編碼為gbk、gb2312、utf8或lantin等指的是字元編碼,也就是Character Set。當表的Character Set 是lantin1時,若欄位類型為nvarchar,則欄位的字符集自動變為utf8。
可見資料庫、表、欄位的Character Set可逐級覆蓋,這有點像上面說的四種字元序設置方式間的優先順序關係。本規範中建議資料庫統一設置編碼為utf8,不僅僅是為了應付資料庫間導入導出過程中、因編碼格式不統一而導致的惱人的亂碼問題,也是因為utf8是一種萬國碼(Unicode)。軟體的國際化是大趨勢 , 而Unicode 是國際化最佳的選擇。在MySQL中有兩個支持 Unicode的Character Set:第一個是UCS2,使用 16 bits 來表示一個 Unicode字元;第二個是utf8,使用 1~3 bytes 來表示一個 Unicode字元。
那字元編碼(Character Set)和字元序(Collation)之間的關係是什麼呢?
每個Character Set 會對應一定數量的 Collation,在MySQL命令窗口中輸入Show Collation;命令可以查看到所有字元序及其所屬的字元編碼列表:
同一個Character Set的不同 Collation 的區別在於排序、字符集對比的準確度以及性能,這裡的準確度是指相同兩個字元在不同國家語言中的排序規則可能是不同的 ,性能是指排序以及比對速度。例如:utf8_general_ci 在排序的準確度上要遜於 utf8_unicode_ci, 當然,對於英語用戶應該沒有什麼區別,但性能上要略優於 utf8_unicode_ci,例如前者沒有對德語中? = ss的支持。而 utf8_danish_ci 相比 utf8_unicode_ci 增加了對丹麥語的特殊排序支持。
Collation名字的規則可以歸納為兩類:&
在上面的截圖中也可以看到,gb2312編碼默認的Collation是gb2312_chinese_ci、gbk編碼默認的Collation是gbk_chinese_ci、utf8編碼默認的Collation是utf8_general_ci。按本文檔中的規範,建議所有編碼統一設置為utf8,如果不單獨設置Collation,則按默認的utf8_general_ci,欄位值是不區分大小寫的。
那在字元序為CI的情況下,如何在執行SQL查詢時區分欄位值的大小寫呢?假設用戶表user中有兩個用戶:MengXianzhi和mengxianzhi,當我們執行如下查詢時會得到兩條記錄:
select * from user where user_name = "MengXianzhi";
如果要區分大小寫,有下面兩種方式可以精確查詢:
select * from user where binary user_name = "MengXianzhi";
select * from user where user_name = binary "MengXianzhi";
推薦使用第二種查詢方式,這樣可以保證當前欄位的索引依然有效,而第一種會使索引失效。其實個人更傾向於建議統一設置資料庫默認的Collation為utf8_bin,也就是對大小寫敏感。程序中針對資料庫欄位內容的比對查詢處處都是,英文內容存儲也處處都有,如果所有相關查詢語句都加binary關鍵字,太過麻煩,不如在資料庫中統一設置,這樣也不會出現在本章節開頭所描述的問題了。
如果不想在資料庫中統一設置,也可以只針對錶、欄位單獨設置,但非常不建議如此,因為這會導致局部配置和全局配置相悖 。一直堅持規範、約定、配置等儘可能採用大一統的方式,除非不得以。開放局部配置會導致配置的多樣性,不利於統一管理維護,不過下面還是會簡單介紹下局部配置的方法。
B.編碼和字元序的設置
這部分會分別對比MySQL、SQLServer、Oracle三種關係型資料庫的字元編碼和字元序配置,先從MySQL開始。
在MySQL中,自己沒有找到從伺服器層面直接配置Collation的方法,但是資料庫、表及具體欄位設置Collation的方法都有。再就是在PD中未能找到全局設置Collation的方法,只找到了具體到某一欄位設置的地方。截圖如下,最後一張是PD中對某一具體欄位進行配置的方法:
如果要直接更改某一個資料庫的Character Set或Collation可以在MySQL Workbench中做如下設置:
其實Character Set和Collation本就是一體的,所以其實都是在這一個地方設置,兩種選項對應的SQL語句就是:
ALTER SCHEMA `bsctelmed` DEFAULT CHARACTER SET utf8 ;
ALTER SCHEMA `bsctelmed` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_bin ;
在SQLServer 2008中,只找到了從資料庫層及具體欄位層面直接配置Collation的方法,但是從表層面中的配置卻沒有。和MySQL一樣,在PD中未能找到全局設置Collation的方法,只找到了具體到某一欄位設置的地方。截圖如下,最後一張是PD中對某一具體欄位進行配置的方法:
如果想查看SQLServer的版本、字元序等相關信息也可以用如下SQL語句:
SELECT
SERVERPROPERTY(N"Edition") AS Edition,
SERVERPROPERTY(N"ServerName") AS ServerName,
SERVERPROPERTY("ProductVersion ") AS ProductVersion,
SERVERPROPERTY("ProductLevel") AS ProductLevel,
SERVERPROPERTY("ResourceVersion") AS ResourceVersion,
SERVERPROPERTY("ResourceLastUpdateDateTime") AS ResourceLastUpdateDateTime,
SERVERPROPERTY("Collation") AS Collation;
直接在SQL Server Management Studio 圖形化界面中更改SQLServer的字符集可能會出現問題:
這時可通過如下更改語句進行更改:
ALTER DATABASE HealthManagement COLLATE Chinese_PRC_CI_AS
在Oracle中貌似沒有Collation的概念,又或者是換了另外一個概念來進行類似的設置?在PD中具體到表欄位的Oracle選項卡中也沒有字符集相關的配置:
Oracle中默認是嚴格區分欄位內容大小的,如果不想對大小寫進行區分可以使用Lower()或Upper()函數來達到目的,也可以使用NLSSORT()函數,覺得這個函數就和MySQL中的Collation設置所達到的效果相似。如下三個SQL語句所達到的效果是一樣的:
select * from user where user_name = "MengXianzhi" COLLATE Latin1_General_CI_AI;
select * from user where Upper(user_name) = Upper("MengXianzhi");
select * from user where NLSSORT(user_name,"NLS_SORT = Latin_CI") = NLSSORT("MengXianzhi","NLS_SORT = Latin_CI");
但是不清楚上面第三種寫法,如果不是精確查詢,而是模糊查詢,用like關鍵字,查詢語句如何下,嘗試下面的寫法,好像不起作用:
select * from user where NLSSORT(user_name,"NLS_SORT = Latin_CI") like NLSSORT("Meng","NLS_SORT = Latin_CI")+"%";
Oracle9i之前,中文是按照二進位編碼進行排序的,而在Oracle9i中新增了按照拼音、部首、筆畫排序功能,使用方法如下:
按漢字拼音排序:
SELECT * FROM USER ORDER BY NLSSORT(user_name ,"NLS_SORT = SCHINESE_PINYIN_M")
按漢字筆劃排序:
SELECT * FROM USER ORDER BY NLSSORT(user_name ,"NLS_SORT = SCHINESE_STROKE_M")
按漢字部首排序:
SELECT * FROM USER ORDER BY NLSSORT(user_name ,"NLS_SORT = SCHINESE_RADICAL_M")
注意,我雖然嚴格測試過MySQL、SQLServer和Oracle三種不同關係型資料庫針對CharacterSet和Collation設置的區別,但對於同一資料庫的不同版本間的區別卻未深究。各種關係型資料庫總是在不停升級,某些升級可能會導致新舊版本間差異巨大 ,而本文檔中所述細節又甚多,所以具體到實際情況,某些地方出現不同也很正常。
C.由此引出的亂碼問題
Character Set和Collation並不僅僅影響到資料庫存儲內容的大小寫敏感問題,還會影響到資料庫操作中常見的亂碼問題。這裡既然提到了,所以簡單講下。
以往負責的項目較為雜亂,所以對各種常見關係型資料庫多有接觸,就自己的經驗,亂碼問題出現最多的是MySQL資料庫,尤其是早期版本的MySQL,其次是Oracle,SQLServer當然也有,但相對少。亂碼問題可以分為以下幾種:
a. 不同類型的關係型資料庫間、數據互相導入導出,導致的中文數據亂碼。
比如將MySQL中的數據導入到SQLServer,將SQLServer中的數據導入到Oracle。這種情況其實相對少見,因為一般數據操作都是在同一類型的資料庫間進行。遇到這種情況時,數據間的導入導出一般都有中間過程,比如先從源資料庫中將數據導出成CSV文件,然後再將CSV文件導入到目標庫。又或者是,藉助目標資料庫的管理工具,直接連接源資料庫進行導入。也有將源資料庫中的數據導出成SQL文件,然後對SQL文件進行一定更改後在目標資料庫中執行的。
b. 類型相同、版本不同的關係型數據間的數據導入導出,導致的中文數據亂碼。
c. 類型相同、版本相同的關係型數據間的數據導入導出,導致的中文數據亂碼。
d. 針對Oracle,客戶端版本和服務端版本不同所致。客戶端的版本比較新、而服務端比較舊,或者是客戶端為32位的而服務端是64位等箸。
e. 主要也是針對Oracle,客戶端和客戶端所在操作系統不協調、服務端和服務端所在操作系統不協調。比如操作系統為32位,但下載的客戶端卻是64位的。
f. 針對程序,官方管理工具操作資料庫查詢沒有問題,但是程序訪問資料庫查詢出的中文卻是亂碼。
中文亂碼問題在各種資料庫的操作中、在種程序語言各種項目的開發中時常出現,針對以上種種我們建議:
a. 安裝及操作資料庫時,編碼相關的默認設置,除非有把握,否則不要隨意更改;
b. 項目開發環境、測試環境、模擬環境、真實環境、線上環境的操作系統及資料庫等儘可能統一版本統一配置,選擇和操作系統相匹配的資料庫版本;
c. 針對Oracle資料庫,客戶端和服務端儘可能統一版本,儘可能選擇和操作系統相匹配的客戶端及服務端版本;
d. 在資料庫日常操作過程中均使用官方的管理工具,或直接在命令行中操作;SQLServer不用說,MySQL我們建議使用MySQL Workbench,Oracle我們建議使用SQL Developer;
e. 如果現實情況不方便或不允許達到以上要求,或者雖然按以上要求操作配置資料庫後依舊出現亂碼問題,那就根據實際情況網路搜索尋求相應解決方案。
遇到具體問題時刻記得Google是第一位的,僅這一項就可以幫我們解決99%的問題。我們的分析討論建議更多的是為了全面了解問題本身,但遇到具體問題如何解決,仍舊要靠自己思考、靠Google的智能搜索。下面的截圖來自於以往筆記,和某一亂碼問題的交鋒:
D.表名欄位名等大小寫
上面講字元序的大小寫敏感,針對的都是資料庫表欄位值或者說是欄位對容,而對於資料庫名、表名、欄位名、變數名、執行目錄名等(在執行SQL查詢時)的大小寫敏感呢?
在Linux下MySQL的資料庫名、執行目錄名、 表名、表的別名、變數名默認是嚴格區分大小寫的,資料庫名大小寫敏感不可改,執行目錄名大小寫敏感可參數調配(lower_case_file_system),表名大小寫敏感也可參數(lower_case_table_names)調配,但不確定這個參數是否影響表別名及變數名的大小寫敏感。列名與列的別名在所有的情況下均是忽略大小寫的,也不清楚可否參數調配。
MySQL在Windows下資料庫名、執行目錄名、表名、表的別名、變數名、列名、列別名等默認都不區分大小寫。
用root登錄伺服器修改 /etc/my.cnf配置文件,在[mysqld]節點下,加入一行: lower_case_table_names=1 可以另其不再區分表名大小寫。而在 Windows系統下, lower_case_table_names參數預設設置即為1,即不區分表名大小寫。
在SQLServer中自己測試的結果是,資料庫名、用戶名、表名、表別名、列名、列別名默認在執行SQL查詢時均不區分大小寫。SQLServer版本為2008 R2。
在Oracle中自己測試的結果是,實例名、表空間名、用戶名、表名、表別名、列名、列別名默認均不區分大小寫。Oracle為Linux版本,11.2.0.4。
這樣整理之後,如下表格:
內容中的是否表示默認情況下是否大小寫敏感,括弧中的(可)表示可參數調配。空白部分表示不確定,或者沒有這一項。
SQLServer和Oracle 、MySQL(Windows系統下)雖然同樣默認對錶名、欄位名等不區分大小寫,但不同的是Oracle及MySQL處理的更嚴謹。通過SQL*Plus、PL/SQL Developer或SQL Devloper在Oracle中建表,默認會自動將表名轉換成大寫後再寫入資料庫。 在Windows系統中,默認情況下,建表時MySQL會強制要求所有表名和列名均為小寫。SQLServer雖然在執行SQL查詢時不區分表名、列名大小寫,但在命名及在可視化管理工具中顯示時卻又區分大小寫。也有另外一種可能,目前我測試用的Oracle及MySQL版本比較新,則SQLServer則較舊,最新版的SQLServer或許已經沒有這種問題。
前面說通過SQL*Plus、PL/SQL Developer或SQL Devloper在Oracle中建表,默認會自動將表名轉換成大寫後再寫入資料庫。但實際上Oracle是可以支持大小寫混排的命名方式的,但前提是要在表名外面加雙引號。
仔細查看過,使用PD設計針對Oracle的PDM,如果你的表名全部大寫,那PD在生成SQL建表語句時不會在表名外面加雙引號,可如果你的表名是大小寫混排的,那PD在生成SQL建表語句時會自動在表名外加雙引號,保留這種大小寫混排的命名方式。其實不光是創建表,在Oracle中創建觸發器、序列時也是如此,名字不加引號就不會區分大小寫,加上引號就會區分。
不建議在Oracle中使用大小寫混排的命名方式,原因有很多:
a. 當你使用Oracle SQL Developer工具查看錶時,點選「詳細資料」選項卡,可能會報錯:執行請求的操作時遇到錯誤,ORA-00904:"STATUS":invalid identifier。網上搜到ORA-00904錯誤原因和Oracle建表時表名大小寫有關,但不清楚和Oracle版本有沒有關係。
b. 如果表列名都區分大小寫,那在建立查詢時表名和列名都應該帶有雙引號,會給後面程序的編寫帶來麻煩。如果使用Hibernate框架,那其生成的查詢是不會帶有雙引號的,會出現無法找到表或視圖的錯誤。
c. 使用PL/SQL Developer工具可視化地進行表的刪除等操作時,後台採用的是不帶雙引號的表名,也會出現無法找到表或視圖的錯誤。這時只能採用類似 drop table "tableName" 的語法,在SQL*Plus或PL/SQL Developer手工刪除或修改表。
我們在基本規範中為什麼要求MySQL的資料庫名、表名、列名等統一為小寫,Oracle中的表名、欄位名等統一為大寫,正是基於以上原因。我們希望藉此規定,將命名大小寫規則統一,儘可能的讓資料庫設計不要在名稱大小寫這個問題上多出不必要的麻煩。
這裡順便一提,在PD中可以將PDM中的表名或列名統一轉換成大寫或小寫,菜單Tools——Model Options——Naming——Convertion——Table或Column中進行設置。
E.針對大小寫合理建議
個人認為Oracle資料庫對錶名、欄位名、欄位內容等大小寫敏感的默認處理是最合適的,在執行SQL查詢時不區分表名、表別名、列名、列別名的大小寫,但嚴格區分欄位內容的大小寫。也正因此,我們在基本規範中建議在Oracle資料庫的設計過程中表、欄位等的名稱統一使用大寫,單詞間用_下劃線分隔。
我們在基本規範中建議,MySQL資料庫、表、欄位等名稱統一使用小寫,單詞間用_下劃線分隔。同時,我們建議在MySQL資料庫中將Character Set設置為utf8、將Collation設置為utf8_bin,並在資料庫配置文件中設置lower_case_table_names=1,當然,Windows系統中默認就是此種設置,無需再做更改。
我們建議在SQLServer中將排序規則設置為Chinese_PRC_CS_AS,其默認為Chinese_PRC_CI_AS,因為SQLServer資料庫不用考慮部署在不同系統的問題,所以不建議更改除此外的其它編碼、字元序相關的默認設置。我們上面也說過SQLServer雖然在執行SQL查詢時不區分表名、列名大小寫,但在命名及在可視化管理工具中顯示時卻又區分大小寫,為了查看方便所以我們在「基本規範」中要求SQLServer用Pascal的命名方式。
在「名大小寫」這個章節,更多的不是制定規範,而是在講解前面的「數據編碼」、「基本規範」等模塊中列出的一些規範制定的原因。在這裡詳細講解了MySQL、SQLServer、Oracle三種資料庫的編碼、字元序相關的配置說明以及表名、欄位名、欄位內容等大小寫敏感的控制處理等。
4、具體規範
A.關於資料庫的命名
對於資料庫的命名不做特別要求,簡單明了即可,這裡主要注意在一個大環境中相似項目的資料庫命名,最好有明顯區分。
這裡順帶一提,互聯網公司的資料庫一般分為五個環境:
a. 開發環境(Development Environment)。開發可讀寫,開發人員可以修改表結構,可以隨意修改其中的數據;但是需要保證不影響其他開發同事。
b. 測試環境(Test Environment)。開發可讀寫,部署的測試系統訪問此庫,代測試人員使用。
c. 模擬環境(Simulation Environment )。 開發可讀寫,通過web平台,發起上線請求時,會先在這個環境上進行預執行,這個環境也可供部署上線演練或壓力測試使用。
d. 線上從庫(Real Environment)。 只讀,會實時從線上資料庫同步,不允許修改數據,不允許修改表結構。供線上問題查找,數據查詢等使用。
e. 線上環境(Online Environment)。開發人員不允許直接在線上環境進行資料庫操作,如果需要操作必須找資料庫主負責人,並做相應記錄。
在這些環境中,一定要做到許可權劃分明確,讀寫帳號分離,並且有辨識度,能區分具體業務。例如用戶名w_wap、r_wap 分別表示對wap資料庫進行讀、寫的帳號。
做企業內部應用系統,要求不是特別嚴格的話,沒有模擬環境和線上從庫。而且通常情況下,線上環境的庫在客戶那邊,開發測試的環境在公司這邊,兩邊還不能互通,有時不得不駐場開發直接連接線上環境。但是對於線上環境的直接操作是非常危險的,且容易導致線上環境和開發測試環境表結構的不同步,這個一定注意。客戶那邊應該用許可權嚴格限制對生產環境訪問的人員,開發人員自己這邊要時刻做好數據備份工作,並提前準備好數據出現意外更改或丟失情況的應對措施。同時,在現場開發,針對線上環境的更改要實施同步到公司的開發環境中。線上線下的所有更改,都要經過資料庫主設計師的審核同意。
我們建議,如果可以控制的話,則在不同的資料庫環境中統一表空間名、資料庫名等,甚至是資料庫訪問的賬號名、許可權也可以統一,這樣在部署項目時,配置文件則無需再做過多更改,不同資料庫環境間有表結構或數據的移植時也可避免出現不必要的問題。在對這些環境的資料庫進行備份時,建議在備份文件名中加上前綴和備份時間,以防混淆,比如備份開發環境的資料庫可命名為:DevelopmentEnvironment201703271149。這些都是非常細節的地方,有點吹毛求疵,不做強制要求。
B.資料庫功能塊概述
在前面「設計規範」——「基本原則」——「高級功能」中提到過,現有的開發模式,資料庫只用來做數據存儲。一直堅持業務相關的部分都由程序處理,不到不得以的情況下不要在資料庫中建存儲過程、觸發器、函數、序列甚至是視圖等,儘管如此,這裡還是會簡單介紹下這些高級功能使用時的命名方式。下面的表格列出了資料庫所涵蓋常見功能元素的英文名稱及縮寫:
有建議,除表和表欄位外,其它功能塊在命名時均要加英文縮寫前綴。但就個人意見,除視圖外,其它部分加不加前綴不太重要,視圖加前綴是為了在執行查詢時和表區分開,而存儲過程、函數、約束等,我們一眼即可看出它是什麼,更何況在可視化管理工具中,這些功能塊本來就是各自獨立展示的。所以本規範中不強制要求在這些功能上加前綴,但如果要統一加的話,建議使用上圖表格中的英文縮寫。
C.關於數據表的命名
關於表的命名,TB這種前綴是毫無意義的,本來就是一個表,為什麼還要說明?這也是我上面不建議在其它功能塊中加前綴的原因。如果表格數量較少,後期項目擴展升級的可能性不大,也沒有必要加其它前綴。但有時規模相對龐大、業務邏輯相對複雜的項目,表格數量多到一定程度,在可視化管理工具中查閱瀏覽不太方便,這時,根據業務或功能對表格進行分類,加前綴也就有必要了。個人感覺是50張表內的資料庫,加前綴意義不大,超過100張,則很有必要加前綴。而且我們要求,為了不給後期代碼生成造成非必要麻煩,如果要給表加前綴,則所有表均要有前綴,不要出現有些表有、有些沒有的情況。
表前綴主要是為了區分不同功能的表,而非解釋表的功能,表的功能由表名來解釋。前面要求表名的長度要控制在30個字元以內,在此前提下,為了儘可能不影響表的命名,表前綴應該越短越好。我們建議表前綴控制在兩個以內。具體表前綴添加規則建議如下,括弧內的單個大寫字母表示要添加的前綴。這裡以Oracle資料庫為例,具體表名、前綴的大小寫根據實際資料庫參照「命名規範」——「名大小寫」章節的說明:
a. 系統表(S_):System,系統配置相關的基本信息表。系統用戶表(S_USER)、系統角色表(S_ROLE)、系統菜單(S_LINK_MENU)、操作日誌(S_OPERATION_LOG)、登錄日誌(S_LOGIN_LOG)、系統字典(S_DICTIONARY)、系統字典類型(S_DICTIONARY_TYPE)等。
b. 字典表(D_):Dictionary,非系統字典外的字典表。在「設計規範」——「相關注釋」——「字典欄位」中提到過字典表的定義,除了資料庫中的通用字典表,還有一些常見表,比如地區表(D_REGION)、ICD編碼(D_ICD)等,也是一種字典表,這裡的D_前綴即加在這類字典表名前面。
c. 中間表(R_):Relationship,多對多關係中間表。具體命名方式建議為:R_主表名_從表名,在多對多關係中其實不分主從表,這裡我們規定核心表為主表,另外一個為從表。比如用戶角色關係中,用戶表(S_USER)為主、角色(S_ROLE)表為從,那中間表就命名為R_USER_ROLE。當中間表名超長時,則根據實際情況縮寫主從表名,建議優先縮寫從表表名。
d. 業務表(B_):Business,核心業務涉及的基本信息表。這裡的業務是非系統配置業務相關的,比如登錄、註冊、許可權這些業務涉及的表都是和系統配置相關的,前綴應該是S_,而非B_。比如在線商城的項目中訂單業務涉及的表即是核心業務表,會診系統中會診單業務涉及的表即是核心業務表,如果項目龐大,涉及業務較多,可以在B後面繼續加單字母區分不同的業務,BA_、BB_、BC_……,沒必要非得和某個英文對應,只是個代號,和項目組的人員說明即可。
表名前綴的說明如上,已經足夠明確,除此外還應該避免無謂的表格後綴。比如存儲客戶信息的表直接命名為Guest而非GuestInfo,存儲航班信息的表直接命名為Flight而非FlightList。還有命名表時,一律使用單數形式。例如,使用 Employee,而不是 Employees,總之,表的命名應該簡單明了。
D.關於表欄位的命名
a. 所有表中的主鍵統一命名為id,主鍵統一使用UUID,類型統一為char(32)。 不建議使用複合主鍵,即便是在多對多關係的中間表中,個人還是建議用單獨的欄位做主鍵,複合欄位加惟一約束。
b. 所有的表欄位中,除外鍵,其它欄位名都無需刻意加前後綴,也不要在欄位名前出現表名。這裡的外鍵是廣義上的外鍵,不僅包括從表引用主表主鍵的外鍵欄位,還包括存放主表相應關鍵信息的擴展欄位。
比如病人表(Patient),主鍵就是id而不是pateint_id,名稱就是name而不是patient_name。但對於外鍵,比如其它表引用Patient表的主鍵那就是patient_id,對應Patient表的name欄位那就是patient_name。如果一個表中有多個外鍵(欄位)同時引用(對應)一張表的同一個欄位,那再用其它標識,比如在「設計規範」——「基本原則」——「主鍵外鍵」中提到的會診單申請表中會診發起醫院(sender_hopital_id)和會診接收醫院(receiver_hospital_id)。
在前面的「設計規範」——「基本原則」——「主鍵外鍵」和「設計規範」——「約束控制」中有提到主鍵欄位和外鍵欄位的命名 ,這裡再次做以上說明。另,PD中在由CDM轉換成PDM時,會自動根據引用關係在從表中添加外鍵欄位,可以自定義外鍵名稱的命名規則:
c. 在前面的「設計規範」——「基本原則」——「連接查詢」和「設計規範」——「相關注釋」——「字典欄位」有關於字典欄位的詳細介紹,這裡再次說明其命名方式:對於字典欄位,編碼欄位後面跟Code後綴,文本欄位跟Text後綴,比如gender_code、gender_text。
d. 本規範中要求所有表示日期時間的欄位,都要有後綴,如果只精確到天則以Date為後綴,如果要精確到時分秒那就用Time作後綴。在「設計規範」——「欄位設置」——「通用欄位處理」中有關於日期時間類型設置的說明,要求日期時間類型的欄位,儘可能精確到時分秒,即便是像生日(birth_date)這種欄位,一般只存儲到年月日,但在選擇欄位類型時建議還是為datetime而非date。所以這裡的後綴並不是和具體欄位類型對應,而是根據實際業務情況,這個欄位存儲的數據多是精確到年月日還是時分秒,則後綴相應的為Date或Time。
網上有建議說,日期時間不要用Time做後綴,因為Time還有一個很常用的意思,就是次數。比如登錄日誌表中有用戶最後一次登錄時間欄位login_time,不去看錶的內容,很容易將login_time理解成登錄的次數。這裡我們不予考慮,只要內部統一規範,這就不會是個問題。
e. 本規範中建議是否註銷、是否成功等類似的布爾型欄位,名稱前統一加is前綴,比如是否成功(is_success)、是否註銷(is_active)、是否顯示(is_display)等。
f. 關於一些通用欄位的命名方式建議如下,僅作參考:
E.關於約束控制命名
在「設計規範」——「約束控制」中介紹過五種約束類型:唯一性和主鍵約束、外鍵約束、檢查約束、空值約束、默認值約束, 本規範中僅對外鍵約束的命名做要求,因外鍵約束標明著表與表之間的關係。我們建議外鍵約束以fk做前綴,後跟從表名稱和主表名稱:fk_從表名_主表名。這種定義方式,約束名稱很容易超長,比如在Oracle中,約束名稱的長度限制和表名一樣,不能超過30個字元。如果超長,我們建議從後向前自動截取多出部分。前面提到過,CDM轉換成PDM時會自動根據引用關係在從表中添加外鍵欄位,外鍵名稱的命名規則可以自定義。外鍵約束名稱沒必要手動添加,在PD的PDM圖中選擇:Database——Edit current DBMS——General選項卡——右側樹形菜單 ScriptObjectsReferenceConstName ,在裡面可以編輯ConstraintName的命名方式,交由PD自動統一處理,比如可設置為:FK_%.U30:CHILD%_%.U30:PARENT%。此設置在PD 15中起作用,16版本中的設置沒找到。
其它四種約束的命名,本規範中不做要求,竊以為這些約束怎樣命名也不太重要,如果需要統一命名規範, 有些也可藉助PD工具進行統一設置。
F.其它功能塊的命名
前面說過,因為自己所主張的開發模式,以往的項目中很少在資料庫中建存儲過程、觸發器、函數、序列、事件甚至是視圖等,這裡只根據經驗,給出少量建議。
視圖的命名和表的命名有很多相似點,但認為視圖的名稱最好可直接反應出其查詢的主表,或者可明確反應出視圖功能。存儲過程、觸發器、函數、索引的名稱則直接反應其功能為好,其命名方式類似於在編程語言中給某一方法命名。序列只在Oracle中有,一般用來填充主鍵和計數。在早期的資料庫設計中,喜歡用自增主鍵,比如要讓用戶表(USER)的主鍵ID自增,則創建名為SQ_USER_ID的序列和名為TR_SET_USER_ID的觸發器。序列名直接反應出自己要計數的表的列,觸發器名直接反應出自己的功能,這種命名方式或可借鑒。
不過後期項目的資料庫設計,自己不再用自增主鍵,原因在「設計原則」——「基本規範」——「主鍵外鍵」中有描述。如果項目龐大,資料庫設計的模式有變動,要大量使用存儲過程、觸發器、函數、序列等,對於這些部分的命名還是有必要規範化的。
5、梳理總結
「命名規範」牽涉的細節太多,在介紹過程中也一直妄求事無巨細,反而導致有些地方比較散亂,這裡把關鍵部分梳理總結如下:
a. 建議在SQLServer中將排序規則設置為Chinese_PRC_CS_AS,在MySQL資料庫中將Character Set設置為utf8、將Collation設置為utf8_bin,並在資料庫配置文件中設置lower_case_table_names=1。
b. 資料庫、表、欄位等所有名稱的可用字元範圍為:A-Z,a-z, 0-9 和_下劃線,長度要嚴格控制在30個字元以內。
c. 資料庫、表、欄位等所有名稱均使用英文單詞或英文短語或相應縮寫,均使用單數名,禁止使用漢語拼音。
d. Oracle表、欄位等名稱的統一使用大寫,單詞間用_下劃線分隔;SQLServer資料庫、表等名稱採用Pascal命名法,欄位名稱採用Camel命名法;MySQL資料庫、表、欄位等名稱統一使用小寫,單詞間用_下劃線分隔。
e. 表主鍵統一命名為id,主鍵統一使用UUID,類型統一為char(32)。
f. 表(廣義)外鍵建議命名為:主表名_欄位名,類型和主表中欄位類型一樣。如果一個表中有多個外鍵(欄位)同時引用(對應)一張表的同一個欄位,再根據實際情況加前後綴區分。
g. 對於字典欄位,編碼欄位後面跟Code後綴,文本欄位跟Text後綴。
h. 表示日期時間的欄位,都要有後綴,如果只精確到天則以Date為後綴,如果要精確到時分秒那就用Time作後綴。
i. 建議是否註銷、是否成功等類似的布爾型欄位,名稱前統一加is前綴,比如是否成功(is_success)、是否註銷(is_active)、是否顯示(is_display)等。
j. 建議外鍵約束以fk做前綴,後跟從表名稱和主表名稱:fk_從表名_主表名。
四 參考文獻
1、引言
文檔一開始提到過,編寫制定這個規範的過程中,從網路上翻閱了數百份資料庫設計相關的文章、帖子。並未找到一個大而全又符合心意的,不過即便是耗費諸多時間沉澱整理出的當前規範,依舊有諸多不滿意的地方,但就目前來講,這已經是最適合自己的了。不敢說是目前網路中最好的,但自信這是最認真用心的一份關係型資料庫設計相關的文檔。
把自己翻閱過的部分相關文章,單獨列成出,放在本章節。自己在文檔中多有引用,這裡對原作者一併表示感謝。
2、參考文獻
a. 設計規範
MySQL 資料庫設計規範:https://loftor.com/archives/MySQL-spec.html
MySQL庫表設計規範:http://rainforc.iteye.com/blog/2221285
資料庫設計規範:http://www.cnblogs.com/chenmh/p/3944116.html
b. 邏輯刪除
邏輯刪除:http://howiefh.github.io/2015/07/25/logical-delete/
防用戶誤刪除,耗費一周時間把DeleteMark標誌都加上來了,所有的刪除操作從「物理刪除」轉為「邏輯刪除」: http://www.cnblogs.com/jirigala/archive/2010/07/19/1780539.html
資料庫設計里的假刪除:http://blog.csdn.net/macavalier/article/details/6535778
在資料庫設計中,當刪除一條記錄的時候,是加一個標記位還是直接刪除這一行:https://segmentfault.com/q/1010000003938997/a-1020000003975492
c. 關係範式
資料庫範式(百科):http://baike.baidu.com/link?url=B12kv0sb8nDXeIpvQfobPL3KOuNpM01ihyhZ4oR9sW2bjxnpdG5bATtiQI_5qweMyL0h97UqmvZlOp7K1537P_
資料庫學習之範式理解:http://www.cnblogs.com/sunfie/p/4859294.html
d. 欄位類型
Oracle/SQLServer/MySQL 常用資料庫的欄位類型及大小:http://suexiukjung.blog.51cto.com/1169266/1147424
ORACLE使用EXPDP和IMPDP數據泵進行導出導入的方法:http://www.cnblogs.com/peterpanzsy/p/3442784.html
char、varchar、varchar2區別:http://blog.csdn.net/honglei_zh/article/details/7172538
CHAR and VARCHAR Data Types in Different Database Engines:http://www.vertabelo.com/blog/tec--hnical-articles/comparing-char-and-varchar-data-types-in-different-database-engines
MySQL探究之NULL與NOT NULL:http://blog.csdn.net/fwkjdaghappy1/article/details/7703974
MySQL的空值與NULL的區別:http://blog.csdn.net/eroswang/article/details/8529817
MySQL的decimal、float、double類型的區別:http://www.studyofnet.com/news/214.html
MySQL的IP處理函數inet_aton()和inet_ntoa():http://blog.csdn.net/ssyan/article/details/6199165
Oracle中的decimal與number區別:http://blog.csdn.net/freshlover/article/details/8809529
e. 關係約束
資料庫的約束簡介:http://www.cnblogs.com/netsql/archive/2010/05/06/1729162.html
f. 索引分類
Oracle索引的分類:http://blog.163.com/sun_xiao_min/blog/static/4859755220118208477618/
Oracle索引分析與比較:http://tech.it168.com/db/o/2006-08-18/200608181553820.shtml
g. 字典設計
應用開發中數據字典項設計實現方案:http://junjunbest.iteye.com/blog/395421
h. 歷史版本
資料庫模型設計——歷史與版本設計:http://www.cnblogs.com/studyzy/archive/2013/09/09/3310266.html
i. PD使用
使用POWERDESIGNER設計資料庫的20條技巧:http://blog.csdn.net/jiangfeng861016/article/details/7363457
Quick Reference PowerDesigner 16.1:http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc38094.1610/doc/pdf/quick_reference.pdf
Customizing and Extending PowerDesigner 16.5 SP02:http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc38628.1652/doc/pdf/customizing_powerdesigner.pdf
用PD進行版本控制:http://www.doc88.com/p-806805418378.html
j. 命名規範
MySQL資料庫命名規範及約定:http://www.jb51.net/article/47617.htm
MySQL的Collation:http://www.cnblogs.com/yjf512/p/4233601.html
MySQL中 Character Set 與Collation 的點滴理解:http://zhongwei-leg.iteye.com/blog/899227
MySQL大小寫小結:http://iMySQLdba.blog.51cto.com/1222376/1304539
MySQL表名不區分大小寫的設置方法:http://www.jb51.net/article/49249.htm
MySQL大小寫敏感說明:http://www.cnblogs.com/wzmenjoy/p/4244545.html
匈牙利命名法,駱駝命名法(camel),帕斯卡(Pascal)命名法:http://www.cnblogs.com/zsb517/p/4036160.html
英語單詞的縮寫規則:http://www.360doc.com/content/10/0519/17/289607_28427480.shtml
C++、Java與C#的命名規範總結:http://www.cnblogs.com/zyobi/archive/2009/05/21/1486448.html
Java語言編碼規範(Java Code Conventions):http://huihoo.org/code/java_code_conventions.html
.Net Framework開發人員指南——名稱準則:https://msdn.microsoft.com/zh-cn/library/ms229002(VS.80).aspx
C#命名規則、開發習慣和風格:http://www.cnblogs.com/netshuai/archive/2008/06/29/1231934.html
Oracle 字符集的查看和修改:http://www.cnblogs.com/rootq/articles/2049324.html
修改SQLServer 的編碼格式:http://blog.csdn.net/zml_900417520/article/details/50481945
查看MySQL和SQLServer資料庫的默認編碼方法:https://my.oschina.net/lance4t/blog/136252
更改SQLServer實例默認字符集:http://www.cnblogs.com/fygh/archive/2012/05/15/2501598.html
如何利用SQL Server2012創建用戶自定義數據類型:http://jingyan.baidu.com/article/6525d4b1699459ac7d2e9486.html
資料庫設計:表的設計命名的十個注意點:http://yupengcc.iteye.com/blog/2042790
k. 使用規範
【MySQL】資料庫使用的一些規範:http://www.cnblogs.com/chenpingzhao/p/5059985.html
經驗呀親。比如前面那位朋友說的NULL,NULL有意義的場合很多,我個人就反對有事兒沒事兒什麼都default,有些default是有必要的,有些就是偷懶。應用層建模也是經過反覆的,早年的空指針不好用,引發很多問題,新興的語言就逐步提供了 enumeration assoicated value ,特別是option類型。為什麼?因為「空值」或者說「非預期結果」是一個不可迴避的問題。這本身一個複雜問題,不能一概而論。
id也是一樣,現在OLTP一般都是帶自動id,提供給應用層的軟體系統使用方便,但是也不一定是自增正整數,我司的項目核心模型就用的uuid,據我所知這麼做的也不止我們一家。OLAP就更不一定了。不是誰都跟鵝廠一樣動輒幾十個PB的規模,看你自己怎麼方便怎麼來。自動主鍵有時候不能很好的表達業務。
首先,如果想要提高資料庫索引的效率,最好不要用default NULL,NULL會降低索引效率,而且不同資料庫管理系統中空值查詢出來的結果可能會不同,如果系統中同時訪問sql server和mysql等,可能會得到不同結果,對有些語言的程序處理空值會帶來麻煩。更好的做法是建表時定義好欄位空值時資料庫存儲的值,給欄位都指定默認值。
這裡反對一下上邊的答主說的每個表都要用自增欄位做主鍵。每個表確實都需要主鍵,但是重要的數據表最好不要用自增主鍵。現實項目中自增主鍵的表,當你需要從一個地方把數據讀取再次存儲時,可能就會面對一些問題,特別是分散式系統,自增主鍵會帶來很多麻煩。可以考慮不需要分散式或者不太重要的數據用自增主鍵,重要的數據則應當使用uuid生成主鍵。
其實我覺得資料庫設計並沒有定法,根據關係數據理論,或者大多數人的建議,資料庫設計理想情況下遵從三範式或BCNF比較好。但是實際上的資料庫設計更多的時候考慮查詢效率,冗餘可能會把很多事變得簡單。
數據表冗餘越少,符合的範式越高,表之間的耦合度也越高,查詢相同信息需要讀取的表可能就越多。現在有很多面向聚合的資料庫,來應對分散式,大數據的存儲需求,面向聚合資料庫的一個重要思想就是把關聯的數據都放在同一個聚合內,如果一個欄位在多個聚合中都包含,每個聚合都會存儲一份,這樣聚合之間的耦合會降低,更容易做分散式處理。
每個程序的要求都不同,資料庫設計都應該根據程序的需求和未來的擴展來考慮設計方式,並沒有定法。我覺得考慮一個欄位是只放在一個表中還是在多個表都存儲,一個操作是通過程序來實現,還是通過資料庫實現,本來就是很有趣的事。
每個系統實現都有很多方式,並不一定哪個方式就是最好的,事實上更多時候沒有最好的方式,每種方式都有好處和壞處,工程師的價值就在於根據需求權衡每種方式的優缺點,做出選擇。最開始不要怕,不要擔心自己的設計不夠好,只有不斷去嘗試,不斷去思考,才能形成自己的設計風格和自己的設計思想。
補充最重要一點,一定要養成好習慣,維護文檔,保持自己任何時候都能懂資料庫的含義。這個更多是方便自己。特別是有多人開發的項目,我通常會把建表語句保存下來。你很難保證團隊里其他人不會改了表沒有及時告訴你。
我也經驗尚淺,上邊是一些自己淺薄的看法,希望能供題主做一下參考。
其實這是一個很好的問題,對於這些細節方面的內容對資料庫的性能、可維護性都有一定影響,下面
我對題主關注的問題進行解答。
1.Null or Not Null
這需要根據業務確定使用哪一種,Null和Not Null對於性能來說各有利弊,如果允許Null則在線添加列等操作都會很方便,但一些Not in等操作的性能會受到影響,對於大多數情況來說Not null會有更好的性能。
2.數據類型
數據類型的選擇盡量對欄位用合適的類型,比如時間就應該是datetime,id就應該是int等,但盡量使用最小的數據類型,比如能用smalldatetime就不用datetime,減少空間佔用,提升性能。
但注意,最重要的一點:對於數據類型的使用必須一致,否則對性能是毀滅性的,比如表定義列a是smalldatetime,而使用是where a&
3.主鍵選擇問題
主鍵(由於主鍵默認建聚集索引,因此這裡把主鍵和聚集索引算作一回事)選擇從性能角度來說,應該遵循下面4點:短、自增、使用代理鍵、不修改。那id就是代理鍵,所謂代理鍵就是和業務無關的鍵,因此滿足上述4點,是最理想的主鍵類型(因為和業務無關,所以可以使用INT盡量短/和業務無關,因此不需要修改/和業務無關,因此可以自增)
4.表設計問題
表設計在OLTP環境一般來說應該遵循第三範式,減少冗餘和不一致問題。將表按照範式分成多個表有如下好處
1)一個過大表需要很多索引滿足查詢,而小表可以只需要更少的索引,減少空間和維護開銷
2)一個大表影響過大,比如鎖了一個大表,使用這個大表的相關查詢都會受到性能影響
3)在線維護大表往往意味著更長的停機時間,小表則具備更多靈活性
4)其實也是最重要的,範式會減少空間佔用避免數據不一致
一般來說即使是數據倉庫,也至少應該遵循第一範式。
1.能夠做到不為空就全部不為空,給defaut值。別人做不到是因為太懶了。大型系統隨便就幾千張表,幾萬個欄位,每個都這樣搞搞不過來,純粹是懶。default值在性能和邏輯上都優於null,而且不容易出bug
2.類型選擇該用啥,用啥即可。是日期就date,是數字就number,大文件blob,其它情況全部varchar. 欄位盡量多給,給到啥程度?看你對業務的理解和經驗。日後要增加長度及其痛苦的。其他的都可以當特殊情況記。有兩種特殊情況,一種是國際化的時候,時間建議存int,因為時區的問題。一種是密碼,反正哈希以後位數一樣,不妨char.
3.pk 。表可以有兩個主鍵,一個叫id純數字,業務無關,一個叫code ,varchar和日期業務等有關。id做查詢時連接使用,code 前端傳過來定位唯一行。如果是ER範式中的R表,可以沒有ID,採用複合主鍵,然而一定要有主鍵。否則資料庫亂套,違反第一範式必然作死!
4. 冗餘,一直都存在。關係型資料庫發明以前都是這樣。互聯網應用,分庫分表也只能如此復古了。
但是需要區分,業務如此的情況。比如說:用戶表,用戶銀行賬號表,付款表。付款表裡面的銀行信息就不能是引用用戶銀行表的。因為,如果用戶更換了登記銀行卡,你是引用,歷史付款記錄就都錯了,這個必須做冗餘。
每個公司都有資料庫設計規範吧。
比如我們公司會規定所有欄位必須為not null 有default值,據說是null的話對索引有影響。
類型的話,日期、時間用date、timestamp,字元串用varchar,沒什麼好爭議的。我們是禁止使用text。long就用bigint,int用int,布爾用tinyint。
每個表都有一個id,非邏輯主鍵。很自然其他表的外鍵也引這個id。
id可以是long 自增。也可以是字元串的uuid,或者其他。看你的需求,以後需不需要分表。
表的設計並不一定非得符合三大範式,有時候多加一個冗餘欄位可以提高很大效率。比如說用戶發帖,展示帖子列表的時候,可能有帖子標題、內容、發帖用戶名,如果你教條的非要去遵守範式,肯定是弄個外鍵關聯用戶,還要查用戶名。如果多加一個冗餘欄位,就只需要查帖子表就可以查到標題、內容、發帖用戶名。
當然資料庫的範式還是盡量要遵守。
最簡化版表設計原則
1、每個表必須有id,自增類型
2、每個表id後的第一個字元型欄位被當做外鍵關聯的顯示值
3、外鍵一律以表名_id進行命名。
轉自我的資料庫實驗項目
GitHub - zhblue/crud: CRUD is Really Urgly coDed -- 課設畢設快速原型
常見的一對多,多對多關係如何體現在關係型資料庫。考慮讀頻繁還是寫頻繁來設計等等。。。
使用uuid做主鍵比使用自增序列要方便的多。
用戶表不一定是bigInt
真心不認同每個表都要加個 ID 的。
推薦閱讀:
※Pythonic 是什麼意思?
※如何學習 Python GUI 編程?
※Python 網路編程需要學習哪些網路相關的知識?
※Python新手,在哪裡才能閱讀到大量的基礎Python常式?
※TCP如何保持長連接,並識別不同請求的?