數據庫設計
一: 為什么要進行數據庫的設計
- 優良的數據設計 : 減少數據冗余、避免數據維護異常、節約空間、高效訪問
- 糟糕的數據庫設計: 存在大量的數據冗余,存在數據插入、刪除、更新異常,浪費大量的存儲空間
訪問效率低。
二: 數據庫設計的步驟
需求分析--》 邏輯設計--》 物理設計 --》維護優化
- 需求分析
(1) 數據是什么
(2)數據有哪些特點
(3)數據和屬性各自的特點有哪些 - 邏輯設計
使用ER圖對數據庫進行邏輯建模 - 物理設計
根據數據庫自身的特點 將邏輯設計轉化為物理設計 - 維護和優化
(1)新的需求進行建表
(2)索引優化
(3)大表拆分
三: 為什么進行需求的分析
(1)了解系統中所要存儲的數據
(2)了解數據存儲的特點
(3)了解數據的生命周期
需要注意一些問題:
(1)實體和實體間的關系(1對1 1對多 多對多)
(2)實體所包含的屬性
(3)那些屬性和屬性之間的組合可以唯一標識一個實體
案例: 以一個電子商務網站為例,在這個電子商務網站中有幾個核心的模塊
用戶模塊、商品模塊、訂單模塊、購物車模塊、供應商模塊
(1)用戶模塊:用于記錄用戶的信息
包括的屬性:用戶名(賬號) 密碼 電話 郵箱 身份證號 地址 姓名 昵稱
可選唯一標識屬性: 用戶名(賬號) 電話 身份證號
存儲的特點: 隨著系統上線時間的增加,需要永久存儲
(2)商品模塊:用于記錄網站所銷售的商品信息
包括的屬性: 商品編碼、商品名稱、 商品描述、商品品類 、供應商名稱 、重量、有效期 、價格 ...
可選的唯一標識屬性: (商品名稱+ 供應商名稱) (商品編碼)
存儲的特點: 對下線商品可以歸檔存儲
(3)訂單模塊
包括的屬性: 訂單號、用戶姓名、用戶電話、收獲地址、商品編碼、商品名稱、數量、價格、訂單狀態、
支付狀態、訂單類型...
可選唯一標識屬性: (訂單號)
存儲特點: 永久存儲(分表、分庫存儲)
(4)購物車模塊
包括的屬性:用戶名、商品編碼、商品名稱、商品價格、商品描述、商品分類、加入時間、商品數量...
可選唯一標識: (用戶名、商品編號、加入時間) (購物車編號)
存儲特點: 不用永久存儲(設置歸檔、清理規則)
(5)供應商模塊:用于保存所銷售商品供應商的信息
包括屬性:供應商編號、供應商名稱、聯系人、電話、營業執照、地址、法人...
可選唯一標識: (供應商編號)(營業執照)
存儲特點: 永久存儲
實體和實體之間的關系:
四: 邏輯設計
(1) 將需求轉化為數據庫的邏輯模型
(2)通過ER圖對邏輯模型進行展示
(3)同所選的DBMS無關
-
表示方法:
矩形: 表示實體集,矩形內寫實體集的名字
菱形:表示聯系集
橢圓:表示實體的屬性
線段: 將屬性連接到實體集或將實體集連接到聯系集
QQ截圖20180402143004.png
數據庫設計的范式
第一范式: 數據表中所有字段都是單一屬性,不可再分
第二范式: 在第一范式的基礎上,數據表中不存在非關鍵字段對任意候選關鍵字段的部分函數依賴
注:部分函數依賴是指,存在組合關鍵字中的某一關鍵字決定非關鍵字的情況,所有的單關鍵字都符合第二范式
第三范式: 在第二范式的基礎上,數據表中不存在非關鍵字段,對任意候選關鍵字段的傳遞依賴
BC范式:在第三范式的基礎上,數據表中不存在任何字段對任一候選關鍵字段的傳遞依賴
注:也就是說如果是復合關鍵字,則復合關鍵字之間不能存在函數依賴關系數據操作異常和冗余
(1)插入異常
如果某個實體隨著另一個實體的存在而存在,即缺少某個實體時無法表示這個實體,那么這個表就存在插入異常
(2)更新異常
如果更改表所對應的某個實體實例的單獨屬性時,需要將多行更新,那么就說這個表存在更新異常
(3)刪除異常
如果刪除表的某一行來反映某實體實例,失效時導致另一個不同 實體實例信息丟失,那么這個表就存在刪除異常
(4) 數據冗余
是指相同的數據在多個地方存在,或者說表中的某個列可以由其他列計算得到,這樣就說表中存在數據冗余
五:物理設計
物理設計需要考慮的問題
1 選擇合適的數據庫管理系統
2 定義數據庫表及字段命名規范
3 根據所選的DBMS系統選擇合適的字段類型
4 反范式化設計
(1)mysql常用的存儲引擎
存儲引擎 | 事物 | 鎖粒度 | 主要應用 | 忌用 |
---|---|---|---|---|
MyISIM | 不支持 | 支持并發插入的表級鎖 | select insert | 讀寫操作頻繁 |
MRG_MyISIM | 不支持 | 支持并發插入的表級鎖 | 分段歸檔,數據倉庫 | 全局查找過多的場景 |
innodb | 支持 | 支持MVCC的行級鎖 | 事物處理 | 無 |
Archive | 不支持 | 行級鎖 | 日志記錄只支持insert selec | 需要隨機讀取更新刪除 |
Ndb cluster | 支持 | 行級鎖 | 高可用性 | 大部分應用 |
(2)表及字段的命名規則
可讀性原則: 使用大寫和小寫來格式化的庫對象名字,獲得良好的可讀性。
表意性原則: 對象的名字應該能夠描述它所標識的對象
長名性原則: 盡可能少使用或者不適用縮寫
(3)字段類型的選擇原則
3.1 列的數據類型一方面影響數據存儲空間的開銷,另一方面也會影響數據查詢的性能。當一個列可以選擇多種數據類型的時候
,應該優先考慮數字類型,其次是日期或二進制類型,最后是字符類型。對于相同級別的數據類型,應該優先選擇占用空間小的數據類型。
以上選擇 主要從 下面兩個角度考慮
a: 在對數據進行比較(查詢條件、join 條件及排序)操作時 :同樣的數據,字符處理往往比數字處理慢
b: 在數據庫中,數據處理以頁為單位,列的長度越小,利于提升性能。
3.2 char 和 varchar 如何選擇
a: 如果列中要存儲的數據長度差不多是一致的,則應該考慮用char ;否則應該考慮用varchar 。
b: 如果列中最大數據長度小于50字節,則一般也考慮用char
c: 一般不宜定義大于50個字節的char類型列
3.3 decimal 與 float 如何選擇
a: decimal用于存儲精確數據 而float用于存儲非精確數據。故精確數據類型只能選擇用 decimal 類型
b: 由于float的存儲空間開銷比decimal小(精確到7位小數只需要4個字節 而精確到15位小數只需要8個字節)故非精確數據優先選擇float類型
3.4 時間類型如何存儲
a: int 類型存儲時間 字段的優缺點
優點: 字段長度比 datetime 小
缺點: 使用不方便,要進行函數轉化
限制: 只能存儲到 2038-1-19 11:14:07 即 2^32
b 需要存儲的時間粒度
年月日 小時 分 秒 周
(4) 需要考慮的其他問題
4.1 如何選擇主鍵
a: 區分業務主鍵和數據庫主鍵
業務主鍵用于標識業務數據 ,進行表與表之間的關聯 ,數據庫主鍵為了優化數據庫存儲 (innodb會生成6個字節的 隱含主鍵)
b: 根據數據庫類型,考慮主鍵是否要順序增長
有些數據庫是按主鍵的順序邏輯存儲的
c: 主鍵字段所占空間盡可能的小
對于使用聚族索引方式存儲的表 每個索引后都附加主鍵的信息
4.2 避免使用外鍵約束
- 降低數據導入的效率
- 增加維護的成本
- 雖然不建議使用外鍵約束,但是相關聯的列上面一定要建立索引
4.3 避免使用觸發器
- 降低數據導入的效率
- 可能會出現 意想不到的異常
- 使業務邏輯變得復雜
4.4 關于預留字段
- 無法準確的知道預留字段的類型
- 無法準確的指導預留字段的內容
- 后期維護預留字段的成本同增加一個字段的成本是相同的
- 嚴禁使用預留字段
(5)反范式化設計
什么是反范式化?
反范式化是針對 范式化而言,所謂反范式化就是為了性能和讀取效率考慮而適當對第三范式的要求進行違反,而允許存在少量的數據冗余,
換句話說反范式化就是用空間換取時間。
反范式化設計的優點:減少表的關聯數量、增加數據的讀取效率
反范式化一定要適度
六: 維護和優化
維護索引
如何維護索引?
索引并不是越多越好,過多的索引不但會降低寫的效率,而且會降低讀的效率,定期維護索引碎片
在sql 語句中不要使用強制索引關鍵字維護表結構
如何維護表結構?
mysql5.5 之前使用pt-online-schema-change
mysql5.6 之后本身支持在線表結構的變更 同時對數據字典進行維護
控制表的寬度和大小
數據庫中適合的操作
- 使用批量操作代替逐條操作
- 禁止使用select * 這樣的查詢
- 控制使用用戶自定義函數
- 不要使用數據庫的全文索引
- 在適當的時候對表進行水平拆分和垂直拆分
表的垂直拆分: 為了控制表的寬度而進行垂直拆分
- 經常一起查詢的列放在一起
- text blob等大字段拆分到附加表中
表的水平拆分: 為了控制表的大小進行水平拆分
原理 對主鍵值進行hash 操作 (比如拆分成5張表 主鍵值除以5根據余數劃分表)
二: 數據庫優化
從索引的角度對數據庫進行優化
索引的分類
(1)從數據結構角度
B+樹索引、hash索引、FULLTEXT索引、R-tree樹索引
(2) 從物理角度
聚族索引、非聚族索引
(3)從邏輯角度
主鍵索引 唯一索引 多列索引 單列索引創建索引的一些基本原則:
(1)多列索引需要滿足左前綴原則
(2)= 和 in 可以亂序, 比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引
可以識別的形式
(3)盡量選擇區分度高的列作為索引(區分度越高,我們掃描的記錄就越少)
(4)索引列不能參與計算(索引列一旦參與函數計算,會使得索引失效)
(5)盡量擴展索引,不要新建索引,比如表中已經有a索引,現在要加(a,b)索引只需要修改原來的索引即可。
注:一些誤區: 多列索引只能用一個,所以我們單獨在每個列上面加索引只能有一個列發揮作用,通常我們在這些列上加一個聯合索
引這樣 發揮索引的列數更多,查詢更快。(聯合索引發揮作用要遵循左前綴原則)
那么理想的索引是什么樣的呢?
1.查詢頻繁 2. 區分度高 3. 長度小 4.盡量能覆蓋常用的查詢字段
- explain 分析sql
explain 輸出解釋:id | select_type| table|type|possible_keys|key|ken_len|ref|rows|Extra
3.1 id
sql執行的順序標識 sql從小到大執行
3.2 select_type
(1)SIMPLE
簡單的SELECT(不適用UNION或子查詢)
(2)PRIMARY
最外層的select
(3)UNION
UNION中的第二個或后面的SELECT語句
(4)DEPENDENT UNION
UNION 中的第二個或后面的SELECT語句,取決于外面的查詢
(5)UNION RESULT
UNION 的結果
(6)SUBQUERY
子查詢中的第一個select
(7)DEPENDENT SUBQUERY
子查詢中的第一個select取決于外面的查詢
(8)派生的select (FROM子句的子查詢)
3.3 table
顯示這一行數據是關于那張表的,有時候不是真實的表 看到的是derivedx(臨時表)
3.4 type
顯示使用哪種類別有無使用索引 從好到差 const eq_ref ref range index All
(1) system
這是const 聯接類型的一個特例。表示僅有一行滿足條件
(2) const
表最多有一個匹配行,它將在查詢開始時被讀取,因為僅有一行,在這行的列值可被優化器剩余部分認為是常數, const表很快,
因為他們只讀取一次。
(3) eq_ref
對于每個來自前面的表的行組合,從該表中讀取一行。這可能是最好的連接類型,除了const類型,它用在一個索引的所有部分被
連接使用,并且索引是UNIQUE和PRIMARYKEY. eq_ref 可以用= 比較帶索引的列。 比較的值可以為常量或一個使用在該表前面
所讀取的表的列的表達式。
(4) ref
對于每個來自前面表的行組合,所有有匹配索引值的行將從這張表讀取。如果連接只是用鍵的最左邊前綴,或如果鍵不是UNIQUE
或 PRIMARY KEY 則使用ref 如果使用的鍵僅僅匹配少量的行,該連接類型是不錯的。ref 可以使用= 或 <=>操作符的帶索引的列
(5) ref_or_null
該列類型如同ref 但是添加了MYSQL 可以專門搜索NULL值的行。在解決子查詢時經常使用該連接類型優化。
(6)index_merge
該連接類型表示使用了 索引合并優化。在這種情況下 key 列包含使用索引的清單 ,key_len 包含使用索引最長的關鍵元素
(7)unique_subquery
該類型替換了下面形式的IN子查詢的ref:
value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery是一個索引查找函數,可以完全替換子查詢,效率更高。
(8) index_subquery
該聯接類型類似于unique_subquery。可以替換IN子查詢,但只適合下列形式的子查詢中的非唯一索引:
value IN (SELECT key_column FROM single_table WHERE some_expr)
(9)range
只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引。key_len包含所使用索引的最長關鍵元素。在該類型
中ref列為NULL。當使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比較關鍵字列時,可以
使用range
(10) index
該聯接類型與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因為索引文件通常比數據文件小。當查詢只使用作為單索引一部
分的列時,MySQL可以使用該聯接類型
(11)all
對于每個來自于先前的表的行組合,進行完整的表掃描。如果表是第一個沒標記const的表,這通常不好,并且通常在它情況下很差。
通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常數值或列值被檢索出
3.5 possible_key
possible_keys列指出MySQL能使用哪個索引在該表中找到行。注意,該列完全獨立于EXPLAIN輸出所示的表的次序。這意味著在possible_keys
中的某些鍵實際上不能按生成的表次序使用。
3.6 key
key列顯示MySQL實際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。要想強制MySQL使用或忽視possible_keys列中的索引,在查詢
中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。
3.7 key_len
key_len列顯示MySQL決定使用的鍵長度。如果鍵是NULL,則長度為NULL。 使用的索引的長度。在不損失精確性的情況下,長度越短越好
3.8 ref
ref列顯示使用哪個列或常數與key一起從表中選擇行。
3.9 rows
rows列顯示MySQL認為它執行查詢時必須檢查的行數。(優化sql主要降低rows的數量)
3.10 Extra
該列包含mysql解決查詢的詳細信息
(1)Distinct
一旦MYSQL找到了與行相聯合匹配的行,就不再搜索了
(2)Not exists
MYSQL優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標準的行, 就不在搜索了
(3)Range checked for each
沒有找到理想的索引,因此對于從前面表中來的每一個行組合,MYSQL檢查使用哪個索引,并用它來從表中返回行。這是使用索引的
最慢的連接之一
(4)Using filesort
看到這個的時候,查詢就需要優化了。MYSQL需要進行額外的步驟來發現如何對返回的行排序。它根據連接類型以及存儲排序鍵值和
匹配條件的全部行的行指針來排序全部行
(5)Using index
列數據是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發生在對表的全部的請求列都是同一個索引的部分的時候
(6)Using temporary
看到這個的時候,查詢需要優化了。這里,MYSQL需要創建一個臨時表來存儲結果,這通常發生在對不同的列集進行ORDER BY上,
而不是GROUP BY上
(7).Using where
使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給用戶。如果不想返回表中的全部行,并且連接類型ALL或index,這就
會發生,或者是查詢有問題