為什麼很多mysql課程不推薦用物理外鍵?
剛好今晚有師弟問我這個問題,就來搜搜。以前我對這個也是蠻困惑的。這一年來對這個問題感受比較深。
用外鍵的好處我就不多說了,既然是關係型資料庫,外鍵的約束為我們保證了數據主從關係和產生的先後關係,級聯操作為我們的update和delete帶來了不少方便。但成本是有的,你要權衡你是不是想付出這些代價。成本參考以下幾點:
1.外鍵的性能問題
我剛寫了一些,然後發現有人寫的更好而且簡潔,就引用吧:@mysqlops
為何說外鍵有性能問題:
1.資料庫需要維護外鍵的內部管理;
2.外鍵等於把數據的一致性事務實現,全部交給資料庫伺服器完成;
3.有了外鍵,當做一些涉及外鍵欄位的增,刪,更新操作之後,需要觸發相關操作去檢查,而不得不消耗資源;
4.外鍵還會因為需要請求對其他表內部加鎖而容易出現死鎖情況;
作者:mysqlops
鏈接:https://www.zhihu.com/question/19600081/answer/13295957
來源:知乎著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
更多的性能討論見下方
2.mysql的外鍵設計問題
雖然很多人都不推薦你在關係型資料庫使用外鍵。 但你更多聽到的是mysql的,而不是SQLserver或者其他。比較公認的是,他的外鍵設計得的確不是很好,限制多功能不強大等,以innodb 為例子,包含但不限於以下幾點(我認為比較嚴重的)
- 所有tables必須是InnoDB型,它們不能是臨時表。
- 在引用表中,必須有一個索引,外鍵列以同樣的順序被列在其中作為第一列。這樣一個索引如果不存在,它必須在引用表裡被自動創建。
- 不支持對外鍵列的索引前綴。這樣的後果之一是BLOB和TEXT列不被包括在一個外鍵中,這是因為對這些列的索引必須總是包含一個前綴長度
- InnoDB不對那些外鍵或包含NULL列的被引用鍵值檢查外鍵約束
詳細參考:mysql的外鍵約束 - Johney - 博客園
印象中輪子哥 @vczh 在回答一個跟存儲過程有關的問題提到過,其他資料庫不建議使用存儲過程那是因為他們設計的不好,銀行內不都比較推薦甚至要求sqlserver使用存儲過程么(你沒騙我吧)。反正我以前也是用sqlserver的,我覺得是挺好的。
3.不使用外鍵我們也有好的解決方案
外鍵是個好東西,他為選擇了關係型資料庫的我們做了約束和級聯做了保障。但不使用物理外鍵的我們也有方案去實現我們的邏輯外鍵,並保證他正確運行。
資料庫上的一個策略:可以選擇大多數情況下我們只更新不刪除,也就是邏輯刪,不再使用的歷史數據定期歸檔來減少壓力。
代碼上的各種設計和限制:對錶範圍的操作許可權,開啟事務去處理邏輯,有需要進行非同步操作來提高性能的我們設計補償機制去彌補,等等。
有人問原本在物理外鍵的開銷,在程序上不也有開銷嗎。但是這樣我們對優化性能的方式也靈活了,剛剛說的非同步處理就是一種。視具體情況而定,如果設計的好,有時候某些臟數據你不是非得立刻刪除他,甚至不是非得刪除他。對於正確性&>性能的說法,如果邏輯複雜到一定程度,物理外鍵一定能給你提供正確性嗎?這個可以討論討論。
4.外鍵對拓展性的限制和影響
計劃趕不上變化,外鍵的主從關係是定的,然後你會因為這個做很多事情,但是萬一哪天主鍵所在表就見鬼去了呢?萬一哪天你發現外鍵表不是非得跟人家的主鍵掛上關係呢?就我經歷過的來看,這種情況並不少見,尤其是資料庫設計者水平不夠高的情況下。
另一個看法比較主觀,就是你讓資料庫去幫你管外鍵了,你平時寫程序的時候就真的很思路清晰嗎?因為某些原因(比如你想要的關係資料庫不支持,mysql經常),有些地方你就不能設計外鍵了,到時候一有級聯更新的需要時,一部分你靠物理外鍵,一部分你還得靠自己,我覺得還不如全靠代碼邏輯去保證。即使你對業務理解深刻,對外鍵也掌握的透徹,你也不太希望老是你管一部分他管一部分吧?
看完你是不是覺得 ?乛?乛? 外鍵什麼的,不存在的。其實如果系統的十分定位明確,對性能要求又不高的話,使用物理外鍵開發起來是蠻快蠻好咯。
最後,來多點傳送門,因為老覺得想探討一個問題在一個帖子里能看到的太窄了。
大家設計資料庫時使用外鍵嗎? - 知乎
資料庫刪除記錄,是直接物理刪除好,還是偽刪除好? - 知乎
在資料庫中具體創建表結構的時候,需要創建外鍵嗎? - 知乎
SQL Server 和 Oracle 以及 MySQL 有哪些區別?
其他的知乎上好像還有好幾個類似問題,不過沒什麼人答,大家怎麼老是問人家問過的啊。。。
因為mysql資料庫設計得不好,用資料庫的外鍵有問題。以及,近年來互聯網的發展,弱化資料庫,可以容忍錯誤數據錯誤也成為潮流。犧牲正確性換取效率,成為潮流。DBA的水平越來也差也成為事實。
別說mysql了,其他資料庫也都不用外鍵了。犧牲掉數據的正確性被認為是沒有任何問題的。哪怕銀行的系統也是一樣。至於業務上如何保證關鍵數據(錢)是對的?好幾個系統互相對賬唄。
錯了,修複數據嘛。只要錯得不離譜就行。用外鍵也不能保證不出業務錯誤。很多時候外鍵的級聯更新或者刪除造成了性能災難。只能級聯禁止,這樣造成了開發效率的下降。沒有開發效率,如何有時間修復bug和優化系統呢?
外鍵會和觸發器一樣變成小眾和適用場景及其少的遺留技術。MySQL默認的存儲引擎InnoDB支持外鍵,如果一定要用外鍵,那就要求用InnoDB引擎.
CREATE TABLE `score` (
`student_id` int(10) unsigned DEFAULT NULL,
`course_id` int(10) unsigned DEFAULT NULL,
`score` int(3) unsigned NOT NULL,
UNIQUE KEY `uniq_stu_cou` (`student_id`,`course_id`),
KEY `idx_stu` (`student_id`),
KEY `idx_cou` (`course_id`),
CONSTRAINT `score_ibfk_1` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT `score_ibfk_2` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
外鍵約束有下面幾種:
ON DELETE CASCADE 級聯刪除(刪除主鍵表student表裡記錄時,外鍵表score里對應的記錄會跟著刪除,默認)
ON UPDATE CASCADE 級聯更新(更新主鍵表student表裡記錄時,外鍵表score里對應的記錄會跟著更新)
ON DELETE RESTRICT 限制刪除(刪除主鍵表student表裡記錄時,如果外鍵表score里存在對應的記錄時會被拒絕)
ON UPDATE RESTRICT 限制更新(更新主鍵表student表裡記錄時,如果外鍵表score里存在對應的記錄時會被拒絕)
ON DELETE CASCADE 和 ON DELETE RESTRICT 是衝突的,不能同時使用.
ON UPDATE CASCADE 和 ON UPDATE RESTRICT 也是如此.
刪除和更新受到限制約束(RESTRICT)時,需要先刪除外鍵表score里的記錄,再刪除主鍵表student里的記錄.
外鍵約束下,每次INSERT/UPDATE/DELETE寫操作都會給資料庫帶來額外的邏輯壓力.
如果CASCADE 這種"級聯"類型的外鍵約束太多,你刪除(更新)一條記錄系統會自動刪掉(更新)其他相關外鍵記錄,但可能有一些記錄你不一定要刪除(更新).
如果RESTRICT這種"限制"類型的外鍵約束太多,你刪除(更新)一條記錄就得先手動刪掉(更新)其他相關外鍵記錄,是不是顯得很麻煩.
插入一個主鍵表中沒有的主鍵編號到外鍵表中,因為外鍵約束會導致出錯.
另外,數據量很大時,如果有外鍵約束,那分表操作就不好搞.
總而言之,外鍵約束像一把雙刃劍,用不到位反而會傷到自己,把簡單問題複雜化.
不只是外鍵。
Mysql里有很多東西都不適用在互聯網的場景下。
包括但不限於:
1。視圖
2。外鍵
3。存儲過程
4。事務
5。觸發器
還有被限制的:
1。聯表查詢
2。Select *
3。Group By
4。其他一些DB的函數
很多時候,要想簡單,快速,好擴展的解決問題,必須要有克制。
select id from tables where field =? order by field limit 1,10;
這種語句幾乎是我們對DB的唯一期望。
1。單表查詢
2。Java中拼接和處理數據
3。盡量多用緩存,防止請求穿透DB
常常考慮DB設計的幾個原則就是:
1。表的數據量會有多大?
2。並發請求會有多少?
3。更新的頻率會是什麼樣?
4。如果分庫的話,怎麼處理?
所以DB通常被弱化為持久化,持久化的意思就是數據要落地,不至於丟。
而緩存才是網站扛並發量的利器。
如果覺得得Mysql的索引還是太慢,對於緩存的使用又太麻煩。
那麼就是ElasticSearch+MongoDB
ES替代DB的索引,做搜索。
MongoDB做Key-Value存儲。
這種比較麻煩一點的就是索引要提前規劃好。否則重建索引的時間挺長的。
因為外鍵是一種「約束」。
這個約束的存在,會保證表間數據的關係「始終完整」。
而日常需求中,通常保證「最終完整」就可以了,甚至可以容忍一些「最終不完整」。
所以放棄外鍵的根本原因是——我們不需要這個約束。
如果使用外鍵,給自己找麻煩不說,還會帶來額外的性能損失。
同樣做為約束的主鍵,就是十分必要的存在。否則需要應用層達成數據唯一性是很麻煩的。
看你要在哪一層面維護模型完整性。我們暫時也沒有用到。但是不應該一概而論,簡單的說外鍵沒有用。
現代程序設計中受大數據影響,資料庫職責就是存儲數據,取數據,其他的事務,視圖,外鍵等會影響性能,負載大了很容易就拖死。
複雜業務結構一般都是用程序,結構設計,Redis等來代替,這樣就可以分散資料庫壓力…
只要你能保證伺服器進程就只有一個,同時並發量也低,那自然愛怎麼做怎麼做。對於不重要的程序,數據出了翔就拔網線自己修嘛,沒什麼大不了的(逃
其實不光是mysql,從DBA的角度出發,我也不建議在oracle里使用外鍵。
外鍵的效果完全可以通過業務邏輯來保證,但是它帶來的一系列性能問題卻很難有辦法解決。
使用外鍵的目的是保證數據的完整性,但是這樣會帶來額外的性能上的開銷。替代的做法是可以在應用程序中完成這一步,並且可以通過非同步處理增加效率。其次外鍵在水平分表和分庫的情況下就無法使用了。
數量級不夠沒有sharding的系統,最好還是用外鍵約束來保護數據,你不能指望程序猿寫的代碼都是高質量的數據訪問。
大數據量的分散式系統最好別用,拆表拆到死,但也要用別的辦法保證數據完整性才好。
我認為主要還是性能和並發鎖的問題
如果完全按範式設計,一條交易記錄,一個帖子,一篇文章,這種屬性比較多的,如果有好幾個列都有外鍵,關聯的東西太多了,性能是個問題。鎖的東西也多。好多參數表作為父表,本身還有好多別的屬性,你這動一下就都給鎖了,系統的並發度就嚴重下降了
外鍵是數據的一種約束方式,直接做在數據持久層。問題是,第一需不需要那麼強的約束,第二硬要做可以在應用層做
一個應用只能滿足CAP理論的2方面,大訪問量下當然放棄一致性是最划算的了
禁止使用外鍵
外鍵會導致表與表之間耦合,update與delete操作都會涉及相關聯的表,十分影響sql 的性能,甚至會造成死鎖。
在高並發情況下容易造成資料庫性能,外鍵與級聯更新適用於單機低並發,不適合分散式、高並發集群;級聯更新是強阻塞,存在資料庫更新風暴的風險;外鍵影響資料庫的插入速度。
高並發業務解放資料庫CPU,將計算轉移到服務層,並發量大的情況下,這些功能很可能將資料庫拖死,業務邏輯放到服務層具備更好的擴展性,能夠輕易實現「增機器就加性能」。讓資料庫只做擅長的事。
我所碰到的項目,幾乎是業務非常複雜,客戶需求非常變態而又沒有多少用戶量的項目。這時候這種傳統行業的項目可不是join 幾個表的問題了。加上快速開發,很多邏輯都依賴資料庫。沒有外鍵結束會增加很多代碼量。
其實大部分的項目並沒有什麼大數據量的,有些朋友說的連join 都不用的也很難做到。
所以該用就用。不用也要在代碼層做這個事,處女座表示臟數據受不了。
外包項目多數會用,因為能減少部分代碼量。公司產品具體看實際情況。
別說外鍵了,我們連聯表查詢都用不了。現在互聯網應對高並發,都對資料庫作了水平,垂直分庫,甚至分片,不同庫分布在不同的伺服器上,外鍵的約束,需要在不同的伺服器上傳遞大量數據來進行數據校驗,可以想像跨庫的約束和left join性能有多低
過去參與的項目中有的在資料庫中加了外鍵的強約束,有的則沒有,有的是在用PD設計時加上了主從引用關係,但在導入到庫中時去掉了外鍵的強約束,而只保留表結構。
表面看,資料庫中加上外鍵、非空、唯一等強約束會給程序編寫帶來麻煩,實則不然。約束是為了保證數據的合理性,如果資料庫設計的約束本身沒問題,那程序編寫中因約束而照成的不便就多是程序本身的不合理造成的。萬千世界,所有約束規範都是為了確保被約束規範的對象將所參與的事情做的更好,這種最終的好不是只針對某個對象的,而是針對所有參與其中的。假如某種規範不是這樣,那它本身就是不合理的;假如它是這樣的,而被約束規範的對象覺得在這種約束下做事情不便,那就是他自己做事方法的問題了,要由他自己根據規範修正。
所以強約束帶來的不便只幻象,這種不便不但不是壞事,還會倒逼程序開發更加趨於合理,修正開發中的錯誤。而不加強約束,程序可以肆無忌憚的對資料庫進行任何操作時,如果架構設計師及開發人員的經驗、技能較強還好,否則的話,程序很可能會因有意無意讀寫錯誤信息,造成針對資料庫的無效或錯誤操作,破壞掉數據的完整性、合理性。
有些情況下,加不加強約束設計者是決定不了的,比如公司有通用的許可權管理系統對所有子項目統一管理,那在設計子項目的資料庫時就不能添加和用戶相關的外鍵約束了。還有其它類似的情況:兩個項目之間有邏輯交差,但資料庫相互隔離,只能通過互相調用介面處理業務。這種情況下,如果想保證好數據的完整性準確性,只能由程序在業務邏輯層控制,必須設計開發足夠精密才能做好。
也許有點跑題,但是這是慘痛的經歷。
我是java技術棧,與國外團隊合作一個項目,拿到項目打開代碼翻了翻,我操,還在用hibernate。分分鐘部署跑起來,頁面夠炫,就是有點卡頓,但是還是很不錯啊。來來來,上點生產的數據,趕緊飛起來。尼瑪,oom了,什麼情況,才剛剛開始。把系統參數調到最大再來,還是oom!裝逼的時候到了,老子來給你優化一下,但是,並沒有搞定。。旁邊大神看我太痛苦,幫我看了看,問題解決。過了兩天,系統運行穩定了,上量。我草怎麼突然間這麼慢了,什麼情況,尼瑪,緩存流量被打爆。但是這次優化就比較痛苦了,因為是物理外鍵,國外的大兄弟偷懶,所有的外鍵載入都是eager模式。與國外的大兄弟開會,強烈要求換成mybatis,但是他說「全世界都在用hibernate,這不是問題,我給你買本hibernate你好好學學」。
外健開銷較大,不利於維護,而且在業務邏輯中維護就綽綽有餘了。
推薦閱讀: