iOS FMDB遷移到WCDB

移動端的數據庫,除了使用"SQLite"這個共識,基本各自爲政。git

iOS這邊以前使用的是基於SQLite封裝的FMDB。一開始使用並沒有問題。但在長期的使用中反映出,有性能瓶頸,好比說某個用戶長期未登陸,在登陸時收到大量消息,因爲FMDB不支持多線程的寫操做,會致使寫入很慢。github

遇到性能瓶頸後咱們開始尋找FMDB的替代品,就是WCDB,微信開源官方移動端數據庫組件。進入咱們的實現。依託微信的用戶量和對數據庫的依賴,WCDB已經處理了不少坑點和瓶頸,開源1年多,不斷地迭代功能,完善文檔。同時WCDB在Github的wiki上提供了專門的教程,幫助使用FMDB的開發者進行遷移。sql

性能對比

對於已經上線運行的項目,解決性能瓶頸會是一個常見的遷移理由。相較於FMDB直白的封裝,WCDB上到OC層的ORM,下到SQLite源碼,都作了各種性能優化。 爲了驗證優化效果,微信提供benchmark,並將性能測試結果和測試代碼上傳到了Github。同時,benchmark中也加入了FMDB的測試代碼,用於橫向比較。 如下性能測試均爲WAL模式、緩存大小2000字節、頁大小4 kb:數據庫

PRAGMA cache_size=-2000
PRAGMA page_size=4096
PRAGMA journal_mode=WAL
複製代碼

測試數據均爲含有一個整型和一個二進制數據的表:CREATE TABLE benchmark(key INTEGER, value BLOB),二進制數據長度爲100字節。緩存

  • 讀操做性能測試
  • 寫操做性能測試
  • 批量寫操做性能測試 (事務)
    對於讀操做,SQLite速度很快,所以封裝層的消耗佔比較多。FMDB只作了最簡單的封裝, 而WCDB還包括ORM、WINQ等操做,所以執行的指令會比FMDB多,從而致使性能劣於FMDB 5%。 而寫操做一般是性能的瓶頸,WCDB對其作了許多針對性的優化,使得寫操做和批量寫操做的性能分別優於FMDB 28% 和 180%。
  • 多線程讀併發性能測試
  • 多線程讀寫併發性能測試
  • 多線程寫併發性能測試
    在多線程讀操做的測試中,WCDB多線程併發的優點,將讀操做的性能劣勢拉了回來,使得最終結果與FMDB基本持平,而多線程讀寫操做性能則優於FMDB 62% 。 在多線程寫操做的測試中,FMDB直接返回錯誤SQLITE_BUSY,沒法完成。
  • 初始化性能測試
    SQLite鏈接的初始化速度會隨着數據庫內表的數量增長而逐漸上升,WCDB也針對這個場景作了優化。相較於沒有優化的FMDB,WCDB 有107% 的性能優點。

平滑遷移

文件格式

因爲FMDB和WCDB都基於SQLite,所以二者在數據庫的文件格式上一致。用FMDB建立、操做的數據庫,能夠直接經過WCDB打開、使用。所以開發者無需作額外的數據遷移。性能優化

表結構

WCDB提供了ORM的功能,將類的屬性綁定到數據庫表的字段。在平常實踐中,類的屬性名和表的字段名一般不一致。所以,WCDB提供了WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)宏,用於映射屬性名。 對於 表:CREATE TABLE message (db_id INTEGER, db_content TEXT) 類:bash

//Message.h
@interface Message : NSObject

@property int localID;

@property(retain) NSString *content;

@end

//Message.mm
@implementation Message

@end
複製代碼

這裏表字段都加了"db_"的前綴,而且使用了不同的字段名。經過WCDB的ORM,能夠映射爲微信

//Message.h
@interface Message : NSObject <WCTTableCoding>

@property int localID;
@property(retain) NSString *content;
WCDB_PROPERTY(localID)
WCDB_PROPERTY(content)

@end
//Message.mm
@implementation Message

WCDB_IMPLEMENTATION(Message)
WCDB_SYNTHESIZE_COLUMN(Message, localID, "db_id")
WCDB_SYNTHESIZE_COLUMN(Message, content, "db_content")

@end
複製代碼

經過WCDB_SYNTHESIZE_COLUMN宏映射後,WCDB一樣能兼容FMDB的表結構,開發者也不須要作數據遷移。因爲WCDB較之FMDB性能上有着較大提高,遷移起來因爲都是基於SQLite封裝,基本上都是兼容的,因此咱們決定使用WCDB。多線程

替換前代碼併發

+ (BOOL)insertGroupInfoData:(KitGroupInfoData *)infoData{
    BOOL result;
    //UPDATE
    KitGroupInfoData *groupInfoExit = [KitGroupInfoData getGroupInfoWithGroupId:infoData.groupId];
    if(groupInfoExit){//存在 updateHIYUNTON Group
        NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET groupName = ?, declared = ?, memberCount = ?,type = ?,isAnonymity = ?,isDiscuss = ? WHERE groupId = '%@' ",DATA_GROUPINFO_DBTABLE,infoData.groupId];
        result = [self updateTable:sql,!KCNSSTRING_ISEMPTY(infoData.groupName)? infoData.groupName:@"",!KCNSSTRING_ISEMPTY(infoData.declared)?infoData.declared:@"",[NSNumber numberWithInteger:infoData.memberCount],[NSNumber numberWithInteger:infoData.type],infoData.isAnonymity?@"1":@"0",infoData.isDiscuss?@"1":@"0"];
        return result;
    }else{//不存在insert
        NSString *sql = [NSString stringWithFormat:@"INSERT INTO %@ %@", DATA_GROUPINFO_DBTABLE, @"(groupId, groupName ,declared, createTime,owner,memberCount,type,isAnonymity,isDiscuss) VALUES (?, ?, ? , ?, ?, ?, ?,?,?)"];
        result = [self updateTable:sql,infoData.groupId, infoData.groupName,infoData.declared,infoData.createTime,infoData.owner,[NSNumber numberWithInteger:infoData.memberCount],[NSNumber numberWithInteger:infoData.type],infoData.isAnonymity?@"1":@"0",infoData.isDiscuss?@"1":@"0"];
        return result;
    }
    return YES;
}

+ (BOOL)upDateGroupInfo:(KitGroupInfoData *)groupInfo{
    __block BOOL result;//UPDATE
    [[[KitDataBaseManager sharedInstance] userDB_Queue] inDatabase:^(FMDatabase *db) {
        [db open];
        NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET groupName = ?, declared = ?,owner = ? WHERE groupId = '%@' ",DATA_GROUPINFO_DBTABLE,groupInfo.groupId];
        result = [db executeUpdate:sql,!KCNSSTRING_ISEMPTY(groupInfo.groupName)? groupInfo.groupName:@"",!KCNSSTRING_ISEMPTY(groupInfo.declared)?groupInfo.declared:@"",groupInfo.owner];
        [db close];
    }];
    return result;
}
複製代碼

替換後代碼

+ (BOOL)insertGroupInfoData:(KitGroupInfoData*)infoData{
    WCTDatabase *dataBase = [DataBaseManager sharedInstance].dataBase;
    return [dataBase insertOrReplaceObject:infoData into:DATA_GROUPINFO_DBTABLE];
}

+ (BOOL)upDateGroupInfo:(KitGroupInfoData *)groupInfo{
    WCTDatabase *dataBase = [DataBaseManager sharedInstance].dataBase;
    return [dataBase updateRowsInTable:DATA_GROUPINFO_DBTABLE onProperties:{KitGroupInfoData.groupName,KitGroupInfoData.declared,KitGroupInfoData.owner} withObject:groupInfo where:KitGroupInfoData.groupId == groupInfo.groupId];
}
複製代碼

總結

在使用了WCDB以後,代碼變得更加簡潔的同時,性能還獲得了提升,也不須要額外關注數據庫升級和多線程操做的問題。WCDB還提供了加密、統計、修復等功能供咱們使用。在解決性能瓶頸的同時,也解決以前使用FMDB的以下問題:

  1. 膠水代碼的問題 過去一個幾十行的函數,絕大部分都是拼接SQL、處理SQLite返回的空數據和錯誤碼之類的「裹腳布」代碼。並且這種代碼四處分佈,字裏行間都寫着"Copy & Paste"。 而如今ORM取出即爲對象無需拼接SQL。
  2. 效率問題 SQL基於字符串,命令行愛好者甚喜之。但對於基於現代IDE的移動開發者,倒是一大痛。字符串得不到任何編譯器的檢查,業務開發每每心中一團熱火,奮筆疾書下幾百行代碼,滿心歡喜點下Run後才發現:出錯了!靜心下來逐步看log、斷點後才發現,噢,SELECT敲成SLEECT了。改正,再等待編譯完成,此時已過去十幾分鍾,還談何效率?而如今經過ORM式可以經過IDE來檢測是否錯誤。
  3. SQL注入問題 SQL注入一般是利用SQL字符串拼接的特色,用一些特殊符號提早截斷SQL,達到執行其餘SQL的目的。試想這麼一段代碼
    這段封裝很簡單,就是將消息內容插入到數據庫中。假設對方發來這麼一條消息:"');DELETE FROM message;--",那麼這條SQL就會被截斷成三部分:
    它會在插入一條消息後,將表內的全部消息刪除。假若存在這樣的漏洞,後果將不堪設想。 其實反注入並不難,經過綁定參數或替換單引號爲雙單引號便可解決。但要在業務開發的過程時時刻刻警戒這樣的風險,並不現實,畢竟人總會犯錯的。
  4. 多端同步問題 因爲以前沒有安卓和iOS端通用的三方庫,你們也是各自爲政的,使用不一樣天然會出現各類各樣的不一樣步問題。在接入了WCDB後,咱們的數據庫方面便會統一

遷移過程當中遇到的問題

  1. 工程中有的類是SDK提供的僅有頭文件。因爲WCDB是基於對象綁定的,因此最後經過建立子類對象綁定WCDB作中間轉換實現。
  2. 項目中存了一個用於查詢運營商的db文件,只是單純的從db裏查詢,一樣因爲WCDB須要綁定沒法實現,因爲只是一個小查找,使用位置很少,最後使用了sqlite3原生方法。

參考資料

爲何要從FMDB遷移到WCDB?

github官方wiki

相關文章
相關標籤/搜索