BUG现象
报错 The FMDatabase is currently in use
报错 Closing leaked statement
写在前面
之前写DEMO的时候其实存储数据用到的NSKeyedArchiver序列化和反序列化比较多。代码简单,存储到沙盒也比较安全。只是存储的模型数据需要实现NScoding协议。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSKeyedArchiver archiveRootObject:_dataSource toFile:MLBCacheHomeItemFilePath];
});
沙盒的安全性包装第三方软件不能拿到你的app沙盒中的数据,这也是为什么不要金山软件等清理垃圾数据的原因,它只是做了一个假动画,创造了相同数量的垃圾,然后再清除自己。
但之前有一个即时聊天的APP,会有大量的存数据,查消息的操作,性能需要数据库实现。
一开始我看着网上的一些例子基本是这么写的
#pragma mark - 接口
- (void)addPerson:(Person *)person{
[_db open];
NSNumber *maxID = @(0);
FMResultSet *res = [_db executeQuery:@"SELECT * FROM person "];
//获取数据库中最大的ID
while ([res next]) {
if ([maxID integerValue] < [[res stringForColumn:@"person_id"] integerValue]) {
maxID = @([[res stringForColumn:@"person_id"] integerValue] ) ;
}
}
maxID = @([maxID integerValue] + 1);
[_db executeUpdate:@"INSERT INTO person(person_id,person_name,person_age,person_number)VALUES(?,?,?,?)",maxID,person.name,@(person.age),@(person.number)];
[_db close];
}
确实在一开始的使用情况下,没有出现什么问题。
可是在我们的APP开始大量数据暴力测试的时候,高量的多线程快速写入查询下。出现了闪退,报错信息如下:
2017-03-01 15:12:51.987912 traffic.fm[4186:1323093] DB Query: INSERT INTO message(id,type,geo_location,geo_path,radius,expire_date,title,description,content,head_img_url,link_url,priority,pub_user_id,geo_hash,gmt_create,gmt_modified)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
2017-03-01 15:12:51.987987 traffic.fm[4186:1323093] Unknown error finalizing or resetting statement (10: disk I/O error)
2017-03-01 15:12:51.988047 traffic.fm[4186:1323093] DB Query: INSERT INTO message(id,type,geo_location,geo_path,radius,expire_date,title,description,content,head_img_url,link_url,priority,pub_user_id,geo_hash,gmt_create,gmt_modified)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
2017-03-01 16:26:42.012233 traffic.fm[2606:1262649] The FMDatabase is currently in use.
2017-03-01 16:26:42.012397 traffic.fm[2606:1262649] The FMDatabase is currently in use.
2017-03-01 16:26:42.012478 traffic.fm[2606:1262649] Closing leaked statement
原因是在多线程下,高并发下不同线程对数据的竞争,对一个数据进行重复写入修改。
需要使用队列实现才能避免这样的问题
正确的姿势 请躺好
// Created by frank on 16/11/23.
// Copyright © 2016年 TopHeavier. All rights reserved.
//
#import "MessageDatabase.h"
#import
#import "FMDatabaseQueue.h"
static MessageDatabase *_instance = nil;
@interface MessageDatabase()
@property (nonatomic, strong) FMDatabaseQueue *queue;
@end
@implementation MessageDatabase
+(instancetype)sharedDataBase{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
// 懒加载数据库队列
- (FMDatabaseQueue *)queue {
if (_queue == nil) {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 文件路径
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"model.sqlite"];
_queue = [FMDatabaseQueue databaseQueueWithPath:filePath];
}
return _queue;
}
-(void)initDataBase{
// 获得Documents目录路径
NSString * sql = @"CREATE TABLE IF NOT EXISTS 'message' ('id' VARCHAR(64) PRIMARY KEY ,'type' int(1),'geo_location' VARCHAR(64),'geo_path' VARCHAR(300),'radius'int(11),'expire_date' timestamp,'title' VARCHAR(64),'description' VARCHAR(200),'content' VARCHAR(1200),'head_img_url' VARCHAR(300),'link_url' VARCHAR(64),'priority' int(11),'pub_user_id' varchar(64),'geo_hash' VARCHAR(64),'gmt_create' timestamp,'gmt_modified' timestamp) ";
[self.queue inDatabase:^(FMDatabase *db) {
BOOL result = [db executeUpdate:sql withArgumentsInArray:nil];
if (result) {
NSLog(@"创建表格成功");
} else {
NSLog(@"创建表格失败");
}
}];
}
#pragma mark - 接口
-(void)addMessage:(MessageObject *)MessageObject{
[self.queue inDatabase:^(FMDatabase *db) {
BOOL result = [db executeUpdate:@"INSERT OR IGNORE INTO message(id,type,geo_location,geo_path,radius,expire_date,title,description,content,head_img_url,link_url,priority,pub_user_id,geo_hash,gmt_create,gmt_modified)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",MessageObject.uniqueId,MessageObject.type,MessageObject.geoLocation,MessageObject.geoPath,MessageObject.radius,MessageObject.expireDate,MessageObject.title,MessageObject.messageDescription,MessageObject.content,MessageObject.headImgUrl,MessageObject.linkUrl,MessageObject.priority,MessageObject.pubUserId,MessageObject.geoHash,MessageObject.gmtCreate,MessageObject.gmtModified];;
if (result) {
NSLog(@"插入数据成功");
} else {
NSLog(@"插入数据失败");
}
}];
}
-(NSMutableArray *)searchRecentMessage:(NSNumber *)num{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
[self.queue inDatabase:^(FMDatabase *db) {
FMResultSet * res = [db executeQuery:@"SELECT * FROM message order by gmt_create desc limit ? ",num];;
if (res) {
while ([res next]) {
}
} else {
NSLog(@"查询最近数据失败");
}
}];
NSEnumerator *enumerator = [dataArray reverseObjectEnumerator];
NSMutableArray *reseverArr= [[NSMutableArray alloc]initWithArray: [enumerator allObjects]];
return reseverArr;
}
-(NSMutableArray *)getMessageBefore:(NSDate *)startTime pageSize:(NSNumber *)pageSize page:(NSNumber *)page {
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
NSInteger offsetInterValue = pageSize.intValue-1;
NSNumber * offset = [NSNumber numberWithInteger:offsetInterValue];
[self.queue inDatabase:^(FMDatabase *db) {
FMResultSet *res = [db executeQuery:@"SELECT * FROM message where gmt_create <=? order by gmt_create asc limit ? offset ?",startTime,pageSize,offset];
if (res) {
while ([res next]) {
}
} else {
NSLog(@"查询之前消息失败");
}
}];
return dataArray;
}
-(void)deleteMessage:(MessageObject *)MessageObject{
[self.queue inDatabase:^(FMDatabase *db) {
BOOL result = [db executeUpdate:@"DELETE FROM message WHERE id = ?",MessageObject.uniqueId];
if (result) {
NSLog(@"删除数据成功");
} else {
NSLog(@"删除数据失败");
}
}];
}
- (NSMutableArray *)getAllMessage{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
[self.queue inDatabase:^(FMDatabase *db) {
FMResultSet *res = [db executeQuery:@"SELECT * FROM message"];
while ([res next]) {
}
}];
return dataArray;
}
@end
这个例子告诉我们有时候可能你觉得自己很水,但网上比你水的人更多。
用新东西的时候一定要了解其底层实现原理,要参考就参考官方库的例子。
做技术要自信。
官方地址