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 更新,即為級聯更新。外鍵與級聯更新適用於單機低並發,不適合分布式、高並發集群 ; 級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度。

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

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

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

 

參考資料
外鍵必須是另一個表的主鍵嗎

關於數據庫主鍵和外鍵(終於弄懂啦)

快速理解MySQL中主鍵與外鍵的實例教程

MySQL使用外鍵實現級聯刪除與更新的方法

阿里巴巴Java開發手冊–MySQL數據庫

https://blog.csdn.net/championhengyi/article/details/78559789


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM