大型Electron應用本地資料庫技術選型

liulun發表於2020-06-07

開發一個大型Electron的應用,或許需要在客戶端儲存大量的資料,比如聊天應用或郵件客戶端

可選的客戶端資料庫方案看似很多,但一一對比下來,最優解只有一個

接下來我們就一起來經歷一下這個技術選型的過程:

排除:把資料以Json的形式儲存在檔案中

 以這種方式儲存一些使用者的配置資訊是完全沒問題的(使用者名稱、家庭住址、是否開啟免打擾模式等)

但要用這種方式儲存大量解構化的資料,就非常不科學了

主要原因是:

用這種方案運算元據是需要把檔案中的所有資料都載入到客戶端電腦的記憶體中去的

由於沒有索引機制,關聯查詢、條件查詢等操作效率不高,

更新了某項資料之後,要持久化更新操作,又要重寫整個檔案。

PS:

如果你的應用操作的資料量不多,

你可以選擇類似lowdb(https://github.com/typicode/lowdb)這樣的工具,

在一定程度上環節這些困難

排除:LocalStorage、SessionStorage、WebSql、Cookies

Cookies儲存容量太小,只能存4kb的內容,而且每次與服務端互動,同域下的Cookie還會被攜帶到服務端,也沒有關聯查詢、條件查詢的機制

LocalStorage儲存容量也很小,大概不會超過10M,它是以鍵值對形式儲存資料的,同樣也沒有關聯查詢、條件查詢的機制

SessionStorage最大的問題是,每次關閉應用程式,它裡面的內容會被清空,想持久化儲存資料,就不用考慮它了

WebSql諸般特性都挺好,無奈這個技術已經被W3C委員會否決了,不知道哪天Electron也不支援了,到時就傻眼了

分析

現在可選的成熟方案几乎只剩下SQLite和IndexedDB了,

SQLite是一個輕型的、嵌入式的SQL 資料庫引擎,其特點是自給自足的、無伺服器、零配置的、支援事務。它是在世界上最廣泛部署的 SQL 資料庫引擎。

IndexedDB是Chromium內建的一個基於JavaScript的物件導向的資料庫,在Electron應用內它儲存的容量限制與使用者的磁碟容量有關,是使用者磁碟大小的1/3

市面上選這兩個方案的商業產品各都有很多

那麼到底哪個好呢?

接下去我們就做一個效能的對比

SQLite和IndexedDB效能對比

測試環境

CPU:I9 9900K 3.6GHZ

記憶體:32G

OS:Win10

環境搭建

SQLite環境

訪問SQLite資料使用的是knexjs操作庫,它是一個sql生成器,支援Promise API,鏈式操作非常好用,推薦使用

在Electron應用內安裝SQLite,比較特殊,需要使用如下安裝指令:

npm install sqlite3 --build-from-source --runtime=electron --target=9.0.0 --dist-url=https://atom.io/download/electron

注意:--target後面的內容與你使用的Electron的版本要一致

SQLite的資料庫表結構

CREATE TABLE [message](
  [id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, 
  [msg_from] VARCHAR(80), 
  [msg_to] VARCHAR(80), 
  [msg] TEXT, 
  [create_time] DATETIME);

這裡主要模擬了一個IM應用的訊息表

SQLite的測試程式碼如下

let { app } = require('electron');
let messages = require('./messages')
let path = require('path');
let filename = path.join(app.getPath('userData'), 'db.db');
let db = require('knex')({
    client: 'sqlite3',
    useNullAsDefault: true,
    connection: { filename },
    timezone: 'UTC',
    dateStrings: true
});
let start = async () => {
    let startTime = Date.now();
    for (let i = 0; i < 10; i++) {
        let index = i % 2;
        await db('message').insert(messages[index]);
    }
    //let arr = await db('message').whereBetween('id',[1600,9600]);
    //await db('message').whereBetween('id',[0,10000]).del();
    //await db('message').update({msg:`天接雲濤連曉霧。!!!`}).whereBetween('id',[2600,2800]);
    let endTime = Date.now();
    console.log(endTime - startTime);
}
module.exports = {
    start
}

其中用到了messages是兩個訊息體的JSON物件,程式碼如下:

let messages = [{
    msg_from: '辛棄疾',
    msg_to: '劉曉倫',
    msg: `醉裡挑燈看劍,夢迴吹角連營。 八百里分麾下炙, 五十弦翻塞外聲, 沙場秋點兵。
    馬作的盧飛快, 弓如霹靂弦驚。 了卻君王天下事, 贏得生前身後名。 可憐白髮生!`,
    create_time: new Date()
}, {
    msg_from: '李清照',
    msg_to: '劉曉倫',
    msg: `天接雲濤連曉霧。 星河欲轉千帆舞。 彷彿夢魂歸帝所, 聞天語, 殷勤問我歸何處。 我報路長嗟日暮, 學詩謾有驚人句。
    九萬里風鵬正舉。 風休住, 蓬舟吹取三山去!`,
    create_time: new Date(),
}];
module.exports = messages

IndexedDB環境

IndexedDB的測試程式碼是在渲染程式中執行的,程式碼如下:

let Dexie = require('Dexie');
const db = new Dexie('db');
db.version(1).stores({
  message: '++, message_from, message_to,msg,create_time'
});
window.onload = async () => {
  let startTime = Date.now();
  for (let i = 0; i < 10000; i++) {
    let index = i % 2;
    await db.message.add(messages[index]);
  }
  //let arr = await db.message.where("id").between(1000, 9000).delete();
  let endTime = Date.now();
  console.log(endTime - startTime);
}

 

測試結果

插入

連續插入100行資料,執行8次

[
  {
    name: 'SQLite',
    data: [526,551,536, 897, 530, 509, 534,538]
  },
  {
    name: 'IndexedDB',
    data: [333,221,167, 169, 336, 313, 187,169]
  }
]

 

連續插入1000行資料,執行7次

[
  {
    name: 'SQLite',
    data: [5669,7488,7443,7033,7231,7537,7563]
  },
  {
    name: 'IndexedDB',
    data: [2140,2111,1755,1716,2126,1757,2006]
  }
]

 

連續插入10000行資料,執行4次

[
  {
    name: 'SQLite',
    data: [202415,158451,144221,143993]
  },
  {
    name: 'IndexedDB',
    data: [20028,18979,21013,18738]
  }
]

 

已存在10000行資料的前提下,再插入10行資料

[
  {
    name: 'SQLite',
    data: [158,268,306,162,149,159]
  },
  {
    name: 'IndexedDB',
    data: [56,99,47,49,53,52]
  }
]

 

檢索

在10000行資料中按主鍵檢索8000行資料

[
  {
    name: 'SQLite',
    data: [47,55,56,60]
  },
  {
    name: 'IndexedDB',
    data: [62,54,58,55]
  }
]

 

刪除

SQLite

已存在10000行資料的前提下,刪除200行資料(毫秒):18、16、18

已存在10000行資料的前提下,刪除8000行資料(毫秒):18

已存在10000行資料的前提下,刪除10000行資料(毫秒):18

IndexedDB

已存在10000行資料的前提下,刪除200行資料(毫秒):21、10、10

已存在10000行資料的前提下,刪除8000行資料(毫秒):58

已存在10000行資料的前提下,刪除10000行資料(毫秒):30

更新

SQLite

已存在10000行資料的前提下,更新1行資料(毫秒):8、8、8、9、8、8

已存在10000行資料的前提下,更新100行資料(毫秒):30、30、28、30、30

IndexedDB

已存在10000行資料的前提下,更新1行資料(毫秒):11、8、7、7、8、8

已存在10000行資料的前提下,更新100行資料(毫秒):15、14、12、10、13

結論分析

結論:插入資料兩個資料庫效能相差巨大,IndexedDB顯然優於SQLite,檢索,刪除,更新操作兩個資料庫效能相差無幾

分析:

SQLite有雙寫入機制,IndexedDB應該是有多級快取寫入機制(待考),顯然多級快取寫入機制更優秀

因為是Electron工程下完成此對比,所以Js經Electron轉到Node.js再轉到SQLite的Node module最後才轉到SQLite的C程式碼,這個過程可能是效能損耗的一大主要原因

最後:

綜合對比下來,大型Electron應用更推薦使用IndexedDB來儲存業務資料

(由於有Dexie的加持,IndexedDB操作也足夠簡單,所有中小型應用也是不錯的選擇)

如果你需要加密客戶端資料,SQLite還需要外套sqlcipher這樣的加密庫,所以效能上會有更多損耗,

然而IndexedDB本身就有一層加密邏輯(可以說只能防君子,防不了小人),雖然簡單,但聊勝於無。

最後

歡迎大家購買我的新書《Electron實戰:入門、進階與效能優化》,

書裡還有更多有趣的內容,

大家感興趣可以加QQ群949674481交流。

噹噹:http://product.dangdang.com/28547952.html
京東:https://item.jd.com/12867054.html

 

相關文章