目錄
文章目錄
- 目錄
- 外鍵約束
- 外鍵關聯
- 外鍵的作用
- 外鍵的性能問題
- 是否使用外鍵?
- 使用外鍵的守則
- 互聯網應用應該盡量避免使用外鍵
- 在業務邏輯中模擬資料庫外鍵
外鍵約束
外鍵約束(Foreign key)是關系型資料庫中的 Table 的一個特殊欄位,經常與主鍵約束(Primary key)一起使用,對于兩個具有關聯關系的表而言,相關聯欄位中主鍵所在的表就是主表(父表),外鍵所在的表就是從表(子表),外鍵約束可以保證參考的完整性(Referential Integrity),
參考完整性是資料的屬性,如果資料擁有該屬性,那么資料中所有的參考都是合法的,在關系型資料庫的背景關系中,這就意味著關系型資料庫中參考另一個表中的值必須存在,
簡而言之,外鍵約束就是用來建立主表與從表的關聯關系,為兩個表的資料建立連接,約束兩個表中資料的一致性和完整性,
NOTE:一個 Table 可以有一個或多個外鍵,外鍵可以為空值,若不為空值,則每一個外鍵的值必須等于主表中主鍵的某個值,
外鍵關聯
所謂外鍵關聯,即:B 存在外鍵 b_f_k,以 A 表的 a_k 作為參照(References)列,則 A 為主表,B 為從表,
-
若 A、B 關聯了 on delete/update 等操作,則 A 中某記錄的更新或洗掉會聯動著 B 中外鍵與其關聯對應的記錄做更新或洗掉操作,
-
反之,B 怎樣變 A 不必跟隨變動,且 A 中必須事先存在 B 要插入的資料外鍵列的值,例如:B.bfk 作為外鍵參照 A.ak,則 B.bfk 插入的值必須是 A.ak 中已存在的,簡而言之,就是若 B 有以 A 作為參照的外鍵,則 B 中的此欄位的取值只能是 A 中存在的值,
外鍵的作用
外鍵用于支持關系型資料庫的 “參照完整性”,外鍵具有保持資料完整性和一致性的機制,對業務處理有著很好的校驗作用,
舉例說明:假設 Table user 的 Column user.id 為主鍵(Primary key),Table profile 的 Column profile.uid 為主鍵,以 user 為主表、profile 為關聯表、profile.uid 為外鍵(Foreign key)并將 user.id 作為參考(References),且聯動了洗掉/更新操作(on delete/update cascade),那么:
- 在 user 中洗掉 id 為 1 的記錄,會聯動洗掉 profile 中 uid 為 1 的記錄,
- 在 user 中更新 id 為 1 的記錄至 id 為 2,則 profile 中 uid 為 1 的記錄也會被聯動更新至 uid 為 2,
這樣即保持了資料的完整性,也保證了資料的一致性,而且這個作業都是交由 RDBMS 內部實作的觸發器來完成的,不需要額外的編碼,
外鍵的性能問題
外鍵的使用往往會帶來性能問題,因為:
- 資料庫需要維護外鍵的內部管理;
- 外鍵等于把資料的一致性事務實作,全部交給資料庫服務器完成;
- 涉及外鍵欄位的增,刪,更新操作,需要觸發相關操作去檢查,而不得不消耗資源;
- 外鍵還會因為需要請求對其他表內部加鎖而容易出現死鎖情況,
是否使用外鍵?
因為外鍵具有性能問題,所以是否采用外鍵需要考慮業務應用場景,以及開發成本:
-
互聯網行業應用不推薦使用外鍵:用戶量大,并發度高,為此,資料庫服務器很容易成為高并發訪問的性能瓶頸,尤其受 I/O 能力限制,且不能輕易地水平擴展,此場景中,應該把資料一致性的實作放到業務邏輯中,讓應用服務器來承擔這部分的功能和壓力,因為應用服務器可以輕松做到水平伸縮;
-
傳統行業可以考慮使用外鍵:因為軟體應用的人數是有限且可控的,資料庫服務器的資料量也一般不會超大,且活躍資料有限,該場景中使用外鍵可以降低開發成本,借助 RDBMS 自身的觸發器可以實作物體表與關聯表之間的資料一致性和更新,另外,使用外鍵還可以做到開發人員和資料庫設計人員(DBA)的分工,DBA 可以為程式員承擔更多的作業量;
使用外鍵的守則
- 主表必須已經存在于資料庫中,或者是當前正在創建的表,如果是后一種情況,則主表與從表是同一個表,這樣的表稱為自參照表,這種結構稱為自參照完整性,
- 必須為主表定義主鍵,
- 外鍵中列的數目必須和主表的主鍵中列的數目相同,
- 外鍵中列的資料型別必須和主表主鍵中對應列的資料型別相同,
互聯網應用應該盡量避免使用外鍵
不使用外鍵的原因其實很簡單,因為 MySQL、PostgreSQL 等關系型資料庫很難水平擴容,但是無狀態的服務往往都可以很容易地擴容,由于外鍵等特性需要資料庫執行額外的作業,而這些操作會占用資料庫的計算資源,所以我們可以將大部分的需求都遷移到無狀態的服務中完成以降低資料庫的作業負載,從而避免資料庫成為高并發性能的瓶頸,
另外,級聯洗掉的出發點是為了保證資料的完整性,但是在設計關系表之間的不同關系時,我們也需要注意級聯洗掉引起的資料大規模洗掉的問題,當客戶端想要在資料庫中洗掉 authos 表中的資料時,如果我們同時在 authors 和 posts 中指定了級聯洗掉的行為,那么資料庫會同時洗掉所有關聯的 posts 記錄以及與 posts 表關聯的 comments 資料,
這種涉及多級的級聯洗掉行為在資料量較小的資料庫中不會導致問題,但是在資料量較大的資料庫中洗掉關鍵資料可能會引起雪崩,一條記錄的洗掉可能會被放大到幾十倍甚至上百倍,這些對磁盤的隨機 I/O 會帶來巨大的開銷,是我們想要盡可能避免的情況,如果我們能夠較好地設計各個表之間的關系并且慎用 CASCADE 行為,這對于保證資料庫中資料的合法性有著很重要的意義,使用該特性可以避免資料庫中出現過期的、不合法的資料,但是在使用時也要合理預估可能造成的最壞情況,
在業務邏輯中模擬資料庫外鍵
想要在應用程式中模擬資料庫外鍵的功能其實比較容易,我們只需要遵循以下的幾個準則:
- 向表中插入資料或者修改表中的資料時,都應該執行額外的 SELECT 陳述句確保它參考的資料在資料庫中存在;
- 在洗掉資料之前需要執行額外的 SELECT 陳述句檢查是否存在當前記錄的參考;
需要注意的是為了保證一致性,我們需要在事務中執行上述的查詢和修改陳述句,這樣才能完整模擬外鍵的功能,例如:當我們向 posts 表中插入或者修改資料時,需要的處理相對比較簡單,我們只需要執行有限的 SELECT 陳述句并按照如下所示的模式執行對應的操作就可以了:
BEGIN
SELECT * FROM authors WHERE id = <post.author_id> FOR UPDATE;
-- INSERT INTO posts ... / UPDATE posts ...
END
但是如果我們要洗掉 authors 表中的資料,就需要查詢所有參考 authors 資料的表;如果有 10 個表都有指向 authors 表的外鍵,我們就需要在 10 個表中查詢是否存在對應的記錄,這個程序相對比較麻煩,不過也是為了實作完整性的必要代價,不過這種模擬外鍵方法其實遠比使用外鍵更消耗資源,它不僅需要查詢關聯資料,還要通過網路發送更多的資料包,
手動實作資料庫的級聯洗掉操作也是可行的,如果我們在一個事務中按照順序洗掉所有的資料,確實可以保證資料的一致性,但是這與外鍵的級聯洗掉功能沒有太大的區別,反而會有更差的表現,如果我們能夠接受在一個時間視窗內的資料不一致,就可以將一個大號的洗掉任務拆成多個子任務分批執行,降低對資料庫影響的峰值,
DELETE FROM posts WHERE author_id = 1 LIMIT 100;
DELETE FROM posts WHERE author_id = 1 LIMIT 100;
...
DELETE FROM authors WHERE id = 1;
注意,與資料庫外鍵的 CASCADE 相比,這種方式會帶來更大的額外開銷,只是我們能降低對資料庫性能的瞬時影響,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/164202.html
標籤:其他
上一篇:資料庫關系范式——第一范式、第二范式、第三范式、BC范式【通俗易懂,博主會講人話】
下一篇:2020-09-28課堂筆記
