iOS資料庫技術進階
資料庫的技術選型一直是個令人頭痛的問題,之前很長一段時間我都是使用的FMDB,做一些簡單的封裝。有使用過synchronized同步,也有用FMDB的DB Queue總之,差強人意。
缺點
FMDB只是將SQLite的C介面封裝成了ObjC介面,沒有做太多別的優化,即所謂的膠水程式碼(Glue Code)。使用過程需要用大量的程式碼拼接SQL、拼裝Object,每一個表都要寫一堆增刪改查,並不方便。
資料庫版本升級不友好,特別是第一次安裝時會把所有的資料庫升級程式碼都跑一遍。
優化
1.自動建表建庫:開發者可以便捷地定義資料庫表和索引,並且無須寫一坨膠水程式碼拼裝物件。
2.開發者無須拼接字串,即可完成SQL的條件、排序、過濾、更新等等語句。
3.多執行緒高併發:基本的增刪查改等介面都支援多執行緒訪問,開發者無需操心執行緒安全問題
4.多級快取,表資料,db資訊,佇列等等,都會快取。
以上說的幾點我會用微信讀書開源的GYDatacenter為例,在下面詳細介紹其原理和邏輯。
更加深入的優化:
加密:WCDB提供基於SQLCipher的資料庫加密。
損壞修復: WCDB內建了Repair Kit用於修復損壞的資料庫。
反注入: WCDB內建了對SQL隱碼攻擊的保護
騰訊前一陣開源的微信資料庫WCDB包括了上面的所有優化
我以前分享的SQLite大量資料表的優化
如果你對ObjC的Runtime不是很瞭解,建議你先閱讀我以前寫的:RunTime理解與實戰之後再閱讀下面的內容
GYDatacenter
1.可以用pod或者Carthage整合GYDatacenter。然後使你的模型類繼承GYModelObject:
@interface Employee : GYModelObject
@property (nonatomic, readonly, assign) NSInteger employeeId;
@property (nonatomic, readonly, strong) NSString *name;
@property (nonatomic, readonly, strong) NSDate *dateOfBirth;
@property (nonatomic, readonly, strong) Department *department;
@end
2.重寫父類的方法
+ (NSString *)dbName {
return @"GYDataCenterTests";
}
+ (NSString *)tableName {
return @"Employee";
}
+ (NSString *)primaryKey {
return @"employeeId";
}
+ (NSArray *)persistentProperties {
static NSArray *properties = nil;
if (!properties) {
properties = @[
@"employeeId",
@"name",
@"dateOfBirth",
@"department"
];
});
return properties;
}
3.然後你就可以像下面這樣儲存和查詢model類的資料了:
Employee *employee = ...
[employee save];
employee = [Employee objectForId:@1];
NSArray *employees = [Employee objectsWhere:@"WHERE employeeId < ?
ORDER BY employeeId" arguments:@[ @10 ]];
model的核心是實現GYModelObjectProtocol協議
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, GYCacheLevel) {
GYCacheLevelNoCache, //不快取
GYCacheLevelDefault, //記憶體不足優先清理
GYCacheLevelResident //常駐
};
@protocol GYModelObjectProtocol <NSObject>
@property (nonatomic, getter=isCacheHit, readonly) BOOL cacheHit;
@property (nonatomic, getter=isFault, readonly) BOOL fault;
@property (nonatomic, getter=isSaving, readonly) BOOL saving;
@property (nonatomic, getter=isDeleted, readonly) BOOL deleted;
+ (NSString *)dbName; //db名
+ (NSString *)tableName; //表名
+ (NSString *)primaryKey; //主鍵
+ (NSArray *)persistentProperties; //入表的列
+ (NSDictionary *)propertyTypes; //屬性型別
+ (NSDictionary *)propertyClasses; //屬性所在的類
+ (NSSet *)relationshipProperties; //關聯屬性
+ (GYCacheLevel)cacheLevel; //快取級別
+ (NSString *)fts; //虛表
@optional
+ (NSArray *)indices; //索引
+ (NSDictionary *)defaultValues; //列 預設值
+ (NSString *)tokenize; //虛表令牌
@end
自動建表建庫,更新表欄位
只要你定義好了你的模型類的屬性,當你準備對錶資料進行增刪改查操作的時候,你不需要手動的去建立表和資料庫,你應該呼叫統一的建表介面。
如果表已經建好了,當你增加表欄位(persistent properties),GYDataCenter也會自動給你更新。我畫了一個圖:
注意:⚠️However, GYDataCenter CANNOT delete or rename an existing column. If you plan to do so, you need to create a new table and migrate the data yourself.
GYDataCenter不能刪除和重新命名已經存在的列,如果你一定要這麼做,需要重新建一張表並migrate資料。
GYDataCenter還可以自動的管理索引(indices),快取(cacheLevel),事務等等。
以查詢為例:
NSArray *employees = [Employee objectsWhere:@"WHERE employeeId <
? ORDER BY employeeId" arguments:@[ @10 ]];
不需要為每一個表去寫查詢介面,只要在引數中寫好條件即可。開發者無須拼接字串,即可完成SQL的條件、排序、過濾、更新等等語句。
思考
NSArray *array = [DeptUser objectsWhere:
@",userinfo where deptUsers.uid = userinfo.uid" arguments:nil];
如果參試這樣寫聯合查詢,會產生一個錯誤!
原因是:查詢結果FMResultSet是按index解析通過KVC賦值modelClass的屬性。
你可以通過指定arguments或者呼叫指定的聯合查詢介面joinObjectsWithLeftProperties來解決這個問題
下面是我在其原始碼中寫了一個按欄位解析方式來驗證這個問題,有興趣的可以看一下。
- (void)setProperty:(NSString *)property ofObject:object withResultSet:(FMResultSet *)resultSet{
Class<GYModelObjectProtocol> modelClass = [object class];
GYPropertyType propertyType = [[[modelClass propertyTypes] objectForKey:property] unsignedIntegerValue];
Class propertyClass;
if (propertyType == GYPropertyTypeRelationship) {
propertyClass = [[modelClass propertyClasses] objectForKey:property];
propertyType = [[[propertyClass propertyTypes] objectForKey:[propertyClass primaryKey]] unsignedIntegerValue];
}
id value = nil;
if (![self needSerializationForType:propertyType]) {
if (propertyType == GYPropertyTypeDate) {
value = [resultSet dateForColumn:property];
} else {
value = [resultSet objectForColumnName:property];
}
} else {
NSData *data = [resultSet dataForColumn:property];
if (data.length) {
if (propertyType == GYPropertyTypeTransformable) {
Class propertyClass = [[modelClass propertyClasses] objectForKey:property];
value = [propertyClass reverseTransformedValue:data];
} else {
value = [self valueAfterDecodingData:data];
}
if (!value) {
NSAssert(NO, @"database=%@, table=%@, property=%@", [modelClass dbName], [modelClass tableName], property);
}
}
}
if ([value isKindOfClass:[NSNull class]]) {
value = nil;
}
if (propertyClass) {
id cache = [_cacheDelegate objectOfClass:propertyClass id:value];
if (!cache) {
cache = [[(Class)propertyClass alloc] init];
[cache setValue:value forKey:[propertyClass primaryKey]];
[cache setValue:@YES forKey:@"fault"];
[_cacheDelegate cacheObject:cache];
}
value = cache;
}
if (value) {
[object setValue:value forKey:property];
}
}
多執行緒高併發
多執行緒同步FMDB已經給我們提供了FMDatabaseQueue,我們可以進一步封裝為同步和非同步 。
- (void)asyncInDatabase:(void (^)(FMDatabase *db))block {
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDatabaseQueueSpecificKey);
FMDBRetain(self);
dispatch_block_t task = ^() {
FMDatabase *db = [self database];
block(db);
if ([db hasOpenResultSets]) {
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue asyncInDatabase:]");
#ifdef DEBUG
NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
NSLog(@"query: '%@'", [rs query]);
}
#endif
}
};
if (currentSyncQueue == self) {
task();
} else {
dispatch_async(_queue, task);
}
FMDBRelease(self);
}
- (void)syncInDatabase:(void (^)(FMDatabase *db))block {
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDatabaseQueueSpecificKey);
FMDBRetain(self);
dispatch_block_t task = ^() {
FMDatabase *db = [self database];
block(db);
if ([db hasOpenResultSets]) {
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue syncInDatabase:]");
#ifdef DEBUG
NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
NSLog(@"query: '%@'", [rs query]);
}
#endif
}
};
if (currentSyncQueue == self) {
task();
} else {
dispatch_sync(_queue, task);
}
FMDBRelease(self);
}
FMDatabaseQueue已經幫你解決了執行緒同步死鎖問題,並且並行佇列,可以高併發。微信讀書的開發團體之前也說過暫時並沒有遇到佇列瓶頸,相信FMDatabaseQueue還是值得信賴的。
dispatch_sync(queue,task) 會阻塞當前執行緒, 直到queue完成了你給的task。
快取
多級快取:我們應該在資料表(model),資料庫資訊,dbQueue,等建立多級快取。model用字典快取,其他可以用dispatch_get_specific或者執行時objc_setAssociatedObject關聯。
當然,快取必須是可控的。有一個全域性控制開關和清除機制,我們查詢時,如果model有快取就取快取,沒有就讀DB並新增快取。並且在更新表資料或者刪除時,更新快取。
注意⚠️ 預設屬性都是readonly,因為每一個資料表都會有一個快取。如果你需要改變model的屬性值,你必須加同步鎖?。或者像下面這樣:
@interface Employee : GYModelObject
@property (atomic, assign) NSInteger employeeId;
@property (atomic, strong) NSString *name;
@property (atomic, strong) NSDate *dateOfBirth;
@property (atomic, strong) Department *department;
@end
Relationship & Faulting
像前面的Employee類,Department類是它的一個屬性,這叫做Relationship(關聯)。需要在.m檔案中動態實現
@dynamic department;
//use
[employee save];
[employee.department save];
當你查詢Employee時,Department屬性僅僅是用Department表的主鍵當佔位符。當你employee.department.xx訪問department的屬性時,會自動動態的載入department的資訊,這叫做Faulting。減少您的應用程式使用的記憶體數量,提高查詢速度。
有興趣的可以研究騰訊前一陣開源的微信資料庫WCDB,或者等我下一次分享。
相關文章
- 資料庫審計技術進化資料庫
- 【資料庫】Redis進階篇資料庫Redis
- Airbnb資料工程師的進階指南:技術基礎AI工程師
- 簡單介紹資料庫技術發展階段!資料庫
- 4月22日丨【雲資料庫技術沙龍】技術進化,讓資料更智慧資料庫
- 向量資料庫技術全景資料庫
- 鵝廠資料庫的進階之路資料庫
- 分散式資料庫技術的演進和發展方向分散式資料庫
- GaussDB(for MySQL)雲原生資料庫技術演進和挑戰MySql資料庫
- Python 資料處理庫 pandas 進階教程Python
- 七、資料庫技術的發展及新技術資料庫
- 進階 Redis 技術與應用Redis
- 技術管理進階——如何面試面試
- 教你資料庫漏洞防護技術資料庫
- 分散式資料庫技術論壇分散式資料庫
- 技術管理進階——技術Leader如何拒絕業務方?
- 技術管理進階——管人還是管事?
- 技術管理進階——如何脫穎而出?
- 1269道Java技術答疑,阿里技術專家幫你Java技術進階Java阿里
- 騰訊雲資料庫伍鑫:MPP資料庫HTAP技術探索資料庫
- 資料庫連線池技術詳解資料庫
- 資料庫備份與恢復技術資料庫
- 聊聊Oracle的分散式資料庫技術Oracle分散式資料庫
- 解讀圖資料庫技術路線資料庫
- 《大型資料庫技術》MySQL管理維護資料庫MySql
- E6 資料庫分割槽技術資料庫
- openGauss持續聚焦資料庫根技術資料庫
- 開源資料庫大會技術分享資料庫
- 《MySQL 進階篇》十七:資料庫其他調優策略MySql資料庫
- 技術管理進階——技術總監的第一要務
- 從資料庫發展史看資料庫未來技術趨勢資料庫
- 技術管理進階——跨級管理/彙報
- 技術管理進階——如何覆盤總結
- 前端如何快速進階,突破技術瓶頸?前端
- 技術管理進階——什麼是公司文化
- 2021 年 -iOS 面試進階資料總結(備戰年後)iOS面試
- 2021年-iOS面試進階資料總結(備戰年後)iOS面試
- 【資料庫系統】資料庫系統概論====第十三章 資料庫技術發展資料庫
- 前沿分享|阿里雲資料庫高階技術專家 宋利兵:阿里雲企業級自治資料庫RDS詳解阿里資料庫