最近有一個需求,對資料的即時性要求比較高,之前尋找過一些記憶體資料庫,首先將收費的產品先排除掉,然後再排除一些嵌入式產品,最終留下兩個產品:
1:Mysql記憶體引擎;
2:基於記憶體檔案對應的文檔資料庫Mongodb;
針對以上兩種產品,mysql記憶體引擎有如下缺點是我們放棄的理由:
1:資料量比較大的情況,比之innodb引擎優勢並不明顯,資料小的時候採用B-Tree索引結構效能還是可以接受的。
2:記憶體引擎對於持久化是非常吃力的,如果大量資料操作均在記憶體引擎中完成,那麼就需要我們來做資料的持久化,無論是複製訂閱者式還是程式方式均存在比較大的困難。
3:記憶體引擎所佔用的資料空間比較大,特別是預設的hash結構,同資料量的資料是sql server的6倍,B-Tree結構未做具體比較。
選擇Mongodb的理由:
1:自身具備資料的持久化;
2:對於資料的插入效能優越,特別是採用不傳回值的模式,理論上來講是一種非同步作業;
3:由於文檔儲存是k-value方式,所以對於單條記錄的查詢操作效能不用考慮;
4:對於水平擴充,資料的主從備份均非常容易。
使用Mongodb過程中遇到過的問題:
1:由於儲存的內容完全由用戶端決定,於是對於一些對欄位做更新的操作,比如sql 中的set count=count+1;存在一個並發更新問題,當兩個線程同時需要對count欄位加1時,兩個線程同時查詢到的count內容為1,當線程A完成加1後,資料庫中實際已經變成2,此時線程B對count進行加1,此時也是2,於時原來是3的內容,最終會變成2。主要原因就在於更新欄位時並不會在服務端進行,欄位的內容完全是由用戶端決定,即更新時並不會像sql server一樣,在服務端變數基礎上再做操作。
解決方案:對記錄增加自訂的鎖標識,嘗試在記錄上增加鎖,如果加鎖成功,然後再查詢,如果鎖標識證明是當前線程才進行更新,否則迴圈嘗試加鎖。這裡有發生死結的可能,為此在加鎖標記時,可以增加一個鎖時間,當超過鎖時間後,自動解鎖,這樣可以避免死結。
2:資料的插入,主要有兩種模式,一種是不返回處理結果,一種是返回處理結果,如果我們不太關心返回結果,那麼可以獲得最高插入效能,對於一些關注處理結果的業務情境,就需要採取返回結果方式。
3:最好自訂主鍵,不採用預設的_id值,我們處理一條學生記錄,學生記錄是唯一的,比如學生ID,如果我們採用預設_id值,那麼在插入資料時也會存在並發問題:兩個線程同時處理學生A,當時查詢時探索資料庫中沒有學生A的記錄,於時就進行插入,由於預設的_id值是自動產生,類似guid或者是自增id,說的通俗點就是會產生一個不重複的key,此時就會插入學生A記錄兩次,如果我們選用學生ID做為主鍵,就可以避免此種情況,當第二次嘗試插入時,系統會拋出主鍵重複的異常。
為什麼需要對mongodb驅動重新封裝?
其實無論是samus驅動還是官方驅動,其實功能都各有鞦韆,samus驅動對資料操作進行了Linq封裝,即我們在操作List時,完全可以採用類似Linq一樣的文法,這樣可以使我們的學習成本降低,官方驅動的特點是針對資料處理有傳回值。我們的需求是即需要操作傳回值也需要Linq封裝,於時我找到了老趙寫的easyMongo,但在它的基本上做了一小部分取捨,取捨內容如下:
1:去掉了如下邏輯,原意是將大多數操作均放在同一串連中處理,但不滿足我們的需求;我們直接對外提供一次性執行方法
InsertOnSubmit,UpdateOnSubmit,DeleteOnSubmit
public void SubmitChanges()
{
using (this.Database.Server.RequestStart(this.Database))
{
this.DeleteEntities();
this.UpdateEntites();
this.InsertEntities();
}
}
2:對上面所提到的insert,update ,delete要求有傳回值,對於insert提供兩種模式,一類是需要傳回值一類則不需要。特別是像更新和刪除,我們是非常有必要知道它的處理結果,如果不關心結果,我認為可以是一些資料準確性要求不高的情境。
public EInsertstatus InsertOnSubmit(TEntity entity)
{
if (entity == null) throw new ArgumentNullException();
return this.InsertEntities(entity,ESafeMode.False);
}
public EInsertstatus InsertOnSubmit(TEntity entity, ESafeMode safeMode)
{
if (entity == null) throw new ArgumentNullException();
return this.InsertEntities(entity, safeMode);
}
public void InsertBatchOnSubmit(List<TEntity> entity)
{
if (entity == null) throw new ArgumentNullException();
this.InsertBatchEntities(entity);
}
public bool UpdateOnSubmit(TEntity entity)
{
if (entity == null) throw new ArgumentNullException();
return this.UpdateEntites(entity);
}
public bool DeleteOnSubmit(TEntity entity)
{
if (entity == null) throw new ArgumentNullException();
return this.DeleteEntities(entity);
}
3:對查詢的修改,在將實體映射為mongo文檔時,需要對我們自訂的實體提供一個Map,這個Map主要是為了標識哪個是主鍵,哪些欄位是可以被Linq識別的欄位,比如我們要按學生ID查詢,就需要在Map中增加此欄位,樣本如下:
public class AggregateCompanyRecruitStudentInfoMap : EntityMap<AggregateCompanyRecruitStudentInfo>
{
public AggregateCompanyRecruitStudentInfoMap()
{
Collection("AggregateCompanyRecruitStudentInfo");
Property(n => n.OrganizationID).Identity();
Property(n => n.LockTime);
Property(n => n.LockFiled);
}
}
但源碼中的查詢,如果沒有提供查詢運算式,則預設只返回Map中提到的欄位,對其它實體欄位則不處理,為此修改如下:即沒有查詢運算式的情況返回完整文檔。
FieldsDocument fieldsDoc = null;
if (null != selector)
{
fieldsDoc = mapper.GetFields(selector);
}
4:去掉原有複雜的更新邏輯,刪除對我們沒用的狀態跟蹤,要的就是簡單直接,過於複雜的邏輯不利於開發。
Mymongo的一個簡化結構圖分享給大家:
說明:最後歡迎大家交流mongodb,文中如有不對的地方,希望批評指正,大家共同進步。