在ios开发中,大家很可能会用到这样一个数据库封装:fmdb.
该封装相比coredata来说有他自己的优势:接口清晰,设计简单,符合规范,多线程情况下使用databasequeue来进行操作也很方便,还可以在其基础上再进行一些封装来方便项目的使用。
正是因为fmdb的简单性,所以很容易被误用。在我们的项目开发中就遇到了一例(我们项目中的代码进行了封装,我这里将其还原,写示例来作说明):
- [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
- [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
- [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
- [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
- [
queue
inDatabase:^(FMDatabase *db) {
// do work B
}];
- if (whoopsSomethingWrongHappened) {
- *rollback = YES; return;
- }
- // etc…
- [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];
- }];
注意第6行往下,是不是混入了什么奇怪的东西?
从这里看起来问题非常明显了,但实际项目中接口被进行了一些封装,所以很难一眼看出问题,这里的问题是这样的:
在queue的事务内部又嵌套使用了该queue去执行任务b,而作为一个串行化的队列来说必须要等该事务整个执行完毕才能执行任务b;此时任务b无法走下去,该事务也就无法执行完毕,导致了死锁。
用一个比喻来说这个问题:一个人出门把门锁上了,然后把钥匙从门缝又塞回到家里,这样他就无法再进入家门了。
这个误用其实很低级,而且从原理上讲任何串行队列里面串行任务嵌套执行都有问题。
另外,fmdb的官方文档也多次提醒避免嵌套使用,请大家写代码的时候一定要注意~