是否使用外來鍵約束
【強制】不得使用外來鍵與級聯,一切外來鍵概念必須在應用層解決.-《阿里Java規範》
首先外來鍵(Foreign Key)是什麼東西
使用方案
假設有一個score表 id是自增id,score是分數,student_id是學號。
另一個student表,id是自增id,name是名字,student_id是學號。
那麼設計這個的時候就希望有一個關聯關係,讓score的student_id指向student表的student_id,存在一個學生對應多個成績的關係。所以我可以使用以下SQL語句
ALTER TABLE score
ADD CONSTRAINT FOREIGN KEY (student_id)
REFERENCES student(student_id);
建立一個外來鍵索引完成這個規則
完成後的表關係如下
外來鍵原理
被指向的欄位,具有唯一性
可以保證成績欄位的一致性,即每一次插入一個score資料,首先要檢測是否student表存在這個id,保證一致性
如果在外來鍵型別上使用CASCADE
,則會保證在做更新和刪除sutudent表的student_id時,觸發一次級聯操作,會同步更新score表的student_id或者刪除student_id.
外來鍵型別RESTRICT
也同樣會做一次檢測,但不會做級聯操作,而是直接拒絕操作。
場景思考
知道外來鍵是什麼後,我們來思考一個場景:
現在有一個電商系統,使用者有一個賬戶id,商品有一個商品id,這兩個欄位和訂單繫結,此時訂單id和賬戶表ID構成一個外來鍵關係,同時和商品表id也構成一個外來鍵關係,那麼我每次生成一筆訂單,就需要向另外兩張表查詢檢測一次資料,那麼就存在幾個問題:
- 增刪改觸發這個查詢操作的效能消耗,伺服器系統是否允許
- 在其他表查詢會上需要對其他表做一個內部鎖,是否存在高併發死鎖情況
- 資料一致性全部交給資料庫伺服器,資料庫伺服器是否能夠承受
這些問題在網際網路公司會顯得格外嚴重,因為訪問流量大的時候以上問題基本上是完全無法得到MySQL系統本身解決的
同時在做分庫分表設計的時候,外來鍵約束就會顯得格外離譜。
同時MySQL系統的外來鍵設計是背離部分SQL標準的
引用自部落格園Eden: (https://www.cnblogs.com/discuss/articles/1862244.html)
對SQL標準的背離:如果ON UPDATE CASCADE或ON UPDATE SET NULL遞迴更新相同的表,之前在級聯過程中該表一被更新過,它就象RESTRICT一樣動作。這意味著你不能使用自引用ON UPDATE CASCADE或者ON UPDATE SET NULL操作。這將阻止級聯更新導致的無限迴圈。另一方面,一個自引用的ON DELETE SET NULL是有可能的,就像一個自引用ON DELETE CASCADE一樣。級聯操作不可以被巢狀超過15層深。
對SQL標準的背離: 類似一般的MySQL,在一個插入,刪除或更新許多行的SQL語句內,InnoDB逐行檢查UNIQUE和FOREIGN KEY約束。按照SQL的標準,預設的行為應被延遲檢查,即約束僅在整個SQL語句被處理之後才被檢查。直到InnoDB實現延遲的約束檢查之前,一些事情是不可能的,比如刪除一個通過外來鍵參考到自身的記錄。
處理
因為以上問題,我們通常在建模時隱性設計外來鍵約束,實際實現採用業務邏輯模擬外來鍵的方式處理,這樣可以解決把一致性全部放在DBA上的效能問題,同時我們可以採用允許髒資料存在,然後定時資料清理的方案去保證資料處理的分時效能,避免高峰處理。
這樣的好處:
- 解決效能問題
- 增加了可擴充套件性,框架遷移不用在資料庫系統內部實現邏輯約束
- 分庫分表的時候方便
- 不會在DB層面造成死鎖
反推:是否可以使用外來鍵
我覺得在部分業務場景下是可以考慮使用的,回到最開始的例子,教務系統的成績模組重要的點不再是效能問題,而是高可靠,因為對學校來說,系統存在以下特點:
- 資料量較少,一個學校學生最多不超過10萬人,通常在5000-50000這個區間內,對DB來說這是一個很小的資料量
- 資料不容許出錯,因為成績和學生的人身利益直接掛鉤
- 能夠進行資料修改操作的使用者極少,只有教務處錄入成績的老師。
- 如果放在業務部分,如果出現student表student_id在第一次被刪除後,未清理score表資料,這個student_id短時間內被再次使用,而沒有做資料清理,就容易出現成績複用錯誤,諸如此類
所以 不得使用外來鍵與級聯,一切外來鍵概念必須在應用層解決。 大部分情況下正確,但同樣我認為需要分業務場景解決,並不能一竿子打死。