1. 程式人生 > 其它 >資料庫專題(二) ——資料庫設計

資料庫專題(二) ——資料庫設計

資料庫專題(二)——資料庫設計

(原創內容,轉載請註明來源,謝謝)

一、資料庫設計規範——正規化

資料庫設計,需要遵循設計原則,最主要的設計原則是正規化。正規化是遵循一定規則的資料庫設計原則,一共有8種正規化:1NF,2NF,3NF,BCNF,4NF,5NF,DKNF,6NF。其中對資料庫設計要求逐步提高,即滿足2NF的資料庫設計必須滿足1NF。

通常資料庫設計到3NF或BCNF,部分情況下還需要反正規化。

1、1NF

第一正規化(1NF)是資料庫設計的基本要求,它要求每一個欄位都具有原子性,不能再分割。

例如記錄個人資訊表,有id、姓名、性別等。當姓名包括中文名和英文名時,就不能單獨用姓名一個欄位來儲存資訊,而需要將姓名欄位拆成中文名和英文名。

1NF是關係型資料庫的基本原則,即便後面提到反正規化通常也不會反1NF,否則就不是關係型資料庫了。

2、2NF

第二正規化要求在滿足1NF的前提下,表中必須要有主鍵,且其他非主鍵的欄位要完全依賴於主鍵。

例如學生成績表,包括學生id、學生姓名、學校名稱、學院名稱、課程id、成績。這個表的冗餘資訊過大,因為從邏輯上來說,知道學生id就可以知道學生姓名、學校、學院,這些都屬於重複的資訊。另外,如果只有這一個表,無法完全表示學校每個學院的資訊,因為如果有個新學院,還沒招人,則無法建立資料。另外,如果某個學院的學生id全部刪除,表示這個學院目前暫時沒有學生,但是在表中卻沒有了資料。

因此,需要拆成兩表,一個表是學生id、學生姓名、課程id、成績,另一個表是表id、學校名稱、學院名稱、學生id。

3、3NF

第三正規化要求在滿足2NF的前提下,表中的非主鍵欄位必須直接依賴於主鍵,而不是通過其他非主鍵欄位間接依賴於主鍵。

例如購物車表,欄位包括使用者id、商品id、商品名稱、商品單價。則此時商品名稱和商品單價是通過商品id來與使用者id關聯的,則屬於冗餘的情況,需要把表拆成使用者購物表:使用者id、商品id,商品表:商品id、商品名稱、商品單價。

但是在很多大資料量情況下,常常會出現反3NF的情況。

4、BCNF

此正規化的要求高於3NF,屬於3NF之上的正規化要求。在2、3NF中都只對非主鍵欄位有要求,但是沒有考慮到多個主鍵的情況下,主鍵之間的關係。BCNF是用來解決此問題的。

例如此時有一個倉庫資訊表,要求如下:公司有多個倉庫,每個倉庫僅一個管理員,每個管理員僅在一個倉庫工作,每個倉庫有多種商品,每種商品也可以放在多個倉庫。

倉庫資訊表的欄位是倉庫名、管理員名、物品名、物品數量。因為倉庫、管理員、物品都是唯一的標識,因此聯合主鍵倉庫、管理員、物品,物品的數量與倉庫、管理員、物品都有關係。

現如果要新增一個倉庫,還沒指派管理員也還沒物品,則無法新增,因為倉庫、管理員、物品都是主鍵;如果要刪除倉庫的全部物品,則倉庫本身和管理員資訊都被刪除;要修改倉庫管理員的資訊,需要把倉庫所有的物品資料都改一遍。

以上三種情況分別稱為插入異常、刪除異常、更新異常。

此時,將表拆成兩個表。

表1是倉庫表,包含倉庫id、倉庫名稱、管理員名稱,倉庫id是主鍵。表2是倉庫物品表,包括倉庫id、物品id、物品名稱、物品數量。

5、文章推薦

知乎上有一篇對此描述很詳細,https://www.zhihu.com/question/24696366。

二、反正規化

1、背景

正規化是在20世紀70-80年代提出的概念,當時的一個前提是硬碟容量少,因此除了理清資料表的關係外,還需要節約珍貴的硬碟儲存空間。而在完全遵守正規化的情況下,設計出來的表將沒有冗餘,做到最大限度的節約空間。

但是,現在硬碟容量已經很大,相比之下,瓶頸不在於此。當大量資料查詢請求下,如果完全遵守正規化,需要多個連表查詢,對於報表等欄位眾多的資料表,甚至要達到數十次的連表查詢,這對效能的消耗非常大,會導致查詢速度緩慢。

2、基本原則

正規化的設計是有其正確性,因此在非必需的情況下,還是要遵守正規化的資料庫設計原則,節約儲存空間,且每個表的資訊單一,對錶的操作相對簡單,邏輯清晰。

1)核心資訊

核心資訊必須遵守正規化,如使用者資訊表,使用者的資訊在其他表裡出現通常只會出現使用者id,不會出現其他資訊,此為對使用者資訊的保密。且保證資訊的完全一致性。

2)弱一致性需求

當對資料完整性要求不大時,為了保證查詢速度的前提下,可以反正規化。反正規化造成的冗餘,如果在歷史資料表中,且這些資料不常使用,則可以定時去更新,不必要實時更新。

實際上冗餘表就是拿空間換取時間的操作,冗餘表一般也要遵守1NF和2NF。

三、資料庫分表

分表有水平分表和垂直分表兩種。

1、水平分表

當表的資料量非常大時,通常會用到水平分表。

例如交易資訊表,其資料量巨大時,可以將表水平拆分。交易資訊的拆分通常是通過日期,將經常查詢的當月資訊放在一個表中,本年每個季度的資訊存在季度的表中,三年內的其餘資訊存在一個表中,剩餘的歷史資料則放在歷史表中。這種歷史表通常放在磁帶庫中,僅作備份使用,通常也不支援使用者查詢。

再例如,使用者資訊表,使用者量大的情況下,可以將表拆成10個表,通過使用者id除以10取餘數,按照餘數0-9分別將使用者資訊放在9個表中。

2、垂直分表

垂直分表通常是因為表的欄位眾多,且有幾個欄位是大欄位(如text型別)。則此時對錶的增刪改查的時候,如果那些大欄位和其他欄位不用同時出現時,可以將大欄位專門放到一個表裡。此時即本可以放在一個表中的資料放到了兩個表,不太符合設計原理,但是卻保證大資料量下的效率與穩定性。

四、資料庫分割槽

資料庫的資料是存放在檔案中,以此來儲存在硬盤裡。資料庫的表的分割槽,可以理解為將邏輯上是在一個表的資料,在物理層面上存放在不同的檔案中。資料庫引擎在收到分割槽命令後,在存放資料時,會根據使用者定義的分割槽規則,自動將資料按照規則存放在不同的檔案內。在使用者讀取資料時,會根據使用者讀取的條件(如where條件),去不同的檔案裡面找相應的資訊。

Mysql的分割槽有Range、List、Hash、Key、複合分割槽。複合分割槽即對前面幾種分割槽方式生成的子分割槽,再次使用前面幾種分割槽方式的任一種進行二次分割槽。

1、Range:每個分割槽包含那些分割槽表示式的值位於一個給定的連續區間內的行

訂單資訊表,按年進行分割槽時,如下。

createtable range(
id int(11),
moneyint(11) unsigned not null,
datedatetime
)partition by range(year(date))(
partitionp2017 values less than (2018),
partitionp2016 values less than (2017),
partitionp2015 values less than (2016)
partitionpother values less than (2015)
);

2、List:每個分割槽的定義和選擇是基於某列的值從屬於一個值列表集中的一個值

使用者資訊表,根據id和4的取餘,將使用者分在兩個表中

createtable user(
id int(11),
name int(11)
)(
partition by list (id%4)
partitionp0 values in (1,3),
partitionp1 values in (0,2)
     );

上面的這個建立list分割槽時,如果有主銉的話,分割槽時主鍵必須在其中,不然就會報錯。

3、Hash:主要用來確保資料在預先確定數目的分割槽中平均分佈,基於將要被雜湊的列值指定一個列值或表示式,以及指定被分割槽的表將要被分割成的分割槽數量。

下面用年的欄位作為雜湊,分割成4個表。

createtable hash(
a int(11),
b datetime
)(
partition by hash (YEAR(b))
partitions 4;
)

4、Key:類似於按照HASH分割槽,除了HASH分割槽使用的使用者定義的表示式,而KEY分割槽的雜湊函式是由MySQL 伺服器提供。

createtable t_key(
a int(11),
b datetime
)(
partition by key (b)
partitions 4;
)

5、分割槽的維護

刪除:

ALTER TABLE table_nameDROP PARTITION partition_name;

新增:

ALTER TABLE table_nameADD PARTITION (PARTITION partition_name VALUES partition_rule);

合併:

ALTER TABLE table_name
REORGANIZE PARTITION partition_name1, partition_name2, partition_name3,
INTO
(
PARTITION partition_name1 VALUES partition_rule,
PARTITION partition_name2 VALUES partition_rule,
PARTITION partition_name3 VALUES partition_rule
);

——written by linhxx 2017.07.29