2015-01-16

SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。SQLite第一个Alpha版本诞生于2000年5月。 至今已经有14个年头,SQLite也迎来了一个版本 SQLite 3已经发布。

SQLite的特性

  1. ACID事务
    ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability). 原子性意味着数据库中的事务执行是作为原子。即不可再分,整个语句要么执行,要么不执行。一致性指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNTS表中Tom和Jack的存款总额为2000元。事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

  2. 零配置 – 无需安装和管理配置

  3. 储存在单一磁盘文件中的一个完整的数据库

  4. 数据库文件可以在不同字节顺序的机器间自由的共享

  5. 支持数据库大小至2TB

  6. 足够小, 大致13万行C代码, 4.43M

  7. 比一些流行的数据库在大部分普通数据库操作要快

  8. 简单, 轻松的API

  9. 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定

  10. 良好注释的源代码, 并且有着90%以上的测试覆盖率

  11. 独立,没有额外依赖

  12. 源码完全的开源, 你可以用于任何用途, 包括出售它

  13. 支持多种开发语言,C, PHP, Perl, Java, C#,Python, Ruby

SQLite在iOS中的基本使用

在iOS开发中可以用一些SQLite数据库管理工具,例如SQLiteManager。

接下来就通过代码来讲述iOS中如何使用sqlite

sqlite.h文件的引入

首先是打开和关闭数据库,打开和创建数据库都是sqlite3_open函数,如果filename已经创建那就是打开。

NSString *filename;//数据库文件路径
sqlite3 *database; //sqlite3数据库句柄的指针
//打开数据库
- (int) open{
    int rc=sqlite3_open([filename UTF8String], &database);
    if (rc) {
        sqlite3_close(database);
        NSLog(@"open database failed");
    }
    return rc;
}
//关闭数据库
- (void) close{
    if (database!=NULL) {
        sqlite3_close(database);
    }
}

接下来插入、删除、更新都是用sqlite3_exec函数,记住执行语句,必须要先打开数据库,完成之后需要关闭数据库。

//执行 insert,update,delete 等非查询SQL语句
 - (int)executeNonQuery:(NSString *)sql error:(NSError **)error {
     int rc;
     char *errmsg;
     rc = [self open];
     if (rc) {
//错误处理
     if (error != NULL) {
         NSDictionary *eDict =
         [NSDictionary dictionaryWithObject:@"open database failed"
         forKey:NSLocalizedDescriptionKey];
         *error =
         [NSError errorWithDomain:kSqliteErrorDomain code:rc userInfo:eDict];
     }
     return rc;
 }
     rc = sqlite3_exec(database, [sql UTF8String], NULL, NULL, &errmsg);
     if (rc != SQLITE_OK) {
         if (error != NULL) {
             NSDictionary *eDict =
             [NSDictionary dictionaryWithObject:@"exec sql error"
             forKey:NSLocalizedDescriptionKey];
             *error =
             [NSError errorWithDomain:kSqliteErrorDomain code:rc userInfo:eDict];
         }
         NSLog(@"%s", errmsg);
         sqlite3_free(errmsg);
     }
     [self close];
     return rc;
 }

上面函数中sqlite3_free就是释放存放错误信息的内存空间。 查询操作会略显复杂,同样需要有开关数据库的操作,不过有一个准备结果集和最后释放结果集的操作,分别是sqlite3_prepare_v2和sqlite3_finalize,sqlite3_stmt就是结果集,下面就是具体操作。

[self open];
 // 查
 strsql = "select * from users";
 // SQLITE_API int sqlite3_prepare_v2(
 // sqlite3 *db, /* Database handle */
 // const char *zSql, /* SQL statement*/
 // int nByte, /* 结果集的最大长度。*/
 // sqlite3_stmt **ppStmt, /* OUT: 结果集 */
 // const char **pzTail /* OUT:指向结果集没有用到的内存部分的指针。 */
 // );
 sqlite3_stmt* rc;//陈述式句柄
 if (sqlite3_prepare_v2(db, strsql, -1, &rc, NULL)!=SQLITE_OK) {
 }
 // sqlite3_step讲结果集数据指针指向下一个元素。
 // 这个函数的返回值如果是SQLITE_ROW就表示我们的结果集里面有数据。
 // 否则我们的结果集就是空的。
 while (sqlite3_step(rc)==SQLITE_ROW) {
 // sqlite3_column系列函数。一般有两个输入参数。第一个是结果集指针,第二是数据所在列的序号。
 // 比如我们现在用的sqlite3_column_int和sqlite3_column_text。
    printf("id:%d | username:%s | password:%s \n",sqlite3_column_int(rc,               0),sqlite3_column_text(rc, 1),sqlite3_column_text(rc, 2));

 }
 // 查完后一定要释放结果集。
 sqlite3_finalize(rc);
 [self close];

数据库加密

免费版的SQLite有一个致命缺点:不支持加密。这就导致存储在SQLite中的数据可以被任何人用任何文本编辑器查看到。

对数据库加密的思路有两种:

1.将内容加密后再写入数据库

这种方式使用简单,在入库/出库只需要将字段做对应的加解密操作即可,一定程度上解决了将数据赤裸裸暴露的问题。 不过这种方式并不是彻底的加密,因为数据库的表结构等信息还是能被查看到。另外写入数据库的内容加密后,搜索也是个问题。

2.对数据库文件加密

将整个数据库整个文件加密,这种方式基本上能解决数据库的信息安全问题。目前已有的SQLite加密基本都是通过这种方式实现的。这里就介绍一个开源的加密工具SQLCipher,安装方法可以参照官网文档,https://www.zetetic.net/sqlcipher/ios-tutorial/,SQLCipher使用256-bit AES加密,由于其基于免费版的SQLite,主要的加密接口和SQLite是相同的,但也增加了一些自己的接口。

其实SQLite的两个加密函数使用起来非常的简单,下面分情况说明:

1 给一个未加密的数据库添加密码:如果想要添加密码,则可以在打开数据库文件之后,关闭数据库文件之前的任何时刻调用sqlite3_key函数即可,该函数有三个参数,其中第一个参数为数据库对象,第二个参数是要设定的密码,第三个是密码的长度。例如:sqlite3_key(db,”1q2w3e4r”,8); //给数据库设定密码1q2w3e4r

2 读取一个加密数据库中的数据:完成这个任务依然十分简单,你只需要在打开数据库之后,再次调用一下sqlite3_key函数即可,例如,数据库密码是123456时,你只需要在代码中加入sqlite3_key(db,”123456”,6);

3更改数据库密码:首先你需要使用当前的密码正确的打开数据库,之后你可以调用sqlite3_rekey(db,”112233”,6) 来更改数据库密码。

4 删除密码:也就是把数据库恢复到明文状态。这时你仍然只需要调用sqlite3_rekey函数,并且把该函数的第二个参数置为NULL或者”“,或者把第三个参数设为0。

事务操作

那么问题又来了,如果iOS的sqlite同时插入或者查询10000条数据,你该怎么办?

这里有三步要做,第一,减少开关数据库操作,插入10000条数据,不能开关10000次数据库,只能进行一次开关;

第二,就是不能放在主线程;

第三,最重要的一点就是加入事务操作。

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在sqlite插入数据的时候默认一条语句就是一个事务,有多少条数据就有多少次磁盘操作。所以10000次磁盘操作可能几分钟都做不完,这个时候需要把10000条语句都封装成一个事务。

下面就是开始事务和提交事务的代码了

-(int)beginService{
     char *errmsg;
     int rc = sqlite3_exec(database, "BEGIN transaction", NULL, NULL, &errmsg);
     return rc;
 }
-(int)commitService{
     char *errmsg;
     int rc = sqlite3_exec(database, "COMMIT transaction", NULL, NULL, &errmsg);
     return rc;
}

接下来就把三个操作合并

-(int)addModelsTest:(NSArray *)models error:(NSError **) error{
     char *errmsg;
     __block NSMutableArray *sqls=[NSMutableArray array];
     __block NoticeModel *aModel=[[NoticeModel alloc] init];
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         for (int i=0; i<100000; i++) {
             aModel=[models objectAtIndex:0];
             NSString *sql=[NSString stringWithFormat:@"insert into notices     values('%lf','%d','%@','%@','%@','%d','%d','%d','%d','%@')",aModel.myID,aModel.news_id,aModel.news_title,aModel.content,aModel.pic,aModel.sort,aModel.record_status,aModel.counter,aModel.suid,aModel.publish_time];
             [sqls addObject:sql];
         }
         int r1=[self open];
         [self beginService];
         int rc;
         int i;
         for (i=0; i<100000; i++) {
             rc=sqlite3_exec(database, [[sqls objectAtIndex:i] UTF8String], NULL, NULL, &errmsg);
         }
         [self commitService];
         [self close];
         if (i ==100000) {
             dispatch_async(dispatch_get_main_queue(), ^{
                 NSLog(@"call back, the data is: %@", i);
             });
         } else {
             NSLog(@"error when download:%@", error);
         }
     });
     return 0;
 }