1. 程式人生 > >MySQL資料庫--外來鍵約束及外來鍵使用

MySQL資料庫--外來鍵約束及外來鍵使用

什麼是主鍵、外來鍵

關係型資料庫中的一條記錄中有若干個屬性,若其中某一個屬性組(注意是組)能唯一標識一條記錄,該屬性組就可以成為一個主鍵。

比如:

學生表(學號,姓名,性別,班級)
其中每個學生的學號是唯一的,學號就是一個主鍵

課程表(課程編號,課程名,學分)
其中課程編號是唯一的,課程編號就是一個主鍵

成績表(學號,課程號,成績)
成績表中單一一個屬性無法唯一標識一條記錄,學號和課程號的組合才可以唯一標識一條記錄,所以學號和課程號的屬性組是一個主鍵

成績表中的學號不是成績表的主鍵,但它和學生表中的學號相對應,並且學生表中的學號是學生表的主鍵,則稱成績表中的學號是學生表的外來鍵。

同理:成績表中的課程號是課程表的外來鍵。

定義主鍵和外來鍵主要是為了維護關係資料庫的完整性,總結一下:

1.主鍵是能確定一條記錄的唯一標識,比如,一條記錄包括身份正號,姓名,年齡。身份證號是唯一能確定你這個人的,其他都可能有重複,所以,身份證號是主鍵。

2.外來鍵用於與另一張表的關聯。是能確定另一張表記錄的欄位,用於保持資料的一致性。比如,A表中的一個欄位,是B表的主鍵,那他就可以是A表的外來鍵。

主鍵、外來鍵和索引的區別

主鍵 外來鍵 索引
定義 唯一標識一條記錄,不能有重複的,不允許為NULL 表的外來鍵是另一表的主鍵, 外來鍵可以有重複的, 可以是NULL 沒有重複值,可以為NULL(會使索引無效)
作用 用來保證資料完整性 用來和其他表建立聯絡用的 提高查詢排序的速度
個數 主鍵只能有一個 一個表可以有多個外來鍵 一個表可以有多個惟一索引

外來鍵約束

在上面“什麼是主鍵、外來鍵” 一小節中,我給大家灌輸的思維是,學生表使用學號作為主鍵,課程表使用課程ID作為主鍵,成績表使用學號、課程ID作為聯合主鍵(聯合主鍵(使用組合索引進行替代)以後壓根就別用,主鍵的設計原則就是欄位數目越少越好),這樣就產成了一個問題,外來鍵的參考鍵必須是另一個表的主鍵嗎?

答案當然不是,但是參考鍵必須是唯一性索引

。主鍵約束和唯一性約束都是唯一性索引。

錯誤的設計方式—[1215] Cannot add foreign key constraint

出現這種問題的原因一般有兩個:

1.兩張表裡要設主鍵和外來鍵的欄位的資料型別或者資料長度不一樣。
2.某個表裡已經有記錄了。

我當時屬於第一個。

如何設計良好的資料庫主鍵

摘抄一位知乎使用者的回答:知乎連結—紀路

主鍵的話我的建議是自增整形,不要使用與業務相關的名字,僅用id即可,而效率問題都可以用索引來解決。因為主鍵的不可變的特性,如果選擇不慎,會在未來產生難以預期的問題。比如你用int型做文章的id,但是如果在未來某一天文章數超過了無符號整形的最大值,你將沒法將主鍵修改成bigint。或者為了給使用者起一個唯一id用了自增主鍵,但是如果未來有其他的專案使用者要合併進來,他也是這麼做的。這時候為了區分不同的專案可能要在這個使用者id前加一個字首,這時候也沒法修改主鍵的值。主鍵之所以叫做主鍵就是到什麼時候都不能改,所以最好的方案就是使用自增數字id做主鍵,並且不要給這個主鍵賦予一個業務相關的意義。

總結上面前輩的一句話就是,不要將表中與業務相關的欄位設定為主鍵,即使它可以唯一標識這一行,比如身份證號,學號等等,主鍵越沒有意義,說明主鍵設定的越好

主鍵、外來鍵的使用

建立表

就按照我們上面的例子來建立三張表吧:student、course、score表。

建立student表:

create table student
(
    pk_id bigint unsigned not null auto_increment primary key,
    uk_sno int(10) unsigned not null,
    name char(60) not null,
    sex char(10) not null,
    class char(60) not null,
    constraint uk_sno unique (sno)
)enige = InnoDB, charset = utf8
;

建立course表:

create table course
(
    pk_id bigint unsigned not null auto_increment primary key,
    uk_course_id int(10) unsigned not null,
    course_name char(30) not null,
    credit int not null,
    constraint uk_course_id unique (course_id)
)enige = InnoDB, charset=utf8
;

建立score表:

create table score
(
    pk_id bigint not null auto_increment primary key,
    fk_sno int(10) unsigned not null,
    fk_course_id int(10) unsigned not null,
    result int not null,
    constraint fk_sno foreign key (fk_sno) references <databasename>.student (sno),
    constraint fk_course_id foreign key (fk_course_id) references <databasename>.course (course_id)
)enige = InnoDB, charset=utf8
;

值得一說的是,建立外來鍵的時候也會自動建立普通索引,所以fk_sno、fk_course_id其實是兩個普通索引的名稱。

對於使用IDEA的同學,我們會發現在設定外來鍵的時候還有Update rule 和 Delete rule規則,對於這兩個選項的解釋,我們下面再說。

這裡寫圖片描述

外來鍵的使用–更新與刪除

表已經建立成功,現在我們插入資料:
student表:

INSERT INTO student(uk_sno, name, sex, class) VALUES(123456, "spider_hgyi", "male", "cs");

crouse表:

INSERT INTO course(uk_course_id, course_name, credit) VALUES(1, "csapp", 10);

score表:

INSERT INTO score(fk_sno, fk_course_id, result) VALUES(123456, 1, 100);

好了,現在三個表裡都已經有了資料,現在我們嘗試更新學生表中學號的資訊:

UPDATE student SET uk_sno=12345678 WHERE uk_sno=123456;

MySQL報錯:

(1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`bookmanager`.`score`, CONSTRAINT `fk_sno` FOREIGN KEY (`fk_sno`) REFERENCES `student` (`uk_sno`))')

看看錯誤告訴我們什麼:不能刪除或更新這一行,存在外來鍵約束,score表中的fk_sno列是當前要更新的uk_sno的外來鍵,也就是說,你要更新學生表中的學號,但是成績表中的學號是你的外來鍵,你不能不管它呀,刪除也是同理。

要怎麼解決?

還記得剛才我貼的那張IDEA的圖片嗎?那兩個規則就可以幫助我們解決這個問題。

級聯刪除與更新

我們在更新與刪除時遇到的外來鍵約束解決方案分別對應設定Update rule與Delete rule。有如下四個選項:

1.CASCADE:從父表刪除或更新且自動刪除或更新子表中匹配的行。
2.SET NULL:從父表刪除或更新行,並設定子表中的外來鍵列為NULL。如果使用該選項,必須保證子表列沒有指定NOT NULL。
3.RESTRICT:拒絕對父表的刪除或更新操作。
4.NO ACTION:標準SQL的關鍵字,在MySQL中與RESTRICT相同。

可以看到我在建立外來鍵的時候選擇的是NO ACTION,也就是第四個選項。我們只需要選擇CASCADE就可以啦。具體效果就不進行演示了。

如果你不用IDEA也沒關係,接下來我給出SQL語句的實現(重新建立score表):

create table score
(
    pk_id bigint not null auto_increment primary key,
    fk_sno int(10) unsigned not null,
    fk_course_id int(10) unsigned not null,
    result int not null,
    constraint fk_sno foreign key (fk_sno) references <databasename>.student (sno) on update cascade on delete cascade,
    constraint fk_course_id foreign key (fk_course_id) references <databasename>.course (course_id) on update cascade on delete cascade
)enige = InnoDB, charset=utf8
;

補充

博主在學習阿里的Java開發手冊時,他們對於外來鍵與級聯是這樣描述的:

【強制】不得使用外來鍵與級聯,一切外來鍵概念必須在應用層解決。

說明:以學生和成績的關係為例,學生表中的 student _ id 是主鍵,那麼成績表中的 student _ id則為外來鍵。如果更新學生表中的 student _ id ,同時觸發成績表中的 student _ id 更新,即為級聯更新。外來鍵與級聯更新適用於單機低併發,不適合分散式、高併發叢集 ; 級聯更新是強阻塞,存在資料庫更新風暴的風險;外來鍵影響資料庫的插入速度。

本來我是打算以後在腦海中拋棄外來鍵與級聯這部分知識的,但經過學長的敲打,不得不說我對阿里的盲目崇拜。

外來鍵約束、級聯更新與刪除對於開發者是非常有用的,它確保了資料刪除與更新的完整性。至於阿里所說的影響效能,學長反問我:“你的應用有多少人在用?阿里的應用有多少人在用?”。

說這這些話的原因也是這次提醒我在軟體開發的過程中應想好受眾的大小,靈活運用所學的知識,不能盲目追求課本以及參考資料。

參考資料

阿里巴巴Java開發手冊–MySQL資料庫