干貨:
主鍵是關系表中記錄的唯一標識。主鍵的選取非常重要:主鍵不要帶有業務含義,而應該使用BIGINT自增或者GUID類型。主鍵也不應該允許
NULL。可以使用多個列作為聯合主鍵,但聯合主鍵並不常用。關系數據庫通過外鍵可以實現一對多、多對多和一對一的關系。外鍵既可以通過數據庫來約束,也可以不設置約束,僅依靠應用程序的邏輯來保證。
通過對數據庫表創建索引,可以提高查詢速度。通過創建唯一索引,可以保證某一列的值具有唯一性。數據庫索引對於用戶和應用程序來說都是透明的。
一、SQL(Structured Query Language)
數據模型:層次模型、網狀模型、關系模型
數據類型:

關系型數據庫:MYSQL
非關系型數據庫:NoSQL、MongoDB、Cassandra、Dynamo
主流關系數據庫:
- 商用數據庫,例如:Oracle,SQL Server,DB2等;
- 開源數據庫,例如:MySQL,PostgreSQL等;
- 桌面數據庫,以微軟Access為代表,適合桌面應用程序使用;
- 嵌入式數據庫,以Sqlite為代表,適合手機應用和桌面程序。
SQL語言定義了三種操作數據庫的能力:
-
DDL: Data Definition Language
DDL允許用戶定義數據,即創建表、刪除表、修改表結構這些操作。通常,DDL由數據庫管理員執行
-
DML: Data Manipulation Language
DML為用戶提供添加、刪除、更新數據的能力,這些是應用程序對數據庫的日常操作。
-
DQL: Data Query Language
DQL允許用戶查詢數據,這也是通常最頻繁的數據庫日常操作。
語法特點:SQL語言關鍵字不區分大小寫,但是,針對不同的數據庫,對於表名和列名,有的數據庫區分大小寫,有的數據庫不區分大小寫。同一個數據庫,有的在Linux上區分大小寫,有的在Windows上不區分大小寫。
二、關系模型
關系數據庫是建立在關系模型上的,而關系模型本質上是若干個存儲數據的二維表,可以把它們看做很多Excel表。
表的每一行稱為記錄(Record),記錄是一個邏輯意義上的數據;
表的每一列稱為字段(Column),同一個表的每一行記錄都擁有相同的若干字段。
字段定義了數據類型(整型、浮點型、字符串、日期等),以及是否允許為NULL。注意NULL表示字段數據不存在。一個整型字段如果為NULL不表示它的值為0,同樣的,一個字符串型字段為NULL也不表示它的值為空串''。
和Excel表有所不同的是,關系數據庫的表和表之間需要建立“一對多”,“多對一”和“一對一”的關系,這樣才能夠按照應用程序的邏輯來組織和存儲數據。
1.主鍵
對於關系表,有個很重要的約束,就是任意兩條記錄不能重復。不能重復不是指兩條記錄不完全相同,而是指能夠通過某個字段唯一區分出不同的記錄,這個字段被稱為主鍵。
選取主鍵的一個基本原則是:不使用任何業務相關的字段作為主鍵。
因此,身份證號、手機號、郵箱地址這些看上去可以唯一的字段,均不可用作主鍵。
作為主鍵最好是完全業務無關的字段,我們一般把這個字段命名為id。常見的可作為id字段的類型有:
- 自增整數類型:數據庫會在插入數據時自動為每一條記錄分配一個自增整數,這樣我們就完全不用擔心主鍵重復,也不用自己預先生成主鍵;
- 全局唯一GUID類型:使用一種全局唯一的字符串作為主鍵,類似
8f55d96b-8acc-4636-8cb8-76bf8abc2f57。GUID算法通過網卡MAC地址、時間戳和隨機數保證任意計算機在任意時間生成的字符串都是不同的,大部分編程語言都內置了GUID算法,可以自己預算出主鍵。
對於大部分應用來說,通常自增類型的主鍵就能滿足需求。我們在students表中定義的主鍵也是BIGINT NOT NULL AUTO_INCREMENT類型。
如果使用INT自增類型,那么當一張表的記錄數超過2147483647(約21億)時,會達到上限而出錯。使用BIGINT自增類型則可以最多約922億億條記錄。
聯合主鍵
關系數據庫實際上還允許通過多個字段唯一標識記錄,即兩個或更多的字段都設置為主鍵,這種主鍵被稱為聯合主鍵。
對於聯合主鍵,允許一列有重復,只要不是所有主鍵列都重復即可:
| id_num | id_type | other columns... |
|---|---|---|
| 1 | A | … |
| 2 | B | … |
| 3 | B | … |
如果我們把上述表的id_num和id_type這兩列作為聯合主鍵,那么上面的3條記錄都是允許的,因為沒有兩列主鍵組合起來是相同的。
沒有必要的情況下,我們盡量不使用聯合主鍵,因為它給關系表帶來了復雜度的上升。
2.外鍵
在students表中,通過class_id的字段,可以把數據與另一張表關聯起來,這種列稱為外鍵。
創建外鍵
外鍵並不是通過列名實現的,而是通過定義外鍵約束實現的:
ALTER TABLE students
ADD CONSTRAINT fk_class_id
FOREIGN KEY (class_id)
REFERENCES classes (id);
其中,外鍵約束的名稱fk_class_id可以任意,FOREIGN KEY (class_id)指定了class_id作為外鍵,REFERENCES classes (id)指定了這個外鍵將關聯到classes表的id列(即classes表的主鍵)。
通過定義外鍵約束,關系數據庫可以保證無法插入無效的數據。即如果classes表不存在id=99的記錄,students表就無法插入class_id=99的記錄。
由於外鍵約束會降低數據庫的性能,大部分互聯網應用程序為了追求速度,並不設置外鍵約束,而是僅靠應用程序自身來保證邏輯的正確性。這種情況下,class_id僅僅是一個普通的列,只是它起到了外鍵的作用而已。
刪除外鍵
ALTER TABLE students
DROP FOREIGN KEY fk_class_id;
注意:刪除外鍵約束並沒有刪除外鍵這一列。刪除列是通過DROP COLUMN ...實現的。
多對多
通過一個表的外鍵關聯到另一個表,我們可以定義出一對多關系。有些時候,還需要定義“多對多”關系。例如,一個老師可以對應多個班級,一個班級也可以對應多個老師,因此,班級表和老師表存在多對多關系。
多對多關系實際上是通過兩個一對多關系實現的,即通過一個中間表,關聯兩個一對多關系,就形成了多對多關系。
一對一
一對一關系是指,一個表的記錄對應到另一個表的唯一一個記錄。
例如,students表的每個學生可以有自己的聯系方式,如果把聯系方式存入另一個表contacts,我們就可以得到一個“一對一”關系:
| id | student_id | mobile |
|---|---|---|
| 1 | 1 | 135xxxx6300 |
| 2 | 2 | 138xxxx2209 |
| 3 | 5 | 139xxxx8086 |
那為啥不給students表增加一個mobile列,這樣就能合二為一了?
如果業務允許,完全可以把兩個表合為一個表。但是,有些時候,如果某個學生沒有手機號,那么,contacts表就不存在對應的記錄。實際上,一對一關系准確地說,是contacts表一對一對應students表。
還有一些應用會把一個大表拆成兩個一對一的表,目的是把經常讀取和不經常讀取的字段分開,以獲得更高的性能。例如,把一個大的用戶表分拆為用戶基本信息表user_info和用戶詳細信息表user_profiles,大部分時候,只需要查詢user_info表,並不需要查詢user_profiles表,這樣就提高了查詢速度。
3.索引
例如:
對於students表:
| id | class_id | name | gender | score |
|---|---|---|---|---|
| 1 | 1 | 小明 | M | 90 |
| 2 | 1 | 小紅 | F | 95 |
| 3 | 1 | 小軍 | M | 88 |
如果要經常根據score列進行查詢,就可以對score列創建索引:
ALTER TABLE students
ADD INDEX idx_score (score);
使用ADD INDEX idx_score (score)就創建了一個名稱為idx_score,使用列score的索引。索引名稱是任意的,索引如果有多列,可以在括號里依次寫上,例如:
ALTER TABLE students
ADD INDEX idx_name_score (name, score);
索引的效率取決於索引列的值是否散列,即該列的值如果越互不相同,那么索引效率越高。反過來,如果記錄的列存在大量相同的值,例如gender列,大約一半的記錄值是M,另一半是F,因此,對該列創建索引就沒有意義。
可以對一張表創建多個索引。索引的優點是提高了查詢效率,缺點是在插入、更新和刪除記錄時,需要同時修改索引,因此,索引越多,插入、更新和刪除記錄的速度就越慢。
對於主鍵,關系數據庫會自動對其創建主鍵索引。使用主鍵索引的效率是最高的,因為主鍵會保證絕對唯一。
唯一索引
在設計關系數據表的時候,看上去唯一的列,例如身份證號、郵箱地址等,因為他們具有業務含義,因此不宜作為主鍵。
但是,這些列根據業務要求,又具有唯一性約束:即不能出現兩條記錄存儲了同一個身份證號。這個時候,就可以給該列添加一個唯一索引。例如,我們假設students表的name不能重復:
ALTER TABLE students
ADD UNIQUE INDEX uni_name (name);
通過UNIQUE關鍵字我們就添加了一個唯一索引。
也可以只對某一列添加一個唯一約束而不創建唯一索引:
ALTER TABLE students
ADD CONSTRAINT uni_name UNIQUE (name);
