SQLite小记

静以修身,俭以养德html

原文连接git

1、移动端数据库方案

一、关系型数据库
  • SQLite:轻量级的关系数据库, 占用资源很是少,目前普遍应用于Android、iOS等手机操做系统。iOS使用时SQLite,只须要加入libSQLite3.0.tbd依赖以及引入SQLite3.h头文件便可。
  • Apple内建的CoreData底层的持久化方式能够是SQLite数据库,也能够是XML文件、甚至是内存; 比较流行的第三方框架FMDB是对SQLite操做的封装
二、非关系数据库
  • Realm:适用于iOS (一样适用于Swift&Objective-C)和Android的跨平台移动数据库,是NoSQL框架,官方定位是取代SQLite。具体可参考Realm(Java)那些事github

  • Realm很是的特点是数据变动通知,查询,存储性能比SQLite好,可是体积大、存入Realm的对象必须继承RealmObject,侵入性强,Realm中存储对象不允跨线程访问sql

  • 非关系型数据库还有LevelDB、RocksDB数据库

三、其余
  • 16年左右,Realm兴起,部分客户端团队开始使用Realm,可是更多的团队仍是继续使用SQLite及其衍生方案;使用Realm并不是就必定Cool,使用SQLite并不是就必定Out,方案的选择应该是基于业务自己和团队的技术积累。
  • 在16年时候,微信分享了本身对优化SQLite的源码,具体可见微信iOS SQLite源码优化实践,随后推出了本身的数据库方案WCDB(基于SQLite)

2、SQLite的线程模式

一、三种线程模式
  • 单线程模式(Single-thread):全部互斥锁都被禁用,SQLite链接不能在多个线程中使用(多线程使用不安全)。
  • 多线程模式(Multi-thread):在多线程中使用单个数据库链接是不安全的,不然就是安全的 (不能在多个线程中共享数据库链接)
  • 串行模式(Serialized),是线程安全的(即便在多个线程中不加互斥的使用同一个数据库链接)。

说明:线程模式能够在编译时(经过源码编译SQLite库时)、启动时(使用SQLite的应用程序初始化时)或者运行时(建立数据库链接时)来指定。通常而言,运行时指定的模式将覆盖启动时的指定模式,启动时指定的模式将覆盖编译时指定的模式。可是,单线程模式一旦被指定,将没法被覆盖。默认的线程模式是串行模式。api

二、编译时选择线程模式
  • 经过定义SQLite_THREADSAFE宏来指定线程模式。若是没有指定,默认为串行模式。
//0:单线程模式;
//1:串行模式;
//2:多线程模式
复制代码
  • SQLite3_threadsafe()返回值能够肯定编译时指定的线程模式。若是指定了单线程模式,函数返回false。若是指定了串行或者多线程模式,函数返回true。
  • 因为SQLite3_threadsafe()函数要早于多线程模式以及启动时和运行时的模式选择,因此它既不能区别多线程模式和串行模式,也不能区别启动时和运行时的模式。
//FMDB 中代码
+ (BOOL)isSQLiteThreadSafe {
    // make sure to read the SQLite headers on this guy!
    return SQLite3_threadsafe() != 0;
}
复制代码
  • 若是编译时指定了单线程模式,那么临界互斥逻辑在构造时就被省略,所以也就没法在启动时或运行时指定串行模式或多线程模式。
三、启动时选择线程模式
  • 假如在编译时没有指定单线程模式,就能够在应用程序初始化时使用SQLite3_config()函数修改线程模式。缓存

    SQLite_CONFIG_SINGLETHREAD  //单线程模式
    SQLite_CONFIG_MULTITHREAD   //多线程模式
    SQLite_CONFIG_SERIALIZED    //串行模式
    复制代码
四、运行时选择线程模式
  • 若是没有在编译时 和 启动时指定为单线程模式,那么每一个数据库链接在建立时,可单独的被指定为多线程模式或者串行模式,可是不能指定为单线程模式
  • 若是在编译时或启动时指定为单线程模式,就没法在建立链接时指定多线程或者串行模式。
  • 建立链接时能够用SQLite3_open_v2()函数的第三个参数来指定线程模式。
SQLite_OPEN_NOMUTEX    //建立多线程模式的链接(没有指定单线程模式的状况下)
SQLite_OPEN_FULLMUTEX  //建立串行模式的链接
复制代码
五、模式的选择和处理

要保证数据库使用安全,通常能够采用以下几种模式安全

  • SQLite 采用单线程模型,用专门的线程(同时只能有一个任务执行访问) 进行访问
  • SQLite 采用多线程模型每一个线程都使用各自的数据库链接 (即 SQLite3 *
  • SQLite 采用串行模型,全部线程都公用同一个数据库链接。
六、SQLite使用建议

​ 写操做的并发性并很差,当多线程进行访问时实际上仍旧须要互相等待,而读操做所须要的 SHARED 锁是能够共享的,因此为了保证最高的并发性,推荐bash

  • 使用多线程模式
  • 使用 WAL 模式
  • 单线程写,多线程读 (各线程都持有本身对应的数据库链接)
  • 避免长时间事务
  • 缓存 SQLite3_prepare 编译结果
  • 多语句经过 BEGINCOMMIT 作显示事务,减小屡次的自动事务消耗

3、SQLite基础操做

一、基础概念
  • :是数据库中一个很是重要的对象,是其余对象的基础。根据信息的分类状况,一个数据库中可能包含若干个数据表
  • 字段:表的“列”称为“字段”,每一个字段包含某一专题的信息
  • 记录:是指对应于数据表中一行信息的一组完整的相关信息
  • iOS使用SQLite,须要引入libSQLite3.0.tbd框架,并引入<SQLite3.h>头文件
二、关键API-打开数据库
//打开数据库链接 定义
SQLite_API int SQLite3_open(
  const char *filename,   /* Database filename (UTF-8) */
  SQLite3 **ppDb          /* OUT: SQLite db handle */
);

//使用数据库链接
//db是SQLite3对象,SQLite3 *db = nil;
 SQLite3_open([sqlPath UTF8String], &db);   
 
//打开
int SQLite3_open_v2(
	const char *filename, /* Database filename (UTF-8) */
	SQLite3 **ppDb, /* OUT: SQLite db handle */
	int flags, /* Flags */
	const char *zVfs /* Name of VFS module to use */
);

复制代码
  • 参数1:数据库的路径(由于须要的是C语言的字符串,而不是NSString因此必须进行转换)微信

  • 参数2:SQLite的数据库的操做句柄(指向指针的指针)

三、关键API - 执行sql语句
//执行sql语句 定义
SQLite_API int SQLite3_exec(
  SQLite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);

//使用
 int result = SQLite3_exec(db, sql.UTF8String, nil, nil, nil);   
 if (result == SQLite_OK) {
     //exec ok
 } else {
     //exec failed
 }
复制代码
  • 参数1:SQLite3对象
  • 参数2:sql语句
  • 参数3:sql执行后回调函数
  • 参数4:回调函数的参数
  • 参数5:错误信息
四、关键API - 执行查询语句
//将sql文本转换成一个准备语句(prepared statement)对象,同时返回这个对象的指针,它实际上并不执行(evaluate)这个SQL语句,它仅仅为执行准备这个sql语句。
SQLite_API int SQLite3_prepare_v2(
  SQLite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  SQLite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);

//使用
result = SQLite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
if (result == SQLite_OK) {
     //exec ok
} else {
     //exec failed
}
复制代码
五、关键API - 关闭数据库
//关闭数据库 定义
SQLite_API int SQLite3_close(SQLite3*);

//使用
SQLite3_close(db);
复制代码

说明:具体API参考C-language Interface Specification for SQLite,FMDB中对SQLite3的操做作了很好的封装,具体可参考FMDB的FMDatabase文件

参考 SQLite线程模式探讨

4、FMDB

FMDB是iOS平台的SQLite数据库框架,iOS项目中使用十分普遍。

一、源码组成
  • FMDatabase : 对SQLite3的封装,能够看作是SQLite3数据库操做实例,经过它能够对SQLite3进行增删改查等等操做。
  • FMResultSet : FMDatabase执行查询以后的结果集。
  • FMDatabaseAdditions : FMDatabase的Extension,新增对查询结果只返回单个值的方法进行简化,对表、列是否存在,版本号,校验SQL等等功能。
  • FMDatabaseQueue : 使用GCD串行队列保证线程安全,全部的线程共用一个SQLite Handle(单句柄),在多线程并发时,可以使各个线程的数据库操做按顺序同步进行,但正是由于各线程同步进行,致使后来的线程会被阻塞较长时间,不管是读操做仍是写操做,都必须等待前面的线程执行完毕,使得性能没法获得更好的保障
  • FMDatabasePool : 使用任务池的形式,对多线程的操做提供支持。(不过官方对这种方式并不推荐使用,优先选择FMDatabaseQueue的方式)

说明:在FMDB中,SQLite运行在多线程模式,一个数据库链接在同一个时间只能在一个线程操做 ,应该是在编译时候肯定的,固然也能够在打开数据库链接时候,指定线程模式是 多线程或串行。

二、数据库建立和打开
  • FMDatabase经过一个 SQLite 数据库文件路径建立的,此路径能够是:

    一个文件的系统路径。磁盘中能够不存在此文件,由于若是不存在会自动为你建立。
    一个空的字符串 `@""`。会在临时位置建立一个空的数据库,当 `FMDatabase` 链接关闭时,该数据库会被删除。
    NULL`。会在内存中建立一个数据库,当 `FMDatabase` 链接关闭时,该数据库会被销毁。
    复制代码
  • FMDatabase必须执行open,在这里才能正在建立并打开SQLite3对象。

    FMDatabase *db = [FMDatabase databaseWithPath:dbpath];
    [db open];
    //...
    
    //关闭
    [db close];
    复制代码
三、数据库查询
//数据库查询
FMResultSet *rs = [db executeQuery:@"select * from people"];
//利用next函数
while ([rs next]) {
    NSLog(@"%@ %@",[rs stringForColumn:@"name"],[rs stringForColumn:@"age"]);
}
复制代码
  • FMResultSet经过调用 -executeQuery... 方法之一执行 SELECT 语句返回数据库查询结果FMResultSet 对象,而后就能够遍历查询结果了。
四、数据库更新
  • SQL 语句中除过 SELECT 语句均可以称之为更新操做。包括 CREATEUPDATEINSERTALTERCOMMITBEGINDETACHDROPENDEXPLAINVACUUMREPLACE 等。

  • 执行更新语句后会返回一个 BOOL 值,返回 YES 表示执行更新语句成功,返回 NO 表示出现错误,能够经过调用 -lastErrorMessage-lastErrorCode 方法获取更多错误信息。

    //建立表
    [db executeUpdate:@"CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER DEFAULT 1)"];
    
    //插入操做
    [db executeUpdate:@"INSERT INTO people(name,age) VALUES (?,?)", @"LiLei",[NSNumber numberWithInteger:28]]
    复制代码
五、多线程数据库访问
  • FMDatabase 自己不是线程安全的,不要实例化一个 FMDatabase 单例来跨线程使用,可是能够经过FMDatabaseQueue保证跨线程操做是同步的,是线程安全的。
FMDatabaseQueue *databaseQueue = [FMDatabaseQueue databaseQueueWithPath:dbpath];
[databaseQueue inDatabase:^(FMDatabase *db) {
        //
    [db executeUpdate:@"CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER DEFAULT 1)"];
 }];
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        [databaseQueue inDatabase:^(FMDatabase *db) {
           BOOL isSuccess = [db executeUpdate:@"INSERT INTO people(name,age) VALUES (?,?)", @"LiLei",[NSNumber numberWithInteger:28]];
            if (isSuccess) {
                NSLog(@"插入成功1");
            }
        }];
    });
    
    dispatch_async(queue, ^{
        [databaseQueue inDatabase:^(FMDatabase *db) {
            BOOL isSuccess = [db executeUpdate:@"INSERT INTO people(name,age) VALUES (?,?)", @"LiLei",[NSNumber numberWithInteger:28]];
            if (isSuccess) {
                NSLog(@"插入成功2");
            }
        }];
    });
 
复制代码
  • FMDatabaseQueue 将块代码 block 运行在一个串行队列上,即便在多线程同时调用 FMDatabaseQueue 的方法,它们仍然仍是顺序执行。这种查询和更新方式不会影响其它,是线程安全的。

5、其余

一、GYDataCenter
二、WCDB
三、Realm
相关文章
相关标签/搜索