SQLite 并發的四種處理方式

      網友投稿 1576 2025-03-31

      SQLite 是一款輕型的嵌入式數據庫它占用資源非常的低,處理速度快,高效而且可靠。在嵌入式設備中,可能只需要幾百 K 的內存就夠了。因此在移動設備爆發時,它依然是最常見的數據持久化方案之一。不過即使 SQLite 已經非常成熟,但是我們在編程中依然會遇到一些問題,其中最常見也最難搞的就是 —— 并發。

      就像其他類似的問題一樣,SQLite 在移動端的并發處理也存在多種不同的設計。下面我們通過 iOS 中四個常用類庫 (SQLite.swift, FMDB, GRDB, Core Data) 來看看這些設計。不過在此之前,我們需要明確 SQLite 在并發編程環境下到底存在哪些問題:

      并發寫操作:某一時刻可能存在對同一個數據庫的寫操作,而這是 SQLite 不允許的行為。

      操作隔離:連續的兩個數據庫查詢操作可能會出現結果差異,因為在并發環境下你無法保證著兩個讀操作中間不會出現寫操作。

      操作沖突:并發環境下數據庫的新增和修改操作執行的時序并不一定與調用時序是一致的。這就導致一個可能的情形就是:數據庫多個更新操作調用后可能存在一些意料之外的情形,而且你還難以追蹤排除。

      明確這些問題后,接下來我們就來看看這些類庫做出了何種應對。

      SQLite.swift 方案

      SQLite 并發的四種處理方式

      然而改方案卻無法應對第二個問題。例如,我們需要為數據庫中的某位用戶設置頭像,如果該用戶存在時則執行插入操作,對應代碼如下:

      let?userAvatars?=?avatars.filter(userId?==?1)let?insert?=?avatars.insert(userId?<-?1,?url?<-?avatarURL)if?db.scalar(userAvatars.count)?==?0?{????try?db.run(insert) }

      咋看之下代碼邏輯并沒有任何問題和缺陷,但是在并發環境下這里存在一個隱藏的問題。你無法保證在執行 * try db.run(insert)* 沒有任何地方執行相同的操作。雖然這種情形很少見而且數據庫在這種情形下也沒有 Crash 出現,但是可能在一開始數據庫在設定的時候就約定了每一個用戶只能存在一條頭像信息,這就導致了業務邏輯錯誤或者沖突。

      當然這個問題我們可以在數據庫定義時就能屏蔽掉,或者我們顯式的通過事務對其進行處理:

      try?db.transaction?{????let?userAvatars?=?avatars.filter(userId?==?1)????let?insert?=?avatars.insert(userId?<-?1,?url?<-?avatarURL)????if?db.scalar(userAvatars.count)?==?0?{????????try?db.run(insert) ????} }

      但是有些時候,開發人員可能因工期等等問題而忽略上訴,最終埋下了隱患。對于第三個問題,類庫并沒有任何處理永遠都是?the last write always win?。

      FMDB 方案

      FMDB 與 SQLite.swift 一樣都是采用串行設計,只不過 FMDB 在此基礎上做了些加強:FMDB 中使用者不會接觸到數據庫連接而是通過在 API 閉包中組織語句來實現數據庫訪問。

      dbQueue.inDatabase?{?db?in ????if?db.intForQuery("SELECT?COUNT?...")?==?0)?{ ????????db.executeUpdate("INSERT?INTO?avatars?...") ????} }

      這種方式不僅解決了同時寫的問題而且還非常平滑的解決了操作隔離問題,相比上一個方案明顯更為友好。

      GRDB 方案

      此方案借鑒了 FMDB 中的 API 設計,使用者通過在閉包中組織語句來實現數據庫訪問。不過與前兩個相比,GRDB 最大的不同就是它不再使用串行隊列設計。通過對 SQLite 本身 WAL 模式進行,GRDB 支持多線程同時進行讀寫操作。

      注意:寫操作依然是串行進行,WAL 依然需要遵守 SQLite 單寫策略

      try?dbPool.write?{?db?in ????if?Int.fetchOne(db,?"SELECT?COUNT?...")?==?0)?{????????try?db.execute("INSERT?INTO?avatars?...") ????} }

      該模式最大的特點在于,我們在進行數據庫寫操作的同時,依然能并行的執行讀操作。這意味著,在特定線程運行費時的數據庫同步寫操作的時候用于更新 UI 的數據庫讀操作不會像前兩種方案一樣被阻塞住。也就是說,寫操作對于讀操作來說是透明的。

      dbPool.read?{?db?in ????//?Those?values?are?guaranteed?to?be?equal: ????let?count1?=?User.fetchCount(db) ????let?count2?=?User.fetchCount(db)}

      并且 GRDB 通過?DatabaseSnapshot?對數據庫訪問進行了讀寫分離實現,進一步提高了多線程訪問的安全。

      Core Data 方案

      雖然 Apple 官方并沒有說 Core Data 是 SQLite 的一個封裝和實現,但是我們都知道其實它底層還是使用 SQLite 作為存儲引擎。

      為了解決文章前面提到的 SQLite 并發情形下的典型問題,Core Data 自己實現并維護了一套上下文管理邏輯。 SQLite.swift 關注的上下文是其執行期間的單個SQL語句。 對于FMDB和GRDB 關注的上下文環境則是閉包中的 SQL 語句塊。 而 Core Data 托管上下文則是 NSManagedObjectContext 實例的整個生命周期,包含數據庫修改和內存修改。

      這讓 Core Data 能夠應對并發問題中的第三種情形,同一個對象如果在不同上下文中同時發生修改則會被檢測出來(文檔)。而前面三種方案只要 SQL 語句沒有違背表定義都能進行記錄更新而且最后一個永遠是贏家。

      但是這種設計也存在缺點,首先擴大后的上下文管理是一件非常麻煩的事,另外所有的寫操作都會被嚴格束縛而且沖突處理依然很棘手,最后嚴格的上下文管理也讓 Core Data 中編寫正確的多線程代碼也變得很困難。

      總結

      總體而言,FMDB 和 GRDB 采用的方式從安全性和靈活性上會更好一點。順便提一下,根據微信團隊的文章他們采用的可能是 GRDB 那種方式,因為在微信的應用場景下寫操作是瓶頸所在。

      SQL 數據庫 SQLite

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:新版整合版WPS2019如何添加備注(wpsppt添加備注)
      下一篇:okr軟件協作(okR管理
      相關文章
      亚洲理论在线观看| 91亚洲国产成人精品下载| 亚洲校园春色小说| 久久亚洲一区二区| 亚洲AV永久无码区成人网站 | 久久国产亚洲精品无码| 亚洲精品无码高潮喷水在线| 色久悠悠婷婷综合在线亚洲| 深夜国产福利99亚洲视频| 国产成人高清亚洲一区91| 亚洲AV无码专区国产乱码不卡| 亚洲狠狠婷婷综合久久| 亚洲国产成人无码AV在线影院| 亚洲国产成人久久综合| 亚洲高清国产拍精品熟女| 亚洲爆乳无码专区www| 亚洲高清乱码午夜电影网| 大胆亚洲人体视频| 国产啪亚洲国产精品无码 | 亚洲一区二区三区高清视频| 亚洲人妖女同在线播放| 456亚洲人成影院在线观| 亚洲国产综合精品中文第一| 国产精品高清视亚洲一区二区| 亚洲中文字幕乱码一区| 亚洲av无码日韩av无码网站冲| 国产成人亚洲精品蜜芽影院| 亚洲国产小视频精品久久久三级| 亚洲国产精品人人做人人爱| 亚洲一区二区精品视频| 国产亚洲成AV人片在线观黄桃| 亚洲av日韩av不卡在线观看| 久久久久亚洲av无码专区喷水 | 国产尤物在线视精品在亚洲| 亚洲国产综合无码一区二区二三区| 亚洲真人日本在线| 亚洲av综合av一区| 亚洲婷婷天堂在线综合| 亚洲日韩精品无码专区加勒比| 免费在线观看亚洲| 亚洲精品成人片在线观看精品字幕 |