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
相關文章
相關標籤/搜索