鸿蒙ArkData事务与备份恢复实战:转账场景与数据救援

上篇讲了 CRUD 基础,这里攻两个难题:一是"多个操作要么全成功要么全失败"——典型场景是转账,扣款和加款必须原子完成;二是"数据丢了怎么救"——数据库异常、用户误删、升级失败,备份恢复是最后的保险。这两块都是出事时才显出价值的能力,平时不练,出事就抓瞎。

一、事务实战:转账场景

转账是最经典的事务场景:A 给 B 转 100 元,要扣 A 的余额、加 B 的余额、记一笔流水。这三步必须原子完成——任何一步失败,全部回滚。如果不用事务,扣了 A 的钱却没加给 B,就是资损事故。

1.1 用 createTransaction 实现转账

import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN = 0x0000;

async function transfer(store: relationalStore.RdbStore, fromId: number, toId: number, amount: number) {
  try {
    // 创建事务对象,开始事务
    const transaction = await store.createTransaction();
    try {
      // 1. 查询转出方余额
      const fromPredicates = new relationalStore.RdbPredicates('ACCOUNT');
      fromPredicates.equalTo('ID', fromId);
      const fromRs = await transaction.query(fromPredicates, ['BALANCE']);
      fromRs.goToNextRow();
      const fromBalance = fromRs.getDouble(fromRs.getColumnIndex('BALANCE'));
      fromRs.close();

      // 2. 余额不足,回滚
      if (fromBalance < amount) {
        await transaction.rollback();
        hilog.info(DOMAIN, 'rdb', '余额不足,转账取消');
        return false;
      }

      // 3. 扣款
      const deductPredicates = new relationalStore.RdbPredicates('ACCOUNT');
      deductPredicates.equalTo('ID', fromId);
      await transaction.update({ BALANCE: fromBalance - amount }, deductPredicates);

      // 4. 加款
      const toPredicates = new relationalStore.RdbPredicates('ACCOUNT');
      toPredicates.equalTo('ID', toId);
      const toRs = await transaction.query(toPredicates, ['BALANCE']);
      toRs.goToNextRow();
      const toBalance = toRs.getDouble(toRs.getColumnIndex('BALANCE'));
      toRs.close();
      await transaction.update({ BALANCE: toBalance + amount }, toPredicates);

      // 5. 记流水
      await transaction.insert('TRANSACTION_LOG', {
        FROM_ID: fromId,
        TO_ID: toId,
        AMOUNT: amount,
        TIME: Date.now()
      });

      // 6. 全部成功,提交
      await transaction.commit();
      hilog.info(DOMAIN, 'rdb', '转账成功');
      return true;
    } catch (error) {
      // 业务异常,回滚
      await transaction.rollback();
      hilog.error(DOMAIN, 'rdb', `转账失败,已回滚: ${(error as BusinessError).message}`);
      return false;
    }
  } catch (error) {
    hilog.error(DOMAIN, 'rdb', `创建事务失败: ${(error as BusinessError).message}`);
    return false;
  }
}

1.2 事务的三种类型

createTransaction 支持配置事务类型,默认 DEFERRED:

类型 行为 适用场景
DEFERRED(默认) 延迟获锁,不立即冲突 大多数场景
IMMEDIATE 立即获取写锁 确定要写的场景
EXCLUSIVE 独占锁,阻塞其他读写 需要强隔离的场景
// 指定 IMMEDIATE 事务
const transaction = await store.createTransaction({ type: relationalStore.TransactionType.IMMEDIATE });

1.3 事务里也能用冲突解决

插入时可以指定冲突解决策略:

const rowId = await transaction.insert(
  'EMPLOYEE',
  { NAME: 'Lisa', AGE: 18, SALARY: 100.5 },
  relationalStore.ConflictResolution.ON_CONFLICT_REPLACE  // 冲突时替换
);

1.4 事务里执行原生 SQL

// 事务里也能跑原生 SQL,参数化绑定
await transaction.execute('DELETE FROM EMPLOYEE WHERE age = ? OR age = ?', [21, 20]);

二、备份实战:给数据上个保险

数据库异常(错误码 14800011)、用户误删、升级失败,这些场景下有备份就能救回来。关系型数据库支持手动备份和自动备份(自动备份仅系统应用可用)。

2.1 手动备份

async function backupDatabase(store: relationalStore.RdbStore) {
  // 'Backup.db' 是备份文件名,默认在 RdbStore 同路径下
  // 也可指定路径:customDir + 'Backup.db'
  store.backup('Backup.db', (err: BusinessError) => {
    if (err) {
      hilog.error(DOMAIN, 'rdb', `备份失败: ${err.message}`);
      return;
    }
    hilog.info(DOMAIN, 'rdb', '备份成功');
  });
}

2.2 什么时候该备份

几个建议备份的时机:

  • 应用升级前:升级有风险,先备份。升级失败可以用备份回退
  • 批量删除/修改前:大操作前备份,操作错了能恢复
  • 定期备份:重要数据每天/每周备份一次
  • 用户主动触发:提供"导出备份"功能让用户安心

三、恢复实战:从备份还原

async function restoreDatabase(store: relationalStore.RdbStore) {
  // 从 Backup.db 恢复数据
  store.restore('Backup.db', (err: BusinessError) => {
    if (err) {
      hilog.error(DOMAIN, 'rdb', `恢复失败: ${err.message}`);
      return;
    }
    hilog.info(DOMAIN, 'rdb', '恢复成功');
  });
}

restore 会用备份文件的内容覆盖当前数据库。注意:恢复是覆盖操作,当前数据库里备份之后新增的数据会丢失。所以恢复前要想清楚,或者先对当前数据再做一次备份。

四、数据库异常重建:错误码 14800011

官方文档特别提到:数据库操作或存储过程中,可能因为各种原因抛出 14800011 异常。这时需要对数据库重建并恢复数据。

一个完整的异常处理流程:

async function safeOperation(store: relationalStore.RdbStore, operation: () => Promise<void>) {
  try {
    await operation();
  } catch (error) {
    const err = error as BusinessError;
    if (err.code === 14800011) {
      // 数据库异常,需要重建
      hilog.error(DOMAIN, 'rdb', '数据库异常,尝试重建');
      
      // 1. 先尝试从备份恢复
      try {
        await new Promise<void>((resolve, reject) => {
          store.restore('Backup.db', (e) => {
            if (e) reject(e);
            else resolve();
          });
        });
        hilog.info(DOMAIN, 'rdb', '从备份恢复成功,重试操作');
        await operation();  // 恢复后重试
      } catch (restoreErr) {
        hilog.error(DOMAIN, 'rdb', `恢复失败: ${(restoreErr as BusinessError).message}`);
        // 2. 备份也救不了,只能重建空库
        // 走 initDatabase 流程重新建库建表
      }
    } else {
      throw error;  // 其他错误向上抛
    }
  }
}

五、事务使用的几个原则

5.1 事务要短

事务持有锁,时间越长越容易阻塞其他操作。事务里不要做耗时操作(网络请求、大文件读写、复杂计算)。把准备工作做完,事务里只做数据库操作。

5.2 必须处理回滚

事务里任何一步失败,都要 rollback()。忘了回滚,事务会一直挂着,锁不释放,其他写操作全阻塞。

5.3 结果集在事务里也要 close

事务里查询返回的结果集,用完一样要 close()。事务提交/回滚不会自动关闭结果集。

5.4 不要嵌套事务

关系型数据库同一时间只支持一个写操作,嵌套事务会死锁。需要多段操作时,串行执行多个独立事务。

六、备份恢复的实战策略

给一个生产环境建议的备份策略:

场景 策略
应用启动 检查上次备份是否存在,异常时自动恢复
版本升级前 强制备份一次
用户清空数据前 备份一次,提供"撤销"窗口
定期(如每天首次启动) 静默备份,保留最近 N 份
关键操作(转账、支付) 操作前备份,操作后确认

七、一个完整的"安全转账"封装

把事务、备份、异常处理组合起来,封装一个生产可用的转账函数:

async function safeTransfer(
  store: relationalStore.RdbStore,
  fromId: number, toId: number, amount: number
): Promise<boolean> {
  // 1. 操作前备份
  await new Promise<void>((resolve) => {
    store.backup('pre_transfer.db', () => resolve());
  });

  // 2. 执行转账事务
  const success = await transfer(store, fromId, toId, amount);
  
  if (success) {
    // 3. 成功后备份
    await new Promise<void>((resolve) => {
      store.backup('post_transfer.db', () => resolve());
    });
    return true;
  } else {
    return false;
  }
}

八、总结一下下

事务和备份恢复是数据库能力的"安全网",平时不起眼,出事时救命。

  1. 事务保证原子性:多步操作用 createTransaction,失败 rollback,成功 commit
  2. 事务要短、要处理回滚、不嵌套:避免死锁和长时间阻塞
  3. 关键操作前备份:升级、批量删除、转账前都该备份
  4. 14800011 异常要重建:捕获这个错误码,尝试从备份恢复
  5. 恢复是覆盖操作:恢复前考虑当前数据是否要先备份
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐