參考網址:https://www.jianshu.com/p/62867b4ff514
不知道各位看官,是否有過想嘗試設計表的想法呢?
數據間紛亂復雜的關系,又該如何下手呢?
滿足什么樣的設計原則,才可以符合正確、完整、一致且安全的要求呢?
或許,小采風想和各位一起學習一下表的設計規范。子曾經曰過:欲窮天下之事,深諳套路二字。踏着深深的套路,走進今天的力學篤行。
一、表的設計步驟
-
需求分析:存儲 處理 安全 完整
有位同學,專職於后台產品經理,負責產品具體的業務邏輯,有幸有過一次深聊。業務邏輯,需要正確的框架和允許范圍的波動。人算不如天算,充分考慮產品的存儲需求、數據處理需求、數據的安全需求和完整性要求,這是文檔要求,我們這些小白能做的,莫過於盡力符合文檔需求,往往不是一蹴而就的。
-
邏輯設計:結構 關系 冗余 異常
什么樣的表設計,是足夠合理呢?符合三大范式設計原則的表,至少是正確完整的,但是往往不是足夠高效的,需要進行一定的反范式化設計。
1 第一范式(不再具體介紹)
字段只有單一屬性且不可分割,由基本數據類型組成,必須是簡單的二維表;
2 第二范式
不能允許非主鍵列對部分主鍵存在依賴關系;
3 第三范式
既不部分依賴於業務主鍵,也不傳遞依賴於業務主鍵;
看完三大范式,不要急着罵小采風。這不就是天書嗎?完全沒有辦法去理解,我們還是看一看具體的例子,嘗試搞清楚這些苦澀的范式吧!
//從選課表中理解第二范式 create table select_course1( stu_id int(10) comment '學號', stu_name varchar(20) comment '姓名', course_name varchar(10) comment '課程名稱', course_credit int(2) comment '學分', //學生和課程作為主鍵,唯一標識 primary key(stu_id,course_name) )engine=Innodb;
簡單一看,這個表的設計,是沒有問題的。可是,如果有一天高數從6個學分變成8個學分了,我們還需要一步步更改表中每行記錄,完成高數學分的修改嗎?問題的本質是,非主鍵course_credit列對復合主鍵列(stu_id,course_name)中的course_name存在部分依賴,即高數學分依賴於高數課程,違反第二范式會產生插入、更新和刪除異常,應該將其更改為兩張表,具體如下:
//更改為選擇表和課程表 create table select_course2( stu_id int(10) comment '學號', stu_name varchar(20) comment '姓名', course_name varchar(10) comment '課程名稱', //學生和課程作為主鍵,唯一標識 primary key(stu_id,course_name) )engine=Innodb; create table course( course_name varchar(10) comment '課程名稱', course_credit int(2) comment '學分', primary key(course_name) )engine=Innodb;
那么存在一個問題,如何生成一張和select_course1相同的表呢?可以使用關聯(join)將select_course2和course連接生成;
稍事休息,一起來看一看第三范式的理解:
//學生信息表 create table stu_info( stu_id int(10) comment '學號', stu_name varchar(20) comment '姓名', school_name varchar(20) comment '學院名稱', school_tel varchar(20) comment '學院電話', primary key(stu_id) )engine=Innodb;
這張表的問題在於,從學號可以知道學院名稱,從學院名稱可以知道學院電話,本質是,school_tel部分依賴於stu_id,即學院電話部分依賴於學院學號,違反第三范式,應該將其拆分成兩張表,具體如下:
create table stu_info( stu_id int(10) comment '學號', stu_name varchar(20) comment '姓名', school_name varchar(20) comment '學院名稱', primary key(stu_id) )engine=Innodb; create table school_info( school_name varchar(20) comment '學院名稱', school_tel varchar(20) comment '學院電話', primary key(school_name) )engine=Innodb;
關於三大范式的理解,先暫時告一段落,不然小采風已經迷亂。
-
物理設計:規范 存儲引擎 數據類型 結構
物理設計主要考慮一下問題:
1 定義數據庫、表及字段的命名規范
2 選擇合適的存儲引擎
3 為字段選擇合適的數據類型
4 數據庫結構
-
維護優化:SQL優化 索引優化
個人認為,相比於前面三點,這點是最為重要,像是終極大boss一樣,需求分析會存在漏洞,邏輯設計會有不足之處,物理設計會有bug,但是維護優化環節,需要更多的經驗和更細致的考量。近日,了解了些數據庫架構中的內容,主從拓撲,主從切換,MMM和MHA等知識,主庫的讀寫分離,從庫的讀負載均衡,主從切換時的異步延遲,從庫IO線程讀取binlog日志的不同,binlog日志bindump命令的阻塞等,面對這樣歷史大難題,小采風只能知難而退了,留給看官中的您,讓世界更美好了。
本文似乎有點頭重腳輕,說是力學篤行篇,卻並沒有一點干貨呢?看官們不要着急,下面讓我們從現實出發,看看現實場景中,一個業務的表是如何設計的(不再進行具體的表創建等),尾隨套路的步伐,一步步登上封頂。
二、需求分析
- 產品需求
實現圖書類的電商網站 - 具備功能
用戶登錄、用戶管理、商品展示、商品管理、在線銷售 - 以電商網站為例考慮
三、邏輯設計
- 用戶登錄及管理
//用戶信息表:用戶名為主鍵 用戶名,密碼,手機號,姓名,出生日期,在線狀態 //具體分析 1 以用戶名為主鍵,滿足第二范式設計; 2 不存在部分依賴和傳遞依賴關系,符合第三范式設計
- 商品展示及管理
//商品信息表:(商品名稱,分類名稱)聯合主鍵 商品名稱,分類名稱,出版社名稱,圖書價格, 圖書描述,作者 //具體分析 1 圖書描述對商品名稱存在依賴關系; 2 僅有分類名稱,沒有分類描述,完整性不足;
可以考慮將商品信息拆分成三張表,如下:
//商品信息表:商品名稱為主鍵 商品名稱,出版社名稱,圖書價格,圖書描述,作者 //分類信息表:分類名稱為主鍵 分類名稱,分類描述 //商品分類關系表:(商品名稱,分類名稱)聯合主鍵 商品名稱,分類名稱
- 訂單管理
//訂單表:訂單編號為主鍵 訂單編號,下單用戶,下單日期,訂單金額, 訂單商品分類,訂單商品名,訂單商品單價, 訂單商品數量,支付金額 //具體分析 1、訂單商品分類,訂單商品名,訂單商品單價已經 存在於商品及分類表中,數據嚴重冗余; 2、訂單金額可以有訂單計算而來;
考慮將訂單表分為下面兩張表,如下:
//訂單表:訂單編號為主鍵 訂單編號,下單用戶名,下單日期,支付金額 //訂單商品關聯表:(編號,分類,商品名)聯合主鍵 訂單編號,訂單商品分類,訂單商品名,商品數量
以上設計,當然不是最合理的,不過基本滿足三大范式的要求,基本實現正確性、完整性和一致性,但是這樣的文檔式設計,能實現高效查詢嗎?
//查詢某位用戶的訂單總金額 select 下單用戶名,sum(d.商品價格*b.商品數量) from 訂單表 a join 訂單商品關聯表 b on a.訂單編號=b.訂單編號 join 商品分類關系表 c on c.商品名稱=b.商品名稱 and c.分類名稱=b.分類名稱 join 商品信息表 d on d.商品名稱=c.商品名稱 group by 下單用戶名 //具體分析 一條簡單的查詢,需要關聯四張表,這樣會嚴重影響查詢效率
教科書中,往往並不是最理想的情況,仍然需要對表進行適當的修改:
- 反范式化設計:增加冗余,減少表的關聯
//1 在訂單商品關聯表中增加商品價格字段, 減少關聯商品信息表 訂單編號,訂單商品分類,訂單商品名, 商品數量,商品價格 //2 去除商品分類信息表, 直接基於商品信息表和分類信息表查詢
//查詢某位用戶的訂單總金額 select 下單用戶名,sum(b.訂單商品名*b.商品數量) from 訂單表 a join 訂單商品關聯表 b on a.訂單編號=b.訂單編號 group by 下單用戶名
世界總是這般公平,反范式化通過增加冗余,提高查詢效率,但是同時增加表結構的冗余和數據維護異常的難度,所以,適當情境下自由適當權衡。
四、物理設計
- 存儲引擎選擇
不同的場景需要不同的存儲引擎選擇,主要是以下幾個方面:
事務支持,表鎖行鎖,讀寫性能,索引優化,主鍵外鍵 - 字段數據類型選擇
1 選擇原則
優先考慮數字類型,其次是日期或二進制類型,最后是字符類型;
相同級別的數據類型,優先選擇占空間小的數據類型;
2 主鍵選擇
主鍵應盡可能小,因為主鍵索引中包含非主鍵屬性;
主鍵應該是順序增長的,提高插入和刪除效率;
3 VARCHAR和CHAR類型:以字符為單位,不是以字節為單位
VARCHAR:
字符串列的最大長度比平均長度大,發揮變長字符特性;
字符串序列少被更新,減少存儲碎片;
CHAR:
字符串序列存儲長度近似的值;
適合存儲短字符串;
適合存儲更新頻度比較高的字符串;
4 整數類型和實數類型
整數類型的位數,由實際場景選擇;
float和double類型的數據精度問題,在財務類應用中需要注意;
5 日期類型:推薦使用date
字符串存儲8字節,datetime存儲4字節,int存儲4字節,date類型3字節;
date類型有很多日期時間函數可以使用;
紙上得來終覺淺,覺知此事要躬行。昨天舍友參加了阿里中間件技術的介紹,主講人一句話,如果說淘寶技術是喜馬拉雅山脈 ,那么中間件技術就是珠穆朗瑪峰。在mysql的架構中,中間件用於洪水猛獸般的高並發請求時的讀寫分離和讀負載均衡,這是技術造福於社會的終極智慧,向工程師看齊。好久沒有這樣的長文了,四個字,與君同行。
作者:采風JS
鏈接:https://www.jianshu.com/p/62867b4ff514
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。