Electron鸿蒙PC上存数据我试了三种方案只有一种能扛住10万条记录上周一个做电商后台的朋友问我他的Electron应用在鸿蒙PC上要存大概5万条订单记录用JSON文件卡成狗问我怎么办。我说你干嘛不用SQLite他说编译不过。后来我自己花了一下午把三种常见方案都跑了一遍发现事情没想象中简单。下面把测试过程和结论摊开说。方案一JSON文件——看起来最省事实际最坑说实话我一开始也图省事。心想不就是存点数据吗fs.writeFileSync一把梭读取的时候JSON.parse一下完事儿。// 最简单的JSON存储方案constfsrequire(fs);constpathrequire(path);const{app}require(electron);constdbPathpath.join(app.getPath(userData),data.json);functionsaveData(data){fs.writeFileSync(dbPath,JSON.stringify(data,null,2));}functionloadData(){if(!fs.existsSync(dbPath))return[];returnJSON.parse(fs.readFileSync(dbPath,utf-8));}这代码在Windows上跑得好好的搬到鸿蒙PC上也没报错。但问题是——数据量一上来主进程直接卡死。我写了段测试代码往JSON里塞10万条假数据consttestDataArray.from({length:100000},(_,i)({id:i,name:Item${i},timestamp:Date.now(),metadata:{category:test,tags:[a,b,c]}}));console.time(json-save);saveData(testData);console.timeEnd(json-save);// 鸿蒙PC上~3200msconsole.time(json-load);constloadedloadData();console.timeEnd(json-load);// 鸿蒙PC上~1800ms3秒多。注意这是在主进程里阻塞的3秒多界面完全冻结。而且这是纯写入如果还要做查询——比如找出某个时间范围内的记录——那得把整个数组filter一遍再塞10万条又卡一次。更坑的是并发。Electron是多进程架构如果主进程和某个渲染进程同时读写同一个JSON文件数据很容易搞坏。我用proper-lockfile试了加锁但加锁本身又引入了额外的延迟和死锁风险。结论JSON文件适合存配置项比如用户偏好设置数量不要超过几百条。一旦上千条这方案就是给自己挖坑。方案二IndexedDB——浏览器里看着美好鸿蒙PC上另有隐情既然JSON不行那用浏览器自带的IndexedDB总行吧毕竟Electron本质就是ChromiumIndexedDB是原生支持的。// Renderer进程中使用IndexedDBconstDB_NAMEMyAppDB;constDB_VERSION1;functionopenDB(){returnnewPromise((resolve,reject){constrequestindexedDB.open(DB_NAME,DB_VERSION);request.onupgradeneeded(event){constdbevent.target.result;if(!db.objectStoreNames.contains(orders)){db.createObjectStore(orders,{keyPath:id});}};request.onsuccess(event)resolve(event.target.result);request.onerror(event)reject(event.target.error);});}asyncfunctionaddOrder(order){constdbawaitopenDB();returnnewPromise((resolve,reject){consttxdb.transaction([orders],readwrite);conststoretx.objectStore(orders);constrequeststore.add(order);request.onsuccess()resolve(request.result);request.onerror()reject(request.error);});}我满怀期待地跑了一下10万条插入测试。IndexedDB确实不卡主进程因为它是异步的而且跑在渲染进程里。但新的问题出现了。第一个坑数据放哪儿在鸿蒙PC上Electron的userData路径和Windows/Mac都不一样。IndexedDB的数据文件实际存放在 Chromium 的用户数据目录下而不是你代码里指定的路径。这意味着——如果你想做数据备份或者迁移根本不知道文件在哪。我在鸿蒙PC上翻了好久最后发现它在~/.config/你的应用名/IndexedDB/下面而且文件名是一堆乱码似的leveldb文件。第二个坑容量限制Chromium对IndexedDB有存储配额限制。虽然桌面端通常宽松一些但鸿蒙PC上的Electron我实测到大概500MB左右就开始弹配额提示了。对于图片缓存或者大量日志这根本不够用。第三个坑查询能力弱IndexedDB本质上是个Key-Value存储虽然支持索引但复杂查询比如多条件筛选、聚合统计写起来非常啰嗦。我做了一个按时间范围状态筛选的需求代码写了一百多行换成SQL就一句WHERE。IndexedDB的10万条插入测试结果大约12秒。虽然不卡界面但速度实在一般。结论IndexedDB适合存图片缓存、临时状态、离线数据这类丢了也能重建的东西。别把它当主数据库用查询能力和容量都扛不住正经业务。方案三SQLitebetter-sqlite3——性能最强但编译这一关不好过前两个方案都不太理想我最后把目光投向了SQLite。社区里用better-sqlite3的比较多因为它是同步API写起来直观而且性能确实顶。但问题来了——better-sqlite3是原生C模块需要编译。在鸿蒙PC的Electron环境下这一步坑了我将近两个小时。安装与编译npminstallbetter-sqlite3如果你运气不好会直接报错。因为鸿蒙PC的架构和工具链与标准Linux有差异node-gyp编译时找不到正确的头文件或者链接器配置。我的解决办法是预先指定Electron的ABI版本npminstallbetter-sqlite3 --build-from-source\--runtimeelectron\--target28.0.0\--disturlhttps://electronjs.org/headers\--abi128ABI版本号一定要对。我第一次编译时 target 写成了 27.x结果启动时直接段错误segfault排查了半天才发现是ABI不匹配。老实说官方文档这块写得很模糊我反复试了五六次才搞明白。代码实现编译通过后用起来就非常顺手了constDatabaserequire(better-sqlite3);constpathrequire(path);const{app}require(electron);constdbPathpath.join(app.getPath(userData),app.db);constdbnewDatabase(dbPath);// 初始化表db.exec(CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, timestamp INTEGER NOT NULL, category TEXT, amount REAL ); CREATE INDEX IF NOT EXISTS idx_timestamp ON orders(timestamp); CREATE INDEX IF NOT EXISTS idx_category ON orders(category););// 插入预编译语句性能关键constinsertStmtdb.prepare(INSERT INTO orders (name, timestamp, category, amount) VALUES (?, ?, ?, ?));functionaddOrder(order){returninsertStmt.run(order.name,order.timestamp,order.category,order.amount);}// 批量插入——这才是sqlite的强项constinsertManydb.transaction((orders){for(constorderoforders){insertStmt.run(order.name,order.timestamp,order.category,order.amount);}});// 查询带条件筛选constqueryStmtdb.prepare(SELECT * FROM orders WHERE timestamp BETWEEN ? AND ? AND category ? ORDER BY timestamp DESC LIMIT ? OFFSET ?);functionqueryOrders(startTime,endTime,category,limit,offset){returnqueryStmt.all(startTime,endTime,category,limit,offset);}性能实测10万条记录单条逐次插入约2.1秒。但上面代码里的transaction批量插入10万条只要~280毫秒。查询呢按时间范围分类筛选再分页取20条不到1毫秒。这差距太明显了。JSON文件写入要3秒读取筛选又要卡界面IndexedDB异步插入要12秒SQLite批量写入只要0.28秒查询几乎无感知。鸿蒙PC上的额外注意点数据库文件位置和日志文件一样鸿蒙PC上的app.getPath(userData)返回的路径和Windows不一样。我的应用实际拿到的是/data/user/0/你的包名/files/下面的路径。这个目录在应用卸载时会被清空所以如果你希望用户数据保留得考虑迁移到外部存储需要申请ohos.permission.WRITE_MEDIA权限。备份与迁移SQLite就一个.db文件备份时直接cp就行。我在应用里加了个导出数据功能本质上就是把这个文件复制到用户选定的目录比JSON和IndexedDB方便太多了。WAL模式高并发场景下建议开启WALWrite-Ahead Logging能显著提升读写并发性能。db.pragma(journal_mode WAL);db.pragma(synchronous NORMAL);我在做导入大批量数据功能时没开WAL之前界面偶尔会有轻微卡顿开了之后完全消失。三种方案对比我整理了一个简单的对比供参考维度JSON文件IndexedDBSQLite10万条写入~3200ms~12000ms~280ms复杂查询全表遍历卡顿支持索引但API啰嗦SQL1ms并发安全无需手动加锁支持支持存储容量受限于内存和解析速度~500MB配额几乎无限制鸿蒙PC兼容性好路径隐晦需编译原生模块数据迁移容易困难路径混乱复制单个文件上手难度极低低中等编译坑我的建议三种方案测下来我的判断很明确小数据量几百条配置项用JSON文件没问题代码少维护简单一把梭。中等规模几千条结构简单可以用IndexedDB凑合毕竟不用操心编译。但要是数据量上万、需要复杂查询、或者对性能有要求——硬着头皮也要上SQLite。编译那点坑踩一次就通了。我写了个内部文档把鸿蒙PC上的编译参数和常见问题都记了下来团队里后面的人半小时就能搞定。总比每次读写都卡界面、或者写一堆IndexedDB的啰嗦代码强。鸿蒙PC的桌面生态还在快速迭代Electron的适配也越来越完善。现在苦一点把存储层打扎实后面加功能会轻松很多。这话也是我踩了这么多坑之后的真心话。版权声明本篇文章遵循 MIT 协议 开源发布。转载请在正文中显著位置注明原作者及本文链接未经作者书面授权不得用于商业目的。