HarmonyOS 关系型数据库 RDB 数据持久化(ArkTS)实战:建库建表、CRUD、事务、FTS、性能优化,一篇搞懂

HarmonyOS 关系型数据库 RDB 数据持久化(ArkTS)实战:建库建表、CRUD、事务、FTS、性能优化,一篇搞懂


1. 场景:为什么要用关系型数据库(RDB)?鸿蒙开发者第四期活动

如果你的数据像"表格"一样,字段之间有明确对应关系,比如:

  • 班级学生信息:姓名、学号、各科成绩
  • 公司员工信息:姓名、工号、职位、部门
  • App 里的"错题本/笔记/订单/清单":都有筛选、排序、分页需求

这类数据的特点是:结构清晰、字段多、查询条件复杂 (按学号查、按成绩排序、按日期筛选等)。

这时候 KV-Store(键值)就不舒服了,RDB 才是正解

RDB 底层基于 SQLite,支持事务、索引、视图、触发器、外键、预编译 SQL、参数化查询等能力,适合做"正经业务数据"的持久化。


2. 必懂概念:谓词 & 结果集

2.1 谓词(RdbPredicates)

你可以理解成:"我想改/删/查哪些行"的条件表达器

比如:NAME = 'Lisa'score > 90id in (...)

2.2 结果集(ResultSet)

查询返回的数据不是一次性给你数组,而是一个"游标":

  • 默认指向 -1
  • goToNextRow() 才会移动到下一行
  • 用完一定要 close(),不然容易内存占用飙升

3. 约束和坑点

这些是官方重点强调的,实际开发也很常见:

  • 单次查询建议不超过 5000 条,否则可能卡死
  • 大查询建议放到 TaskPool 里做
  • SQL 拼接尽量简洁、合理分页/分批
  • 同一时间只能有一个写操作(写连接不会动态扩充)
  • 单条数据建议不要超过 2MB:超过可能"插入成功但读取失败"
  • ArkTS 支持的基本类型:number / string / boolean / 二进制(Uint8Array) / BigInt(用 UNLIMITED INT)
  • 默认日志模式 WAL ,落盘方式 FULL
  • 应用卸载后:数据库文件和临时文件(-wal/-shm)会被清理

4. 实战案例:做一个"学生表"并完成全套 CRUD(Stage 模型,ArkTS)

我用一个最经典的案例:学生信息管理

4.1 表结构设计

表:STUDENT

字段建议:

  • ID:主键自增
  • NO:学号(唯一)
  • NAME:姓名
  • MATH:数学成绩
  • ENGLISH:英语成绩
  • UPDATED_AT:更新时间(时间戳,便于排序)

建表 SQL:

ts 复制代码
const SQL_CREATE_STUDENT_TABLE =
  `CREATE TABLE IF NOT EXISTS STUDENT (
    ID INTEGER PRIMARY KEY AUTOINCREMENT,
    NO TEXT NOT NULL UNIQUE,
    NAME TEXT NOT NULL,
    MATH INTEGER,
    ENGLISH INTEGER,
    UPDATED_AT INTEGER
  )`;

5. 我推荐的项目结构(不把数据库写死在页面里)

很多人写 demo 喜欢把 getRdbStore 写在 UIAbility 里,能跑但不利于维护。

我更推荐:RdbManager(单例)+ DAO(数据访问层)

5.1 RdbManager:负责"拿到 store + 建表 + 版本管理"

ts 复制代码
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

const DB_NAME = 'Student.db';
const DB_VERSION = 1;

export class RdbManager {
  private static instance: RdbManager;
  private store?: relationalStore.RdbStore;

  static getInstance(): RdbManager {
    if (!RdbManager.instance) {
      RdbManager.instance = new RdbManager();
    }
    return RdbManager.instance;
  }

  async init(context: Context): Promise<relationalStore.RdbStore> {
    if (this.store) return this.store;

    const config: relationalStore.StoreConfig = {
      name: DB_NAME,
      securityLevel: relationalStore.SecurityLevel.S3,
      encrypt: false,
      isReadOnly: false,
    };

    this.store = await new Promise((resolve, reject) => {
      relationalStore.getRdbStore(context, config, (err, store) => {
        if (err) reject(err);
        else resolve(store);
      });
    });

    // 首次创建数据库时 version = 0
    if (this.store.version === 0) {
      await this.store.execute(SQL_CREATE_STUDENT_TABLE);
      this.store.version = DB_VERSION;
    }

    return this.store;
  }

  getStore(): relationalStore.RdbStore {
    if (!this.store) {
      throw new Error('RdbStore not initialized. Call init() first.');
    }
    return this.store;
  }
}

同一个数据库名,但不同 context 可能会产生多个数据库实例,所以建议统一在一个入口初始化(比如 EntryAbility / Application 启动时)。


6. StudentDao:增删改查(CRUD)

6.1 插入数据 insert()

ts 复制代码
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

export interface Student {
  no: string;
  name: string;
  math?: number;
  english?: number;
  updatedAt: number;
}

export class StudentDao {
  constructor(private store: relationalStore.RdbStore) {}

  async insertStudent(s: Student): Promise<number> {
    const values: relationalStore.ValuesBucket = {
      NO: s.no,
      NAME: s.name,
      MATH: s.math ?? null,
      ENGLISH: s.english ?? null,
      UPDATED_AT: s.updatedAt
    };

    try {
      // 冲突策略:学号唯一,重复就替换(你也可以改成 ON_CONFLICT_ABORT)
      return await this.store.insert('STUDENT', values, relationalStore.ConflictResolution.ON_CONFLICT_REPLACE);
    } catch (e) {
      const err = e as BusinessError;
      console.error(`insertStudent failed, code=${err.code}, msg=${err.message}`);
      throw err;
    }
  }
}

官方有个点很好用:RDB 不需要 flush,insert 就直接落盘了。你博客可以强调一下。


6.2 修改 update()(用谓词锁定行)

ts 复制代码
async updateScore(no: string, math: number, english: number): Promise<number> {
  const values: relationalStore.ValuesBucket = {
    MATH: math,
    ENGLISH: english,
    UPDATED_AT: Date.now()
  };

  const predicates = new relationalStore.RdbPredicates('STUDENT');
  predicates.equalTo('NO', no);

  return await new Promise((resolve, reject) => {
    this.store.update(values, predicates, (err, rows) => {
      if (err) reject(err);
      else resolve(rows);
    });
  });
}

6.3 删除 delete()

复制代码
async deleteByNo(no: string): Promise<number> {
  const predicates = new relationalStore.RdbPredicates('STUDENT');
  predicates.equalTo('NO', no);

  return await new Promise((resolve, reject) => {
    this.store.delete(predicates, (err, rows) => {
      if (err) reject(err);
      else resolve(rows);
    });
  });
}

6.4 查询 query() + ResultSet 遍历(重点:记得 close)

ts 复制代码
async queryByNo(no: string): Promise<Student | null> {
  const predicates = new relationalStore.RdbPredicates('STUDENT');
  predicates.equalTo('NO', no);

  const rs = await new Promise<relationalStore.ResultSet>((resolve, reject) => {
    this.store.query(predicates, ['NO', 'NAME', 'MATH', 'ENGLISH', 'UPDATED_AT'], (err, resultSet) => {
      if (err) reject(err);
      else resolve(resultSet);
    });
  });

  try {
    if (rs.goToNextRow()) {
      return {
        no: rs.getString(rs.getColumnIndex('NO')),
        name: rs.getString(rs.getColumnIndex('NAME')),
        math: rs.getLong(rs.getColumnIndex('MATH')),
        english: rs.getLong(rs.getColumnIndex('ENGLISH')),
        updatedAt: rs.getLong(rs.getColumnIndex('UPDATED_AT')),
      };
    }
    return null;
  } finally {
    rs.close(); // ✅ 非常重要
  }
}

7. 事务:批量写入/更新一定要用(性能 + 原子性)

比如:一次导入 1000 个学生成绩,推荐写法:

ts 复制代码
async importStudents(list: Student[]): Promise<void> {
  const tx = await this.store.createTransaction();
  try {
    for (const s of list) {
      await tx.insert('STUDENT', {
        NO: s.no,
        NAME: s.name,
        MATH: s.math ?? null,
        ENGLISH: s.english ?? null,
        UPDATED_AT: s.updatedAt
      }, relationalStore.ConflictResolution.ON_CONFLICT_REPLACE);
    }
    await tx.commit();
  } catch (e) {
    await tx.rollback();
    throw e;
  }
}

我一般会在博客里写一句"人话":

事务就像"打包提交",要么全成功,要么全失败,同时还能减少频繁落盘带来的耗时。


8. 大数据量查询:放进 TaskPool(避免 UI 卡死)

官方建议:大数据查询放 TaskPool。思路就是:耗时操作不要堵主线程

你可以在博客里写成"经验结论":

  • 单次不要超过 5000 条
  • 分页/分批
  • TaskPool 异步查,查完再回到 UI 更新

(如果你要我补一段 TaskPool + 分页查询的完整示例,我也能继续给你配好。)


9. FTS 全文检索(中文支持 ICU 分词器)

如果你做"笔记/文章/错题解析"这种场景,全局搜索非常常用。

RDB 支持 FTS,中文分词建议 icu zh_CN

建 FTS 表:

ts 复制代码
await this.store.execute(
  'CREATE VIRTUAL TABLE IF NOT EXISTS note_fts USING fts4(title, content, tokenize=icu zh_CN)'
);

查询:

ts 复制代码
const rs = await this.store.querySql(
  'SELECT title FROM note_fts WHERE note_fts MATCH ?',
  ['测试']
);

try {
  while (rs.goToNextRow()) {
    const title = rs.getValue(rs.getColumnIndex('title'));
    console.info(`hit: ${title}`);
  }
} finally {
  rs.close();
}

10. 数据库异常(14800011)怎么办?

官方提到:数据库在操作/存储中可能出现非预期异常(例如 14800011),需要重建并恢复数据

我一般博客里会提醒两句:

  • 平时要做 备份机制(手动备份也行)
  • 真遇到异常,走"重建 + restore"流程,保证业务能恢复

11. 备份 & 恢复(同路径)

备份:

ts 复制代码
this.store.backup('Backup.db', (err) => {
  if (err) console.error(`backup failed: ${err.code}`);
  else console.info('backup success');
});

恢复:

ts 复制代码
this.store.restore('Backup.db', (err) => {
  if (err) console.error(`restore failed: ${err.code}`);
  else console.info('restore success');
});

12. 删除数据库(慎用)

ts 复制代码
import { relationalStore } from '@kit.ArkData';

relationalStore.deleteRdbStore(this.context, 'Student.db', (err) => {
  if (err) console.error(`delete failed: ${err.code}`);
  else console.info('delete success');
});

13. 最后我给一个"选择建议总结"

  • 数据结构复杂、要筛选排序分页 → RelationalStore(RDB)
  • 配置开关类 → Preferences
  • 业务关系弱、想跨设备更友好 → KV-Store
相关推荐
YJlio5 小时前
Active Directory 工具学习笔记(10.14):第十章·实战脚本包——AdExplorer/AdInsight/AdRestore 一键化落地
服务器·笔记·学习
d111111111d5 小时前
江协科技-PID基本原理-(学习笔记)-主页有所有STM32外设的笔记基本都是万字起步。
笔记·科技·stm32·单片机·嵌入式硬件·学习
ℳ₯㎕ddzོꦿ࿐5 小时前
先立后破:Linux 下“新建管理员 → 验证 → 禁用 root 远程 SSH”的零翻车笔记
linux·笔记·ssh
LO嘉嘉VE5 小时前
学习笔记二十九:贝叶斯决策论
人工智能·笔记·学习
2401_834517075 小时前
AD学习笔记-33 丝印位号的调整
笔记·学习
hssfscv5 小时前
Mysql学习笔记——多表查询
笔记·学习·mysql
相思半5 小时前
机器学习模型实战全解析
大数据·人工智能·笔记·python·机器学习·数据挖掘·transformer
开开心心_Every6 小时前
Word转PDF工具,免费生成图片型文档
网络·笔记·pdf·word·powerpoint·excel·azure
hd51cc7 小时前
MFC基础知识
笔记·学习·mfc