做爲移動開發者,或多或少會與 SQLite 直接或間接打過交道,在使用過程當中可能有以下疑問:html
本文是淺層次但較系統學習 SQLite 後的總結筆記,看完或許能解答上述問題;本文敘述的出發點是從設計一個簡單的 SQLite framework 開始;關於 SQLite 的第三方庫有不少,對於 iOS 生態,知名的包括 FMDB、WCDB、GRDB、SQLite.swift 等,學習它們也是本文的一個任務之一。git
以熟悉 Swift 和 SQLite 爲目的,寫了一個相似於 FMDB 的 SQLite wrapper,詳見 SBDBgithub
在設計一個 SQLite framework 過程當中,須要理解 SQLite APIs 的使用,以及一些核心概念,包括 SQLite 的線程安全模型、所支持的併發模型等等。編寫一個 SQLite 工具庫並不是常見需求,但私覺得此過程有助於幫助更全面理解 SQLite 以及更好地使用 SQLite。sql
關於 SQLite 的介紹能夠從官方的 About SQLite 開始,本文羅列一些重要的點:數據庫
vacuum
命令 rebuild 數據庫文件把 SQLite 數據類型專門擰出來介紹是由於它相對於其餘 SQL 數據庫有一些特別之處...swift
SQLite 支持 5 種存儲類型:安全
和其餘 SQL 數據庫不太同樣的是,SQLite 的列沒有真正的類型約束;做爲對比,其餘數據庫譬如 MySQL 在建立數據表定義字段時,必定要指定列(字段)類型,以後插入數據時,得確保值和列類型匹配。SQLite 沒有這樣的約束,任何列(除了 integer 型主鍵列 )均可以同時存儲如上 5 種類型值。性能優化
> create table foo (bar); -- 定義字段 bar,但沒有指定類型
> insert into foo (bar) values (42); -- 插入整型值
> insert into foo (bar) values (NULL); -- 插入 null
> insert into foo (bar) values ("42"); -- 插入字符串
> insert into foo (bar) values (42.0); -- 插入浮點值
> select typeof(bar), bar from foo; -- 其中 `typeof` 返回值類型
42 | integer
| null
42 | text
42.0 | real
複製代碼
然而,SQLite 定義數據表時也是能夠爲字段指定類型的,譬如: create table foo (field1 numeric, field2 blob, field3)
,但這些類型並不起約束做用,它們在 SQLite 語義中被稱爲:type affinity,常譯爲「類型相像」。也有 5 種類型:integer、real、text、blob、numeric。微信
能夠把 type affinity 理解爲轉換器,以 text 爲例,若是列的 type affinity 爲 text,那麼 insert 數據時,內部會將插入的數據儘量轉爲字符串,譬如插入 1
,則存爲 "1"
;插入 2.0
,則存爲 "2.0"
;若是插入 null
,則仍然存爲 null
。SQLite 官方文檔 中有着詳細說明,本文很少贅述。網絡
SQLite 的 APIs 超過 200 個,但查看使用 SQLite 的知名庫(譬如 FMDB、YYCache 等)的源碼,能夠發現它們使用的 API 都很是少。
實際上,基於 2 個類型,8 個核心 APIs 就能完成基本功能:
{
// 兩個核心類型:
// - sqlite3: 句柄,表明鏈接
// - sqlite3_stmt: statement,能夠簡單理解爲 sql 語句的抽象
sqlite3 *db = NULL; sqlite3_stmt *stmt = NULL;
// 八個核心 APIs:
// - sqlite3_open/sqlite3_close: 用於打開/關閉鏈接
// - sqlite3_prepare/sqlite3_finalize: 建立/銷燬 statement
// - sqlite3_bind 系列: 爲 statement 綁定參數
// - sqlite3_step: 執行 statement,對於 select 語句,可能要執行屢次
// - sqlite3_reset: 將 statement 恢復到初始狀態(譬如解除綁定的參數),以便重複使用
// - sqlite3_exec: sqlite3_prepare/sqlite3_step/sqlite3_finalize 的 wrapper
sqlite3_open("path/to/db", &db);
sqlite3_prepare(db, "select * from someTable where id = ?", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, 42);
while (sqlite3_step(stmt) == SQLITE_ROW) {
// 使用 sqlite3_column 系列 API 提取數據
}
sqlite3_finalize(stmt); // 或者使用 `sqlite3_reset(stmt);`
sqlite3_exec(db, "drop table someTable", nil, NULL, NULL);
sqlite3_close(db);
}
// 如上 API 中,除了 sqlite3_stmt 和相關 API,都比較容易理解;
// 對於 sqlite3_stmt 和相關 API,花太多文字描述感受意義不大,寫點 demo 就能很快理解了
複製代碼
看起來挺簡單?然而,實際操做中有很多問題要處理,包括但不限於:
本文沒打算將上述全部點都涉及到,將內容主要收斂在基礎方面,討論:線程安全、事務、併發。
不一樣場景下,討論線程安全的關注點可能不同,譬如死鎖、非主線程執行 UI 操做等。對於 SQLite,本文討論的點是:是否能夠在任何線程使用 SQLite 的 API,且不會帶來數據安全問題。
SQLite 的 API 是支持多線程訪問的,多線程訪問必然帶來數據安全問題。
爲了確保數據庫安全,SQLite 內部抽象了兩種類型的互斥鎖(鎖的具體實現和宿主平臺有關)來應對線程併發問題:
下面畫了一張圖用來描述 fullMutex 和 coreMutex 所起到的做用:
如何理解 fullMutex?SQLite 中與數據訪問相關的 API 都是經過鏈接句柄 sqlite3 進行訪問的,基於 fullMutex 鎖,若是多個線程同時訪問某個 API -- 譬如 sqlite3_exec(db, ...)
,SQLite 內部會根據鏈接的 mutex 將該 API 的邏輯給保護起來,確保只有一個線程在執行,其餘線程會被 mutex 給 block 住。
對於 coreMutex,它用來保護數據庫相關臨界資源,包括本文將要介紹的文件鎖。
用戶能夠配置這兩種鎖,對這兩種鎖的控制衍生出 SQLite 所支持的 三種線程模型:
如何配置線程模型呢?有三個階段能夠配置線程模型:
0
: single-thread, 1
: serialized, 2
: multi-threadsqlite3_threadsafe()
能夠在運行時知道所用的 sqlite3 庫的 SQLITE_THREADSAFE 編譯選項值sqlite3_config(SQLITE_CONFIG_SINGLETHREAD
: 設置 single-thread
sqlite3_config(SQLITE_CONFIG_MULTITHREAD)
: 設置 multi-threadsqlite3_config(SQLITE_CONFIG_SERIALIZED)
: 設置 serializedsqlite3_open_v2()
創建鏈接時爲第三個參數(flags 參數)指定值,即爲每一個鏈接配置 fullMutex 的使能開關:
SQLITE_OPEN_NOMUTEX
,關閉 fullMutex,即 multi-thread 模式SQLITE_OPEN_FULLMUTEX
,打開 fullMutex,即 serialized 模式來個 Demo 直觀感覺這幾種線程模型:
// multi-thread.c
void* access_database(void *db) {
for (int i = 0; i < 1000; i++) {
sqlite3_exec((sqlite3 *)db, "begin", NULL, NULL, NULL);
sqlite3_exec((sqlite3 *)db, "end", NULL, NULL, NULL);
}
return (void *)NULL;
}
int main() {
if (sqlite3_config(SQLITE_CONFIG_MULTITHREAD) == SQLITE_OK)
printf("設置 multi-thread 模式成功\n");
sqlite3 *db = NULL;
sqlite3_open("./test.db", &db);
{
// 模擬主線程和子線程併發訪問數據庫
pthread_t p;
pthread_create(&p, NULL, access_database, db);
access_database(db);
}
sleep(2);
sqlite3_close(db);
return 0;
}
// gcc multi-thread.c -lsqlite3
複製代碼
上述 demo 代碼中,兩個線程基於同一個鏈接訪問數據庫,分別訪問了 1000 次,基本上可以觸發兩個線程同時訪問數據庫的場景,程序的執行會以 crash 結束,由於在一開始經過 sqlite3_config()
配置了 multi-thread 線程模式。
若是將 SQLITE_CONFIG_MULTITHREAD
改成 SQLITE_CONFIG_SERIALIZED
,即將 multi-thread 線程模式改成 serialized 模式,程序就能夠正常運行。
另外,若是上述 demo 中兩個線程使用的是獨立的鏈接,又是不同的結果:multi-thread 模式下,SQLite 容許多個線程使用訪問數據庫,只要不是同一個鏈接就 ok 了。
搞清楚了 fullMutex 和 coreMutex 的做用,理解 SQLite 提供的這三種線程模型並不難,此處稍做總結。
single-thread 模式下,使用者要承擔較多線程安全職責:確保在任什麼時候候,只有一個線程訪問數據庫,對於移動端而言,實在想不到使用它的收益,對於別的場景,譬如單線程模式下的嵌入式設備,或許有它存在的價值。經測試,macOS、iOS 內置 SQLite 禁掉了該模型。
Serialized 模式看似是最省心的,用戶徹底不用擔憂「illegal multi-threaded access to database connection」crash;但它付出的代價是,任何基於鏈接句柄的 API 操做都會有一個鎖檢測邏輯,對效率有所折損。
關於 fullMutex 鎖的效率折損,經 demo 測試:上千次連續讀寫,發現並不明顯。
在實際使用中,選擇最多的是 multi-thread 模式,它也是 iOS/macOS 內置 sqlite 庫的默認模式;在multi-thread 模式,須要在用戶層保證任什麼時候候只有線程在使用鏈接句柄(sqlite3 實例)訪問數據庫。
SQLite 的線程安全問題相對來講是比較容易搞定的,畢竟能夠經過配置合適的線程模型,或者在應用層經過隊列等手段來規避;但併發問題就複雜得多。
不一樣語境下併發所指的意義可能不同,對於 SQLite 而言,討論併發的粒度是事務;也就 SQLite 是否支持併發事務;因此本文將事務和併發放在一塊兒討論。先拋出結論:
如上並不是完整的結論,SQLite 對併發讀寫 -- 也即同時進行讀事務和寫事務 -- 的支持如何?這個問題的答案與用戶所選擇的日誌模型有關,下文有分析。
上文介紹了 coreMutex、fullMutex 兩種鎖,可能對理解併發形成一些干擾,此處補充一些說明。在 serialized 和 multi-thread 模式下,用戶能夠併發訪問 SQLite API,但並不意味着能夠讀寫成功,「支持併發訪問 API」和「支持併發執行事務」徹底是兩回事。
接下來的重點敘述包括:理解 SQLite 事務,理解 SQLite 事務的實現原理。
事務是 SQL 數據庫裏的通用概念,它描述的是一個或一組數據庫操做指令的執行單元;具備四個屬性:原子性、一致性、隔離性、持久性,即所謂 ACID,關於它的概念本文不過多贅述。
默認狀況下,SQLite 數據庫的全部操做都是事務的,即所謂隱式事務(implicit transaction)。以下就是一個隱式事務:
sqlite3_stmt *stmt = NULL;
sqlite3_prepare(db, "select * from table_name", -1, &stmt, NULL);
while (sqlite3_step(stmt) == SQLITE_ROW) {
// 使用 sqlite3_column 系列 API 提取數據
}
sqlite3_finalize(stmt); // 或者使用 `sqlite3_reset(stmt);`
複製代碼
用戶還能夠自行指定事務的開始與結束,即所謂顯式事務(explicit transaction),語法詳見 這裏,以下是一個小 demo:
sqlite3_exec(db, "begin", nil, NULL, NULL);
while (i < 5000) {
sqlite3_exec(db, "insert into table_name (column_name) values (42)", nil, NULL, NULL);
i++;
}
sqlite3_exec(db, "end", nil, NULL, NULL);
複製代碼
不管顯式事務仍是隱式事務,根據是否會對數據庫進行修改,能夠分爲:讀事務 和 寫事務。理清楚讀事務和寫事務這兩個概念對於分析事務併發很是重要。
drop、update、insert 等 SQL 語句,由於都涉及數據庫變動,因此包含這些語句的事務都是寫事務;若是事務中只有 select 語句,那麼它屬於讀事務。
對事務有一個基本的瞭解後,如今將注意力集中在問題中:SQLite 是如何實現事務的呢?
這個問題涉及太多細節,難以用幾段文字把它描述清楚;但若是想用好 SQLite,這個問題又不得不理清楚。咱們先從 SQLite 的日誌模型開始討論。
一個很重要的事實是,要想實現事務,單靠數據庫文件是難以完成的,須要藉助一個文件輔助完成,這個輔助文件被稱爲日誌文件(journal)。
SQLite 支持兩種日誌記錄方式,或者說兩種日誌模型:Rollback 和 WAL。
這兩種模型,日誌的文件格式不一樣,更重要的是日誌在事務執行過程當中扮演的角色不一樣;換句話說,選擇了日誌模型,至關於選擇了一種事務處理模型。
下面來分別簡述這兩種日誌模型中的事務處理邏輯。
在 rollback 日誌模型中,當執行寫事務的時候,會在數據庫文件(本文稱爲 .db)所在目錄下產生一個日誌文件(本文稱爲 .db-journal),下圖簡單描述了寫事務執行過程當中,.db-journal 所起到的做用:
以下補充一些文字說明:
SQLite 官方在 Atomic Commit In SQLite 花了至關多的筆墨介紹 rollback 日誌模式下事務處理的邏輯細節。上圖省掉了不少細節(鎖管理、內存-磁盤交互等),將重點放在了描述日誌文件自己上,能夠看出:
接下來談談 rollback 模式下的鎖邏輯。SQLite 使用文件鎖來保證事務之間的隔離性(isolation)和原子性(atomicity)。
所謂文件鎖,並非一個計算機原語,也即沒有所謂的 API 來直接控制它;它是 SQLite 抽象的一個概念,具體的實現和宿主有關,其實現細節並不是本文討論重點;須要注意的是它的 feature:
SQLite 文件裏專門有一段數據區域與鎖有關,詳見 Database File Format 的「The Lock-Byte Page」;文件鎖的具體實現與宿主有關,對於 Unix 而言,詳見 src/os_unix.c 裏 unixLock() 函數。根據官方文檔的說法,文件鎖相關數據不會回寫到磁盤,因此不用擔憂某個進程持有該鎖後,由於異常沒法釋放致使永久死鎖。
針對文件鎖五種狀態的轉換,Understanding SQLITE_BUSY 畫了一張很是棒的圖,copy 以下:
下面從代碼層面進一步理解一下:
sqlite3 *db = NULL;
sqlite3_open("path/to/db", &db);
// 開始事務
sqlite3_exec(db, "begin", nil, NULL, NULL);
// 獲取 shared 鎖
// 若是文件鎖處於 pending 或 exclusive 狀態,則失敗,返回:SQLITE_BUSY 錯誤碼
sqlite3_exec(db, "select * from table_name", nil, NULL, NULL);
// 獲取 reserved 鎖
// 僅當文件鎖處於 shared 或 unlocked 狀態,才能成功;不然失敗,返回:SQLITE_BUSY 錯誤碼
sqlite3_exec(db, "drop table table_name", nil, NULL, NULL);
// 獲取 pending 鎖,等待升級爲 exclusive 鎖
// 僅當 shared 鎖所有被釋放,才能成功執行,不然失敗返回:SQLITE_BUSY 錯誤碼
sqlite3_exec(db, "commit", nil, NULL, NULL);
複製代碼
有些相似於 2PL 併發控制機制;但 SQLite 作得更復雜一些,以規避 dead lock。
此處能夠對 rollback 日誌模式稍做總結:
激活 rollback 日誌模式能夠在鏈接數據庫後使用 pragma journal_mode
開啓:
sqlite3 *db = NULL;
sqlite3_open("path/to/db", &db);
sqlite3_exec(db, "pragma journal_mode=delete", nil, NULL, NULL);
複製代碼
其中 rollback 模式下 journal_mode 的可選值包括以下值,它們用於指定 .db-journal 的清理方式:
SQLite 默認的日誌模式是 rollback,清理模式爲 delete。
WAL 的全稱是 Write-Ahead Logging。
在 rollback 日誌模式中,寫操做是直接發生在數據庫文件上的,日誌充當備份用,主要用於確保數據庫的一致性;正常完成寫事務後,它就被銷燬了。
wal 日誌模式中,提供了另外一種日誌類型,常稱爲 wal 文件,記爲 .db-wal,在這個模型中,寫操做都發生在 wal 文件中;另外一個不一樣點是,.db-wal 文件是持久存儲的,它是數據庫完整的重要組成部分。
SQLite 官方對 rollback 日誌模式有着很是詳細的圖文並茂的 介紹,但 wal 日誌模式的待遇沒那麼好,介紹信息 相對來講沒那麼生動,因而動手根據本身的理解分別針對寫操做和讀操做畫了兩張圖。
先看看寫事務:
從上圖能夠看出,數據庫的全局數據可能分佈在兩個地方:.db 和 .db-wal 中,那麼讀操做是怎樣讀數據的呢?詳見下圖:
對 WAL 模式稍做一些總結:
激活 wal 模式能夠在鏈接數據庫後使用 pragma 開啓:
sqlite3 *db = NULL;
sqlite3_open("path/to/db", &db);
sqlite3_exec(db, "pragma journal_mode=wal", nil, NULL, NULL);
// `pragma journal_mode=wal` 語句會有一個返回值,返回當前 journal mode
// 若是不爲 wal,表示失敗
複製代碼
再回過頭來補充一些事務類型相關內容,使用 SQLite 時可能常會和它們打交道。開啓顯式事務時,能夠指定三種類型:
begin deferred ... end
begin immediate ... end
begin exclusive ... end
默認狀況下,SQLite 選用 deferred 類型。該類型下,調用 begin deferred
並不會立馬開始一個 transaction,而是延遲到第一次訪問數據庫時,若是事務的全部指令都是讀操做,那麼這一個事務被認爲是讀事務;只要其中包括寫事務,那麼它就升級爲寫事務。以下 demo:
sqlite3_exec(db, "begin deferred", nil, NULL, NULL);
// 這是一個讀事務,獲取 shared 鎖
sqlite3_exec(db, "select * from table_name", nil, NULL, NULL);
// 升級爲寫事務,獲取 reserved 鎖
sqlite3_exec(db, "drop table table_name", nil, NULL, NULL);
sqlite3_exec(db, "commit", nil, NULL, NULL);
複製代碼
Immediate 類型也被較多使用,它至關於直接告訴 SQLite 開始寫事務了,即使包含的 SQL 語句所有是讀操做,甚至不執行任何 SQL 語句;它會嘗試獲取 reserved 鎖,由於 reserved 鎖有惟一約束,因此在執行 begin immediate
時可能會產生 SQLITE_BUSY 錯誤。
對於 exclusive 類型事務,兩種日誌模式下的表現不同。在 wal 模式下,它和 immediate 類型同樣。在 rollback 日誌模式下,它會嘗試獲取 exclusive 鎖,要求獨佔數據庫,這意味着它成功的前提是:當前數據庫是徹底閒置的,沒有其餘的讀事務或寫事務在進行;換句話說,執行 begin exclusive
比 begin immediate
產生 SQLITE_BUSY 錯誤的機率更大。
搞清楚這三種事務以及互相的影響,有利於理解 SQLite 的事務處理邏輯,以及各類鎖在哪一個階段發揮做用,限於篇幅,本文不展開贅述,直接拋結果。
下表中,將事務分爲 4 類:deferred read、deferred write、immediate、exclusive;爲敘述方便,將每一個事務的執行分爲:三個階段:begin -> execute -> commit。表中描述某個事務執行過程當中對其餘事務的影響。
第一列縱座標表示正在執行(還沒有 commit)的事務類型,橫座標描述該事務對其餘類型事務的影響。
Rollback 日誌模式下,事務之間的影響以下表:
值得注意的是:
begin immediate
會嘗試獲取 reserved 鎖commit
會嘗試獲取 exclusive,哪怕 immediate 事務啥都沒作(空事務)begin immediate
會嘗試獲取 exclusive 鎖WAL 日誌模式下,事務之間的影響以下圖:
注意:exclusive 類型在 wal 模式下不起做用,和 immediate 效果同樣。
「無影響」 表格即表明着所支持的併發狀況,顯然,wal 日誌模式下的併發支持要比 rollback 模式下支持得好得多。
上述兩個表格的 cases 能夠從:Rollback 日誌模式下事務之間的影響 和 WAL 日誌模式下事務之間的影響 驗證獲得。
上文頻繁出現 SQLITE_BUSY,它是 SQLite 內部用來描述併發錯誤的錯誤碼,詳見 這裏;從上文對事務邏輯的分析能夠看出,SQLite 事務執行的過程當中,可能出現 SQLITE_BUSY 錯誤的節點很是多,在 rollback 日誌模式下尤爲如此。
對於任何一個 SQLite 庫,處理 busy 錯誤是必不可少的。
對於 SQLite 自己而言,它提供了簡單的 busy retry 方案,即設置超時時間,設超時後,SQLite 在遇到 SQLITE_BUSY 錯誤後,會在內部作一些重試嘗試。有多種設置超時時間的方式:
基於 SQLite 提供的 busy retry 方案,在 retry 過程當中,休眠時間的長短和重試次數,是決定性能和操做成功率的關鍵。Retry 超時時間的設置因不一樣操做不一樣場景而不一樣。若休眠時間過短或重試次數太多,會空耗CPU的資源;若休眠時間過長,會形成等待的時間太長;若重試次數太少,則會下降操做的成功率。
即使有了 busy retry 方案,SQLITE_BUSY 錯誤還可能仍是會出現,使用過程當中不該該忽視該問題的存在,在知名的 SQLite 庫的裏都能看到大量的 APIs 返回布爾值:
/** FMDB 庫的一些 APIs **/
- (BOOL)executeUpdate:(NSString*)sql, ...;
/** WCDB 庫的一些 APIs **/
- (BOOL)insertObject:(WCTObject *)object into:(NSString *)tableName;
- (BOOL)beginTransaction;
- (BOOL)commitTransaction;
複製代碼
像 beginTransaction、commitTransaction 這種 API 返回 false 的緣由基本上就是 SQLITE_BUSY 錯誤致使的。
從框架的角度來看,彷佛很難在內部規避掉 SQLITE_BUSY 錯誤,由於很難去約束用戶的使用姿式、譬如日誌模型、線程管理等。
對於 iOS 生態,由於每一個應用都是獨立進程,無需擔憂多進程引發的併發問題,問題相對簡單了一些。若是遵循以下姿式使用 SQLite 數據庫,應該能基本上規避 busy 問題:
這是根據先驗知識所總結的,不具有權威性,其實是否還會存在一些其餘邊界沒考慮到,得通過充分的實踐才能知道。
這一部分簡單介紹 iOS 平臺中一些知名第三方 SQLite 庫。
FMDB 多是 Objective-C 社區使用最多的 SQLite 第三方庫,它對 sqlite3 C API 比較薄地包了一層,很是輕量級,沒有任何限制,有三個主要類:
使用 FMDatabaseQueue 比較安全,但效率顯然較低,若是是時間敏感型業務,用它可能有些捉急。
但若是使用 FMDatabasePool,做者在 FMDatabasePool.h 裏留下的 comment 可能讓用戶有些緊張:
這段 comment 恐嚇若是使用不當可能會致使死鎖;然而,做者給的死鎖 case 不太靠譜,經測試沒用,做者彷佛也認識到了,詳見 這裏;我的感受,FMDatabasePool 仍是值得一用的。
總之,FMDB 是一個沒有態度的 SQLite 三方庫,沒有約束日誌模型、checkpoint 策略等,只是提供了最簡單直接的使用姿式。
沒有提供 ORM 功能,除了易用性上有些捉急以外,我認爲 FMDB 還有一個不足點:缺少日誌收集、性能監控相關 API;對於深度使用 SQLite 的功能,這是一個很是大的不足。
WCDB 是微信團隊出品的,經得起檢驗。相對於 FMDB,WCDB 要重得多。對於 iOS,若是使用 WCDB,意味着要引入兩個庫:WCDBOptimizedSQLCipher 和 WCDB。
WCDBOptimizedSQLCipher 是從 SQLCipher fork 的一個庫,後者提供了加密功能,微信團隊在此基礎上作了大量的性能優化。WCDB 提供的 性能報告,在多方面都吊打 FMDB,就是由於 WCDBOptimizedSQLCipher 從 SQLite 源碼層面作了一些優化工做,詳見 微信 iOS SQLite 源碼優化實踐。
WCDB 庫自己而言,最大的特點是提供了 ORM 等易用性方面的功能,使用體驗挺不錯。支持 ORM 對於不少高級語言(譬如 Swift)而言,是一件挺簡單的事情;但對於 Objective-C 而言挺爲難,語言 feature 太少了,譬如不支持符號重載,這在支持 ORM 中可能用得較多。
微信讀書團隊基於 FMDB 作了一個 ORM 庫:GYDataCenter,但看起來不太好用。
WCDB 主要代碼都是 C++,基於 C++ 的 feature 實現的 ORM 使用起來要好用得多。此外,WCDB 還針對數據庫損壞,提供了修復功能。
WCDB 默認使用 wal 日誌模型,和 FMDatabasePool 相似,在內部維護了一個鏈接池,提供了良好的併發性。
總之,WCDB 是一個很是牛 x 的庫,若是開發比較重的、強依賴 SQLite 的業務,它多是一個不錯的選擇。
SQLite.swift 是 Swift 生態中,stars 最多的第三方庫。但我的觀感,雖然 stars 多,但質量通常,在學習分析過程當中,發現了好幾處 bug;更新頻次低;issues 較多但大多沒有響應,給人感受這個庫沒人維護了。
GRDB 是另外一個 Swift 生態中的 SQLite 三方庫;維護良好,感受比 SQLite.swift 要好不少。但 stars 比後者少得多(2.5k v.s 6.5k),大概是由於取名沒有後者好,出現時機沒有後者早吧。
原博客閱讀體驗更好哦:zhangbuhuai.com/post/sqlite…