系統(tǒng)高吞吐量下的數(shù)據(jù)庫重復寫入問題分析解決
問題分析
為了提高系統(tǒng)的吞吐量,很多環(huán)節(jié)下對于數(shù)據(jù)庫的寫入是多線程,甚至是多進程的。為了保證寫入成功,在很多情況下需要多次重試。這就會帶來一個問題,數(shù)據(jù)重復,同一條數(shù)據(jù)會被記錄多次。有些情況下數(shù)據(jù)重復無傷大雅,但是很多情況系統(tǒng)是無法容忍數(shù)據(jù)重復的。因此這個問題需要解決。我個人覺得解決這一問題有兩個方向:第一,從數(shù)據(jù)庫上保證數(shù)據(jù)不重復,第二,從程序上保證數(shù)據(jù)不重復。
數(shù)據(jù)庫上解決
主要包括:主鍵,唯一性索引,甚至是臨時表。程序上解決無非就是要保證同步,這兩種方式能解決很多情況下的數(shù)據(jù)重復。但是有些情況可能比較棘手,使用者兩種方法有時并不能很好的解決,或實現(xiàn)起來比較復雜,如下面的數(shù)據(jù)
假如下表主要字段如下:
id studentId teacherId states
狀態(tài)(states)是有多種的(0,1,2,3),狀態(tài)可以轉換,但是狀態(tài)為,0的一個只能由一個,其它字段是可以重復的。這個其實就是保證某一種狀態(tài)下的數(shù)據(jù)不重復。
首先唯一性索引不能夠起作用,因為無法建立唯一性索引。主鍵也沒有效果,沒法通過這些字段生成可區(qū)分的id。所以這兩種方法都失效了。還有一種方法就是臨時表,在臨時表中插入一條能夠區(qū)分的數(shù)據(jù)(比如studentId,teacherId組合),無論是唯一索引還是主鍵都可以。寫入時首先寫臨時表,臨時表寫入成功則插入一條數(shù)據(jù),然后清空臨時表。這在嚴格保證數(shù)據(jù)不重復的情況下是能夠起作用的,但是比較繁瑣,需要處理一個臨時表。
另外的一個辦法就是根據(jù)我們的業(yè)務場景,在一個時間段內(nèi)(比如1分鐘)不會出現(xiàn)兩條相同數(shù)據(jù)寫入。這樣我們可以使用studentIdTeacherid加上精確到分鐘的時間來構成一個唯一id,重試時間間隔一般都極短(秒級別),這樣通過id來保證數(shù)據(jù)的唯一性。
從程序上保證數(shù)據(jù)不重復
如果從程序上來保證數(shù)據(jù)不重復,則更加復雜。第一種辦法是對寫入過程加鎖,確保只有一次寫入成功代碼如下(偽代碼):
Lock lock =new WriteLock();
public void write(Data data){
try{
if(lock.tryLock() ){
dataRepository.save(data);
if(dataRepository.numberOf(data)>1){//在寫入的時候檢測如果數(shù)據(jù)庫中有該數(shù)據(jù)拋出異常。再次保證數(shù)據(jù)不重復。
throw new DataException
}
}
}finally{
lock.unlock();
}
}這種方式首先會存在效率問題,所有的數(shù)據(jù)都要順序寫入會導入效率下降。我們只需要保證同一條數(shù)據(jù)不能并發(fā)寫入而不是不同數(shù)據(jù)。另外這種方式還會存在一定概率的重復,因為網(wǎng)絡問題和數(shù)據(jù)庫或ORM框架的緩存問題,會導致寫入檢測時并不能發(fā)現(xiàn)數(shù)據(jù)庫的更新。比如使用hibernate,兩次線程調(diào)用write方法會使用兩個session,從而使得第一次寫入緩存的數(shù)據(jù)無法在下一次操作中看到。在write方法中多次調(diào)用numberOf方法也是不起用的,由于session的緩存,后面的查詢會使用第一次的緩存結果,在第一次查詢后的數(shù)據(jù)庫變化,后面的查詢?nèi)匀粺o法覺察。
針對寫入效率低的問題,這里可以采用數(shù)據(jù)鎖,即通過一種方法比如使用data的hashcode來映射來獲取鎖,這樣不同的數(shù)據(jù)會獲取到不同的鎖,解決了所有數(shù)據(jù)的順序寫入問題。但是跟第一種情況一樣仍會存在數(shù)據(jù)重復問題。
對于多進程的情況,如微服務部署多個的情況,上面的同步會失效。對于這種情況唯一的解決辦法就是使用上面所說的數(shù)據(jù)庫同步或者構造一個環(huán)節(jié)鎖,類似于令牌的方法。只有獲取到令牌才有寫入資格,寫入成功后銷毀針對該數(shù)據(jù)的“令牌“。這種實現(xiàn)其實也比較簡單,如使用一個redis的hashmap,每次寫入首先獲取該數(shù)據(jù)對應的value,通過value來判斷該數(shù)據(jù)是否寫入,來保證數(shù)據(jù)不重復。
總結
上面無論哪種方法,針對本文所提到的數(shù)據(jù),解決重復問題都是不容易的。要么實現(xiàn)起來比較復雜,要么還是不能100%保證數(shù)據(jù)不重復。針對我們的業(yè)務場景:state為0的狀態(tài)下數(shù)據(jù)能有一條,且0的狀態(tài)不會持續(xù)太久,后面的操作會將其修改。而且多線程重試并不是每次都發(fā)生的,多進程情況下,每次數(shù)據(jù)寫入也多是只通過其中一個節(jié)點,針對這種情況其實可以采取更簡單的處理方式,不做過多的順序限制只在寫入時檢查數(shù)據(jù)庫,如果真的因為數(shù)據(jù)更新或者并發(fā)導致了多次寫入也沒有關系(這種情況很少),后續(xù)的操作時再刪除多寫入的數(shù)據(jù)。這是一種樂觀的處理方式,但是對于很多情況是可以解決數(shù)據(jù)重復問題的。
以上是我個人對于只有某種狀態(tài)的數(shù)據(jù)不能重復寫入問題的處理方法的思考。主要是從數(shù)據(jù)庫和程序上來控制及如果場景允許使用樂觀(后續(xù)補救)的方法。僅供參考,更多關于高吞吐量系統(tǒng)的數(shù)據(jù)庫重復寫入解決的資料請關注腳本之家其它相關文章!
相關文章
Windows下修改mysql的data文件夾存放位置的方法
這篇文章主要介紹了在Windows下修改mysql的data文件夾存放位置的方法,需要的朋友可以參考下2014-03-03
mysql 8.0 Windows zip包版本安裝詳細過程
這篇文章主要為大家詳細介紹了mysql 8.0 Windows zip包版本安裝詳細過程,以及密碼認證插件修改,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05
詳解mysql8.0創(chuàng)建用戶授予權限報錯解決方法
這篇文章主要介紹了詳解mysql8.0創(chuàng)建用戶授予權限報錯解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09
Linux下MySQL安裝配置 MySQL配置參數(shù)詳解
Linux下MySQL安裝配置 MySQL配置參數(shù)詳解,在linux下配置mysql的朋友可以參考下。2011-07-07
mysql中find_in_set()函數(shù)的使用詳解
這篇文章主要介紹了mysql中find_in_set()函數(shù)的使用,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-05-05
MySQL查詢表中某列字段相同的重復數(shù)據(jù)的方法
在數(shù)據(jù)庫查詢中,我們經(jīng)常需要查找表中某列中重復的數(shù)據(jù),本文將介紹如何使用 SQL 查詢語句來查找表中某列字段相同的重復數(shù)據(jù),幫助你快速定位重復數(shù)據(jù)問題并進行處理2023-08-08

