问题描述
在 Electron 应用中使用 sql.js 作为 SQLite 适配器时,性能监控页面的图表数据无法显示。虽然数据已经成功保存到数据库(日志显示保存时数据值正常),但查询时返回的所有数值字段(如 `fps`、`bitrate`、`frameDropRate` 等)都是 `null` 或 `0`。
具体表现:
- 数据库查询返回的数据行数正确
- 但所有数值字段的值都是 `null` 或 `0`
- 前端图表无法显示数据
- 日志显示数据保存时值正常,但读取时值异常
根本原因
SQLite 适配器在读取数值类型(INTEGER、REAL)时,使用 `getColumn()` 或 `get()` 方法可能返回 `null` 或空值。这是因为:
1. **sql.js API 限制**:`getColumn()` 和 `get()` 方法在处理某些数据类型时可能无法正确读取数值
2. **数据类型转换问题**:SQLite 存储的数值类型在通过 `getColumn()` 读取时可能被错误地转换为 `null`
3. **列名映射问题**:虽然列名正确,但值读取失败
根本原因是 sql.js 的 `getColumn()` 和 `get()` 方法在处理数值类型时存在已知问题,而 `getAsObject()` 方法能够正确处理所有数据类型。
解决方案
修复 `SqlJsStatement.all()` 方法,优先使用 `getAsObject()` 方法读取数据:
```typescript
// 在 src/main/utils/database.ts 的 SqlJsStatement.all() 方法中
while (this.stmt.step()) {
// 优先使用 getAsObject() 方法,它更可靠且能正确处理所有数据类型
let row: any = null;
try {
if (typeof this.stmt.getAsObject === 'function') {
row = this.stmt.getAsObject();
}
} catch (error) {
// getAsObject 失败,回退到逐列读取
}
// 如果 getAsObject 不可用或失败,使用逐列读取
if (!row || typeof row !== 'object' || Object.keys(row).length === 0) {
row = {};
// ... 逐列读取逻辑(作为回退方案)
}
rows.push(row);
}
```
关键修改点:
1. **优先使用 `getAsObject()`**:在 `step()` 之后立即调用 `getAsObject()` 获取整行数据
2. **回退机制**:如果 `getAsObject()` 不可用或失败,回退到逐列读取方式
3. **类型检查**:确保返回的对象有效且包含数据
背景信息
这是一个基于 Electron + Vue 3 + TypeScript 的推流监控应用。使用 sql.js 作为 SQLite 的内存数据库适配器,通过 `SqlJsAdapter` 封装数据库操作。
技术栈:
- **前端**:Vue 3 + TypeScript + Chart.js
- **后端**:Electron Main Process + TypeScript
- **数据库**:SQLite (通过 sql.js)
- **IPC**:Electron IPC 通信
问题发现过程:
1. 前端图表显示为空,但日志显示数据已保存
2. 添加详细日志后发现查询返回的数据字段值都是 `null`
3. 通过调试查询(`SELECT *`)确认数据确实存在于数据库中
4. 检查 `SqlJsStatement.all()` 方法,发现使用 `getColumn()` 读取数据
5. 改为使用 `getAsObject()` 方法后问题解决
相关文件:
- `src/main/utils/database.ts` - SQLite 适配器实现
- `src/main/modules/StorageService.ts` - 存储服务,调用数据库查询
- `src/renderer/components/StreamPerformanceViewer.vue` - 前端图表组件