移動端數(shù)據(jù)庫新王者:realm

realm

介紹

realm是一個跨平臺移動數(shù)據(jù)庫引擎,支持iOS、OS X(Objective?C和Swift)以及Android。
2014年7月發(fā)布。由YCombinator孵化的創(chuàng)業(yè)團隊歷時幾年打造,是第一個專門針對移動平臺設計的數(shù)據(jù)庫。目標是取代SQLite。
為了徹底解決性能問題,核心數(shù)據(jù)引擎用C++打造,并不是建立在SQLite之上的ORM。如果對數(shù)據(jù)引擎實現(xiàn)想深入了解可以查看:Realm 核心數(shù)據(jù)庫引擎探秘。因此得到的收益就是比普通的ORM要快很多,甚至比單獨無封裝的SQLite還要快。
因為是ORM,本身在設計時也針對移動設備(iOS、Android),所以非常簡單易用,學習成本很低。

碾壓級性能

數(shù)據(jù)引自:introducing-realm
每秒能在20萬條數(shù)據(jù)中進行查詢后count的次數(shù)。realm每秒可以進行30.9次查詢后count。


在20萬條中進行一次遍歷查詢,數(shù)據(jù)和前面的count相似:realm一秒可以遍歷20萬條數(shù)據(jù)31次,而coredata只能進行兩次查詢。

這是在一次事務每秒插入數(shù)據(jù)的對比,realm每秒可以插入9.4萬條記錄,在這個比較里純SQLite的性能最好,每秒可以插入17.8萬條記錄。然而封裝了SQLite的FMDB的成績大概是realm的一半。

簡單易用

實例代碼語言是Objective?C。
Realm對象和其他對象沒有太大區(qū)別,只是需要繼承RLMObject

@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@end

Dog *mydog = [[Dog alloc] init];

存儲起來也非常簡單,獲取數(shù)據(jù)庫實例,在一個事務中進行寫入。

RLMRealm *realm = [RLMRealm defaultRealm];

[realm transactionWithBlock:^{
    [realm addObject:mydog];
}];

方便的查詢,可以在一個查詢結(jié)果中再進行查詢。查詢的條件有著豐富的支持。

RLMResults *r = [Dog objectsWhere:@"age > 8"];
 
// Queries are chainable
r = [r objectsWhere:@"name contains 'Rex' AND  name BEGINSWITH '大'"];

zero-copy和懶加載

在通常的數(shù)據(jù)庫中,數(shù)據(jù)大多數(shù)時間都靜靜地呆在硬盤當中。當你訪問 NSManagedObject 對象中的某個屬性的時候,Core Data 會將這個請求轉(zhuǎn)換為一組 SQL 語句,如果還未連接數(shù)據(jù)庫的話則創(chuàng)建一個數(shù)據(jù)庫連接,然后將這個 SQL 語句發(fā)送給硬盤,執(zhí)行檢索,從匹配檢索的結(jié)果中讀取所有的數(shù)據(jù),然后將它們放到內(nèi)存當中(也就是內(nèi)存分配)。然而,這時候你需要對其格式進行反序列化(deserialize),因為硬盤上存儲的格式不能直接在內(nèi)存中使用,這意味著你需要調(diào)整位,以便 CPU 能夠?qū)ζ溥M行處理。
然而Realm跳過了整個拷貝數(shù)據(jù)到內(nèi)存的過程,稱之為zero-copy。做到這點是因為文件始終是內(nèi)存映射的,無論文件是或否在內(nèi)存當中,你都能夠訪問文件的任何內(nèi)容。關于核心文件格式的重要一點就是,確保硬盤上的文件格式都是內(nèi)存可讀的,這樣就無需執(zhí)行任何反序列化操作了。
這樣就帶來了一個問題,難道數(shù)據(jù)全加載到內(nèi)存里了?所以這里懶加載應運而生,比如在查詢到一組數(shù)據(jù)后,只有當你真正訪問對象的時候才真正加載進來。

VS SQLite

SQLite第一個版本發(fā)布于2000年,至今已16年。以當今的角度來看,它的編程抽象程度非常低。業(yè)務上我們其實只想把這些對象存進去,可以查詢出來。
即便已經(jīng)是封裝過的FMDB,要寫這樣的代碼心里也依舊難受:

FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
if (![db open]) {
    [db release];
    return;
}

NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
                 "create table bulktest2 (id integer primary key autoincrement, y text);"
                 "create table bulktest3 (id integer primary key autoincrement, z text);"
                 "insert into bulktest1 (x) values ('XXX');"
                 "insert into bulktest2 (y) values ('YYY');"
                 "insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

sql = @"select count(*) as count from bulktest1;"
       "select count(*) as count from bulktest2;"
       "select count(*) as count from bulktest3;";

success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
    NSInteger count = [dictionary[@"count"] integerValue];
    XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
    return 0;
}];

[db close];

VS CoreData

詳細的比較推薦看這篇:CoreData VS Realm。
下面給出一個查詢的比較:

// Core Data
let fetchRequest = NSFetchRequest(entityName: "Specimen")
let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString)
fetchRequest.predicate = predicate
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
let error = NSError()
let results = managedObjectContext?.executeFetchRequest(fetchRequest, error:&error)

Realm則簡單的多:

// Realm
let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString);
let specimens = Specimen.objectsWithPredicate(predicate).arraySortedByProperty("name", ascending: true)

總結(jié)一下Realm對CoreData的優(yōu)勢:

不需要架構Context那種煩人的東西

CoreData 是一個博大精深的技術,不要妄想幾天之內(nèi)可以用的很溜。
不服看看這些書。

支持 NSPredicate

從 CoreData 轉(zhuǎn)過來并沒有太多的不適應。

CoreData多個持久化文件很麻煩,Realm輕松支持這個功能

劣勢:

是會增加應用大概1MB的體積。CoreData原生支持,不會增加App體積。

雖然看上去很厲害,但是這么新靠譜嗎

Realm大部分源碼公開在github上:realm。項目在新建不到兩年里,已經(jīng)得到開源社區(qū)大量關注:


官方也承諾會持續(xù)解決用戶反饋的各種問題。也可以直接在他們twitter上去@他們。

就算靠譜,有別人在用嗎

推薦閱讀這篇博客,作者介紹了他痛下決心拋棄CoreData后,如何安全遷移至Realm:《高速公路換輪胎——為遺留系統(tǒng)替換數(shù)據(jù)庫》。

在多年以前,人們做了個決策,用CoreData做本地存儲,替換掉NSUserDefaults。這之間的歷史已經(jīng)遠不可考,但自從我加入項目以來,整個團隊已經(jīng)被它高昂的學習曲線、復雜的數(shù)據(jù)Migration流程以及過時陳舊的設計折磨的苦不堪言。于是我們決心把CoreData換掉。

文/涼粉小刀(簡書作者)
原文鏈接:http://www.itdecent.cn/p/d684693f1d77
再看下SO的情況:


已經(jīng)有大概兩萬條相關結(jié)果,你不是一個人!

需要知道的一些問題

其實我自己覺得這些是可以接受的問題。技術很多時候就是權衡,為了達到一些目的,總是要犧牲掉一些東西。

  • 所有的存儲對象需要繼承RealmObject
    比如我現(xiàn)在的項目的數(shù)據(jù)從網(wǎng)絡請求回來都會繼承自己寫的一個方便解析的基類,在這里就需要做出一些適應。
    但是該問題在swift中是不存在的。因為swift是天生的面向協(xié)議編程范式。
  • 不能自定義getter、setter
    realm會自動生成getter、setter,如果自定義getter、setter存儲就會有影響。如果要規(guī)避這個問題,可以通過設置這個對象的忽略屬性。
    比如有個屬性id,需要自定義setter??梢栽趯ο髮傩岳锇裪d設置為忽略屬性,這樣realm就不會為它自動生成getter、setter,但是也不會把id存入數(shù)據(jù)庫。接著自定義一個用于存儲的屬性比如realm_id。在id的setter中可以把把值也賦給realm_id。
    這個問題在swift中也是不存在的,因為swfit中使用的是willset、didset這種通知機制。
  • 查詢的結(jié)果不是數(shù)組
    為了能夠支持查詢結(jié)果的鏈式查詢,realm自定義了一個集合類型。可以枚舉,但是不是熟悉的數(shù)組了。又因為realm的懶加載機制,所以不建議在數(shù)據(jù)層把這個枚舉轉(zhuǎn)成數(shù)組類型。這樣的缺點就是上層知道了數(shù)據(jù)的存儲邏輯。嚴格的說這里不應該讓上層知道。但是這樣設計也許是為了方便上層進行再次檢索,realm有著優(yōu)越的查詢性能。

歡迎關注我的微博:@沒故事的卓同學

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容