开发一个大型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

大型Electron应用本地数据库技术选型的更多相关文章

  1. WPF技术触屏上的应用系列(二): 嵌入百度地图、API调用及结合本地数据库在地图上进行自定义标点的实现

    原文:WPF技术触屏上的应用系列(二): 嵌入百度地图.API调用及结合本地数据库在地图上进行自定义标点的实现 去年某客户单位要做个大屏触屏应用,要对档案资源进行展示之用.客户端是Window7操作系 ...

  2. Aibabelx-shop 大型微服务架构系列实战之技术选型

    一.本项目涉及编程语言java,scala,python,涉及的技术如下: 1.微服务架构: springboot springcloud mybatisplus shiro 2.全文检索技术 sol ...

  3. (转).net项目技术选型总结

    原文作者:mcgrady 原文地址:.net项目技术选型总结 做.net开发已经几年了,也参与开发了很多大大小小的项目,所以现在希望总结出一套开发.net项目的常用技术,也为以后做项目技术选型的时候作 ...

  4. 【转】服务化框架技术选型与京东JSF解密

    [京东技术]声明:本文转载自微信公众号“开涛的博客”,转载务必声明. 作者:章耿,原京东资深架构师,曾负责京东服务框架,配置中心等基础平台.近十年工作经验,专注于基础中间件等底层技术架构,对分布式系统 ...

  5. 数据库技术丛书:SQL Server 2016 从入门到实战(视频教学版) PDF

    1:书籍下载方式: SQL Server2016从入门到实战 PDF 下载  链接:https://pan.baidu.com/s/1sWZjdud4RosPyg8sUBaqsQ 密码:8z7w 学习 ...

  6. .net项目技术选型总结

    做.net开发已经几年了,也参与开发了很多大大小小的项目,所以现在希望总结出一套开发.net项目的常用技术,也为以后做项目技术选型的时候作为参考. 数据库 小型项目:SQLite(工具) 中大型项目: ...

  7. SQLCE使用本地数据库优化

    一.数据绑定 1.使用数据虚拟化和SKIP/TAKE 使用 Skip 和 Take 方法可确保直到需要在 ListBox 控件中显示数据时才将数据库中的数据加载到内存中. 例如,以下代码显示了如何从数 ...

  8. 多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了

    1.引言 对于即时通讯网来说,所有的技术文章和资料都在围绕即时通讯这个技术方向进行整理和分享,这一次也不例外.对于即时通讯系统(包括IM.消息推送系统等)来说,MQ消息中件间是非常常见的基础软件,但市 ...

  9. react 前端项目技术选型、开发工具、周边生态

    react 前端项目技术选型.开发工具.周边生态 声明:这不是一篇介绍 React 基础知识的文章,需要熟悉 React 相关知识 主架构:react, react-router, redux, re ...

随机推荐

  1. Sentinel源码解析一(流程总览)

    引言 Sentinel作为ali开源的一款轻量级流控框架,主要以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度来帮助用户保护服务的稳定性.相比于Hystrix,Sentinel的设计更加简 ...

  2. 关于web标准

    从我去年接触前端,到现在,我似乎都没有特意去研究过web标准.我只知道传统上推崇结构样式行为分离,js.css.html各司其职, 不推荐在文档的节点上写类似<p onclick=“fn()”& ...

  3. 原生js获取元素的各种位置(大全)

    加给元素: offsetLeft (距离定位父级的距离) offsetTop (距离定位父级的距离) offsetWidth (可视宽度) offsetHeight (可视高度) clientLeft ...

  4. ie ajax 跨域情况遇到的各种问题

    jQuery.support.cors = true; http://blog.csdn.net/jupiter37/article/details/25694289 jQuery ajax跨域调用出 ...

  5. Android Studio代码编译通过但是提示停止运行

    这种问题也许有很多可能,但是我遇到过一种,下面分享给大家希望问题会得到解决 检查你的布局文件中图片的位置 如果图片名前有“(v24)”的删掉 重新插入图片到这个目录 然后就可以正常运行了

  6. MyBatis——Mapped Statements collection does not contain value for XXX

    报错信息: Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql. ...

  7. 关于mobileSelect.js日期数据获取封装,动态时间,封装

    传入起始年份和起始月份 得到 插件的标准格式放到 效果 let getAllDatas = (stime, etime) => { //接收传进来的参数月份 var data_M = etime ...

  8. 第4章 最基础的分类算法-k近邻算法

    思想极度简单 应用数学知识少 效果好(缺点?) 可以解释机器学习算法使用过程中的很多细节问题 更完整的刻画机器学习应用的流程 distances = [] for x_train in X_train ...

  9. 在 Linux 系统中如何管理 systemd 服务

    在上一篇文章<Linux的运行等级与目标>中,我介绍过 Linux 用 systemd 来取代 init 作为系统的初始化进程.尽管这一改变引来了很多争议,但大多数发行版,包括 RedHa ...

  10. 数据库-第八章 数据库编程-8.4 ODBC编程

    ODBC编程 一.ODBC概述 二.ODBC工作原理概述 1.用户应用程序 2.ODBC驱动程序管理器 3.数据库驱动程序 4.数据源管理 5.小结 三.ODBC API基础 1.函数概述 2.句柄及 ...