面試官:使用 MySQL 時如果發(fā)現(xiàn)數(shù)據(jù)不一致了,可能是什么原因?
使用 MySQL 時有時候會遇到數(shù)據(jù)不一致的問題。那一般是什么原因會導致數(shù)據(jù)不一致呢?今天來聊一聊這個話題。
1.事務失效
1.1 單體事務
事務失效是造成數(shù)據(jù)不一致的一種常見情況。舉一個例子,客戶下訂單后,訂單表插入一條記錄,插入成功,賬戶表扣減金額,但扣減金額失敗。如下圖:
圖片
假如這兩張表在同一個庫,正常流程是回滾事務,但因為事務失效,導致訂單表插入成功,賬戶表扣減金額失敗,最終數(shù)據(jù)不一致。
可能造成事務失效的因素有很多,比如下面集中情況:
- Java 代碼開發(fā)中事務管理多數(shù)都由 Spring 來管理,但有些情況下 Spring 管理事務會失效,比如事務所在方法的定義上有 private、final、static,或者事務方法是一個內(nèi)部方法,因為 Spring 無法創(chuàng)建代理;
- 事務所在方法被內(nèi)部,Spring 也無法代理;
- 異常被捕獲后沒有拋出,或者拋出的異常不是 Spring 事務管理的異常,造成事務回滾失敗(Spring 默認回滾 RuntimeException)。
1.2 隔離級別
雖然事務沒有失效,但是因為事務隔離級別的問題,出現(xiàn)了臟讀、不可重復讀和幻讀等問題。比如讀取了其他未提交事務修改的數(shù)據(jù),如果那個事務最終回滾,讀取到的就是臟數(shù)據(jù),導致不一致。
要解決這個問題,就需要根據(jù)業(yè)務需求選擇合適的隔離級別。
1.3 分布式事務
還是用上面客戶下單的例子,如果系統(tǒng)是分布式架構(gòu),訂單服務和賬戶服務不在一個應用中,那賬戶服務扣減金額失敗了,訂單服務無法回滾。
這種情況可以引入分布式事務框架,比如 Seata 或者 RocketMQ 的分布式事務。
2.數(shù)據(jù)同步
數(shù)據(jù)同步失敗或未完成,也可能會造成數(shù)據(jù)不一致。
2.1 數(shù)據(jù)未同步
如下圖是一個讀寫分離的主備架構(gòu),應用寫主庫,然后從備庫中讀,如果數(shù)據(jù)未同步完成,就會造成問題。
圖片
對一些數(shù)據(jù)敏感的場景,可以選擇從主庫讀取數(shù)據(jù)。
2.2 數(shù)據(jù)同步失敗
再看下面雙主架構(gòu)的例子,假如我們有一張表,里面定義了自增主鍵 id,庫 M1 生成了主鍵 1、2、3,庫 M2 也生成了主鍵 1、2、3,這樣兩邊同步的時候因為主鍵沖突,同步失敗。
圖片
要解決這個問題,可以把兩個主庫的起始 id 值設(shè)置為不一樣(比如 M1 id 起始值設(shè)置成 1,把步長設(shè)置成1,M2 起始值設(shè)置成 2,把步長設(shè)置成 2)。如下圖:
圖片
3.系統(tǒng)故障
3.1 主庫奔潰
在主備架構(gòu)中,如果主庫發(fā)生奔潰,InnoDB 利用重做日志進行崩潰恢復,這時要保證已提交事務的數(shù)據(jù)持久化以及未提交事務的數(shù)據(jù)回滾。這個過程有可能會造成數(shù)據(jù)不一致。
3.2 主備切換
如果發(fā)生主備切換,備庫還有部分數(shù)據(jù)沒有同步到新主庫,這時應用來讀取數(shù)據(jù)就會出現(xiàn)數(shù)據(jù)不一致。
4.數(shù)據(jù)備份
在數(shù)據(jù)備份和恢復過程中,可能沒有停止業(yè)務,有部分業(yè)務還在寫入。備份完成后做數(shù)據(jù)恢復時,后寫入的業(yè)務數(shù)據(jù)就會丟失,造成數(shù)據(jù)不一致。
5.應用邏輯問題
應用程序的邏輯有問題,也可能會造成數(shù)據(jù)不一致。下面列舉兩個場景:
- 多個線程對同一條數(shù)據(jù)進行操作,在應用中沒有采用加鎖機制,導致出現(xiàn)數(shù)據(jù)不一致;
- 數(shù)據(jù)插入時因為邏輯問題造成了主鍵沖突,插入失敗。