RDB事务与并发:HarmonyOS开发数据库高级操作

RDB事务与并发:HarmonyOS开发数据库高级操作

一、背景与动机

你有没有遇到过这种场景:转账操作,A账户扣100块,B账户加100块。如果扣款成功但加款失败,钱就凭空消失了。这就是数据一致性问题。

再比如:秒杀场景,100个用户同时抢10件商品。如果并发控制没做好,可能卖出100件,库存变成-90。这是并发安全问题。

事务就是解决这些问题的利器。它把多个数据库操作打包成一个原子单元------要么全部成功,要么全部失败,不存在中间状态。

鸿蒙的RDB提供了完整的事务支持,包括:

  • beginTransaction:开启事务
  • commit:提交事务
  • rollBack:回滚事务

但事务只是第一步。并发场景下,还有隔离级别、锁机制、死锁预防这些要考虑。这篇我们就深入聊聊。

二、核心原理

事务的ACID特性

#mermaid-svg-28ENDvEPfgRzvGPD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-28ENDvEPfgRzvGPD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-28ENDvEPfgRzvGPD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-28ENDvEPfgRzvGPD .error-icon{fill:#552222;}#mermaid-svg-28ENDvEPfgRzvGPD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-28ENDvEPfgRzvGPD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-28ENDvEPfgRzvGPD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-28ENDvEPfgRzvGPD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-28ENDvEPfgRzvGPD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-28ENDvEPfgRzvGPD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-28ENDvEPfgRzvGPD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-28ENDvEPfgRzvGPD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-28ENDvEPfgRzvGPD .marker.cross{stroke:#333333;}#mermaid-svg-28ENDvEPfgRzvGPD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-28ENDvEPfgRzvGPD p{margin:0;}#mermaid-svg-28ENDvEPfgRzvGPD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-28ENDvEPfgRzvGPD .cluster-label text{fill:#333;}#mermaid-svg-28ENDvEPfgRzvGPD .cluster-label span{color:#333;}#mermaid-svg-28ENDvEPfgRzvGPD .cluster-label span p{background-color:transparent;}#mermaid-svg-28ENDvEPfgRzvGPD .label text,#mermaid-svg-28ENDvEPfgRzvGPD span{fill:#333;color:#333;}#mermaid-svg-28ENDvEPfgRzvGPD .node rect,#mermaid-svg-28ENDvEPfgRzvGPD .node circle,#mermaid-svg-28ENDvEPfgRzvGPD .node ellipse,#mermaid-svg-28ENDvEPfgRzvGPD .node polygon,#mermaid-svg-28ENDvEPfgRzvGPD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-28ENDvEPfgRzvGPD .rough-node .label text,#mermaid-svg-28ENDvEPfgRzvGPD .node .label text,#mermaid-svg-28ENDvEPfgRzvGPD .image-shape .label,#mermaid-svg-28ENDvEPfgRzvGPD .icon-shape .label{text-anchor:middle;}#mermaid-svg-28ENDvEPfgRzvGPD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-28ENDvEPfgRzvGPD .rough-node .label,#mermaid-svg-28ENDvEPfgRzvGPD .node .label,#mermaid-svg-28ENDvEPfgRzvGPD .image-shape .label,#mermaid-svg-28ENDvEPfgRzvGPD .icon-shape .label{text-align:center;}#mermaid-svg-28ENDvEPfgRzvGPD .node.clickable{cursor:pointer;}#mermaid-svg-28ENDvEPfgRzvGPD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-28ENDvEPfgRzvGPD .arrowheadPath{fill:#333333;}#mermaid-svg-28ENDvEPfgRzvGPD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-28ENDvEPfgRzvGPD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-28ENDvEPfgRzvGPD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-28ENDvEPfgRzvGPD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-28ENDvEPfgRzvGPD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-28ENDvEPfgRzvGPD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-28ENDvEPfgRzvGPD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-28ENDvEPfgRzvGPD .cluster text{fill:#333;}#mermaid-svg-28ENDvEPfgRzvGPD .cluster span{color:#333;}#mermaid-svg-28ENDvEPfgRzvGPD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-28ENDvEPfgRzvGPD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-28ENDvEPfgRzvGPD rect.text{fill:none;stroke-width:0;}#mermaid-svg-28ENDvEPfgRzvGPD .icon-shape,#mermaid-svg-28ENDvEPfgRzvGPD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-28ENDvEPfgRzvGPD .icon-shape p,#mermaid-svg-28ENDvEPfgRzvGPD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-28ENDvEPfgRzvGPD .icon-shape .label rect,#mermaid-svg-28ENDvEPfgRzvGPD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-28ENDvEPfgRzvGPD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-28ENDvEPfgRzvGPD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-28ENDvEPfgRzvGPD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-28ENDvEPfgRzvGPD .primary>*{fill:#4CAF50!important;stroke:#388E3C!important;color:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .primary span{fill:#4CAF50!important;stroke:#388E3C!important;color:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .primary tspan{fill:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .success>*{fill:#2196F3!important;stroke:#1976D2!important;color:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .success span{fill:#2196F3!important;stroke:#1976D2!important;color:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .success tspan{fill:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .warning>*{fill:#FF9800!important;stroke:#F57C00!important;color:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .warning span{fill:#FF9800!important;stroke:#F57C00!important;color:#fff!important;}#mermaid-svg-28ENDvEPfgRzvGPD .warning tspan{fill:#fff!important;} 是

事务开始
操作1: 扣款
操作2: 加款
所有操作成功?
COMMIT

提交事务
数据持久化

其他事务可见
ROLLBACK

回滚事务
数据恢复

如同操作从未发生
ACID特性
原子性 Atomic

全部成功或全部失败
一致性 Consistent

数据始终合法
隔离性 Isolated

事务互不干扰
持久性 Durable

提交后永久保存

隔离级别

SQLite支持四种隔离级别,鸿蒙RDB默认使用READ_UNCOMMITTED

隔离级别 脏读 不可重复读 幻读 说明
READ_UNCOMMITTED 可能 可能 可能 最弱,性能最好
READ_COMMITTED 不可能 可能 可能 读已提交数据
REPEATABLE_READ 不可能 不可能 可能 可重复读
SERIALIZABLE 不可能 不可能 不可能 最强,性能最差

实际开发中,大部分场景用默认级别就够了。如果业务对一致性要求极高,可以在事务中显式加锁。

并发模型

SQLite使用文件锁实现并发控制:
#mermaid-svg-yBfb3OXwnPKcSWfk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yBfb3OXwnPKcSWfk .error-icon{fill:#552222;}#mermaid-svg-yBfb3OXwnPKcSWfk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yBfb3OXwnPKcSWfk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yBfb3OXwnPKcSWfk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yBfb3OXwnPKcSWfk .marker.cross{stroke:#333333;}#mermaid-svg-yBfb3OXwnPKcSWfk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yBfb3OXwnPKcSWfk p{margin:0;}#mermaid-svg-yBfb3OXwnPKcSWfk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yBfb3OXwnPKcSWfk .cluster-label text{fill:#333;}#mermaid-svg-yBfb3OXwnPKcSWfk .cluster-label span{color:#333;}#mermaid-svg-yBfb3OXwnPKcSWfk .cluster-label span p{background-color:transparent;}#mermaid-svg-yBfb3OXwnPKcSWfk .label text,#mermaid-svg-yBfb3OXwnPKcSWfk span{fill:#333;color:#333;}#mermaid-svg-yBfb3OXwnPKcSWfk .node rect,#mermaid-svg-yBfb3OXwnPKcSWfk .node circle,#mermaid-svg-yBfb3OXwnPKcSWfk .node ellipse,#mermaid-svg-yBfb3OXwnPKcSWfk .node polygon,#mermaid-svg-yBfb3OXwnPKcSWfk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yBfb3OXwnPKcSWfk .rough-node .label text,#mermaid-svg-yBfb3OXwnPKcSWfk .node .label text,#mermaid-svg-yBfb3OXwnPKcSWfk .image-shape .label,#mermaid-svg-yBfb3OXwnPKcSWfk .icon-shape .label{text-anchor:middle;}#mermaid-svg-yBfb3OXwnPKcSWfk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yBfb3OXwnPKcSWfk .rough-node .label,#mermaid-svg-yBfb3OXwnPKcSWfk .node .label,#mermaid-svg-yBfb3OXwnPKcSWfk .image-shape .label,#mermaid-svg-yBfb3OXwnPKcSWfk .icon-shape .label{text-align:center;}#mermaid-svg-yBfb3OXwnPKcSWfk .node.clickable{cursor:pointer;}#mermaid-svg-yBfb3OXwnPKcSWfk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yBfb3OXwnPKcSWfk .arrowheadPath{fill:#333333;}#mermaid-svg-yBfb3OXwnPKcSWfk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yBfb3OXwnPKcSWfk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yBfb3OXwnPKcSWfk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yBfb3OXwnPKcSWfk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yBfb3OXwnPKcSWfk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yBfb3OXwnPKcSWfk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yBfb3OXwnPKcSWfk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yBfb3OXwnPKcSWfk .cluster text{fill:#333;}#mermaid-svg-yBfb3OXwnPKcSWfk .cluster span{color:#333;}#mermaid-svg-yBfb3OXwnPKcSWfk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yBfb3OXwnPKcSWfk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yBfb3OXwnPKcSWfk rect.text{fill:none;stroke-width:0;}#mermaid-svg-yBfb3OXwnPKcSWfk .icon-shape,#mermaid-svg-yBfb3OXwnPKcSWfk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yBfb3OXwnPKcSWfk .icon-shape p,#mermaid-svg-yBfb3OXwnPKcSWfk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yBfb3OXwnPKcSWfk .icon-shape .label rect,#mermaid-svg-yBfb3OXwnPKcSWfk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yBfb3OXwnPKcSWfk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yBfb3OXwnPKcSWfk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yBfb3OXwnPKcSWfk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 获取锁成功
锁被占用
线程1
RdbStore实例1
线程2
RdbStore实例2
数据库文件锁
执行操作
等待或失败
释放锁

关键点:SQLite是数据库级别的锁,不是行级锁。这意味着写操作会锁住整个数据库文件,其他写操作必须等待。

三、代码实战

3.1 基础事务操作

先看个简单的转账例子:

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

/**
 * 账户表初始化
 */
async function initAccountTable(rdbStore: relationalStore.RdbStore): Promise<void> {
  await rdbStore.executeSql(`
    CREATE TABLE IF NOT EXISTS accounts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_name TEXT UNIQUE NOT NULL,
      balance REAL NOT NULL CHECK(balance >= 0),
      updated_at INTEGER NOT NULL
    )
  `);
  
  // 初始化测试账户
  await rdbStore.executeSql(`INSERT OR IGNORE INTO accounts (user_name, balance, updated_at) VALUES ('Alice', 1000, ${Date.now()})`);
  await rdbStore.executeSql(`INSERT OR IGNORE INTO accounts (user_name, balance, updated_at) VALUES ('Bob', 500, ${Date.now()})`);
}

/**
 * 转账操作(事务版本)
 * @param fromUser 转出账户
 * @param toUser 转入账户
 * @param amount 金额
 */
async function transfer(
  rdbStore: relationalStore.RdbStore,
  fromUser: string,
  toUser: string,
  amount: number
): Promise<boolean> {
  // 开启事务
  await rdbStore.beginTransaction();
  
  try {
    // 1. 查询转出账户余额
    const fromPredicates = new relationalStore.RdbPredicates('accounts');
    fromPredicates.equalTo('user_name', fromUser);
    const fromResultSet = await rdbStore.query(fromPredicates);
    
    if (!fromResultSet.goToFirstRow()) {
      throw new Error(`账户 ${fromUser} 不存在`);
    }
    
    const fromBalance = fromResultSet.getDouble(fromResultSet.getColumnIndex('balance'));
    const fromId = fromResultSet.getLong(fromResultSet.getColumnIndex('id'));
    fromResultSet.close();
    
    // 2. 检查余额是否充足
    if (fromBalance < amount) {
      throw new Error(`余额不足,当前余额: ${fromBalance}`);
    }
    
    // 3. 查询转入账户
    const toPredicates = new relationalStore.RdbPredicates('accounts');
    toPredicates.equalTo('user_name', toUser);
    const toResultSet = await rdbStore.query(toPredicates);
    
    if (!toResultSet.goToFirstRow()) {
      throw new Error(`账户 ${toUser} 不存在`);
    }
    
    const toId = toResultSet.getLong(toResultSet.getColumnIndex('id'));
    toResultSet.close();
    
    // 4. 扣款
    const deductBucket: relationalStore.ValuesBucket = {
      'balance': fromBalance - amount,
      'updated_at': Date.now()
    };
    const deductPredicates = new relationalStore.RdbPredicates('accounts');
    deductPredicates.equalTo('id', fromId);
    await rdbStore.update(deductBucket, deductPredicates);
    
    // 5. 加款(模拟可能失败的情况)
    const addToBucket: relationalStore.ValuesBucket = {
      'balance': relationalStore.RelationalStoreValue // 需要查询当前余额再加
    };
    
    // 使用SQL更新更简洁
    await rdbStore.executeSql(
      'UPDATE accounts SET balance = balance + ?, updated_at = ? WHERE id = ?',
      [amount, Date.now(), toId]
    );
    
    // 6. 提交事务
    await rdbStore.commit();
    
    console.info(`转账成功: ${fromUser} -> ${toUser}, 金额: ${amount}`);
    return true;
    
  } catch (err) {
    // 回滚事务
    await rdbStore.rollBack();
    console.error('转账失败,已回滚:', err);
    return false;
  }
}

/**
 * 查询账户余额
 */
async function getBalance(rdbStore: relationalStore.RdbStore, userName: string): Promise<number> {
  const predicates = new relationalStore.RdbPredicates('accounts');
  predicates.equalTo('user_name', userName);
  
  const resultSet = await rdbStore.query(predicates);
  if (resultSet.goToFirstRow()) {
    const balance = resultSet.getDouble(resultSet.getColumnIndex('balance'));
    resultSet.close();
    return balance;
  }
  resultSet.close();
  throw new Error(`账户 ${userName} 不存在`);
}

// UI示例
@Entry
@Component
struct TransferDemo {
  @State aliceBalance: number = 0;
  @State bobBalance: number = 0;
  @State transferAmount: number = 100;
  @State message: string = '';
  
  private rdbStore: relationalStore.RdbStore | null = null;

  async aboutToAppear() {
    const context = getContext(this) as common.UIAbilityContext;
    const config: relationalStore.StoreConfig = {
      name: 'Bank.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };
    this.rdbStore = await relationalStore.getRdbStore(context, config);
    await initAccountTable(this.rdbStore);
    await this.refreshBalances();
  }

  async refreshBalances() {
    if (!this.rdbStore) return;
    this.aliceBalance = await getBalance(this.rdbStore, 'Alice');
    this.bobBalance = await getBalance(this.rdbStore, 'Bob');
  }

  build() {
    Column({ space: 20 }) {
      Text('银行转账演示')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      // 账户余额显示
      Row() {
        Text(`Alice: ¥${this.aliceBalance.toFixed(2)}`)
        Text(`Bob: ¥${this.bobBalance.toFixed(2)}`)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)

      // 转账金额输入
      Row() {
        Text('转账金额:')
        TextInput({ text: this.transferAmount.toString() })
          .type(InputType.Number)
          .width(150)
          .onChange((value: string) => {
            this.transferAmount = parseFloat(value) || 0;
          })
      }

      // 转账按钮
      Button('Alice -> Bob 转账')
        .width('100%')
        .onClick(async () => {
          if (!this.rdbStore) return;
          
          const success = await transfer(this.rdbStore, 'Alice', 'Bob', this.transferAmount);
          this.message = success ? '转账成功' : '转账失败';
          await this.refreshBalances();
        })

      // 消息提示
      if (this.message) {
        Text(this.message)
          .fontColor(this.message.includes('成功') ? '#4CAF50' : '#F44336')
      }
    }
    .width('100%')
    .padding(20)
  }
}

这个转账例子展示了事务的核心价值:原子性保证。如果扣款成功但加款失败,事务会自动回滚,数据恢复原状。

3.2 进阶用法:批量操作与嵌套事务

实际场景中,事务往往更复杂------批量导入数据、嵌套事务、保存点等:

arkts 复制代码
/**
 * 批量导入数据(事务优化)
 * 批量操作一定要用事务,否则性能极差
 */
async function batchImportNotes(
  rdbStore: relationalStore.RdbStore,
  notes: Array<{ title: string, content: string }>
): Promise<number> {
  // 不用事务的版本(慢)
  // for (const note of notes) {
  //   await rdbStore.insert('notes', note); // 每次insert都是一次事务
  // }
  
  // 用事务的版本(快)
  await rdbStore.beginTransaction();
  
  try {
    let successCount = 0;
    
    for (const note of notes) {
      const valueBucket: relationalStore.ValuesBucket = {
        'title': note.title,
        'content': note.content,
        'created_at': Date.now(),
        'updated_at': Date.now(),
        'is_deleted': 0
      };
      
      await rdbStore.insert('notes', valueBucket);
      successCount++;
    }
    
    await rdbStore.commit();
    console.info(`批量导入完成: ${successCount} 条`);
    return successCount;
    
  } catch (err) {
    await rdbStore.rollBack();
    console.error('批量导入失败:', err);
    throw err;
  }
}

/**
 * 带保存点的事务
 * 保存点允许部分回滚
 */
async function importWithSavepoint(
  rdbStore: relationalStore.RdbStore,
  items: Array<{ id: number, data: string }>
): Promise<{ success: number, failed: number }> {
  await rdbStore.beginTransaction();
  
  let success = 0;
  let failed = 0;
  
  try {
    for (const item of items) {
      try {
        // 创建保存点
        await rdbStore.executeSql(`SAVEPOINT item_${item.id}`);
        
        // 插入数据
        const valueBucket: relationalStore.ValuesBucket = {
          'id': item.id,
          'data': item.data
        };
        await rdbStore.insert('test_items', valueBucket);
        
        // 释放保存点(可选)
        await rdbStore.executeSql(`RELEASE SAVEPOINT item_${item.id}`);
        success++;
        
      } catch (itemErr) {
        // 回滚到保存点,只撤销当前item的操作
        await rdbStore.executeSql(`ROLLBACK TO SAVEPOINT item_${item.id}`);
        failed++;
        console.warn(`Item ${item.id} 导入失败:`, itemErr);
      }
    }
    
    await rdbStore.commit();
    return { success, failed };
    
  } catch (err) {
    await rdbStore.rollBack();
    throw err;
  }
}

/**
 * 订单创建(多表关联事务)
 */
interface Order {
  orderId: string;
  userId: number;
  items: Array<{ productId: number, quantity: number, price: number }>;
}

async function createOrder(
  rdbStore: relationalStore.RdbStore,
  order: Order
): Promise<string> {
  await rdbStore.beginTransaction();
  
  try {
    // 1. 创建订单主表
    const totalAmount = order.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    
    const orderBucket: relationalStore.ValuesBucket = {
      'order_id': order.orderId,
      'user_id': order.userId,
      'total_amount': totalAmount,
      'status': 'pending',
      'created_at': Date.now()
    };
    await rdbStore.insert('orders', orderBucket);
    
    // 2. 创建订单明细
    for (const item of order.items) {
      const itemBucket: relationalStore.ValuesBucket = {
        'order_id': order.orderId,
        'product_id': item.productId,
        'quantity': item.quantity,
        'price': item.price,
        'subtotal': item.price * item.quantity
      };
      await rdbStore.insert('order_items', itemBucket);
      
      // 3. 扣减库存
      await rdbStore.executeSql(
        'UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?',
        [item.quantity, item.productId, item.quantity]
      );
      
      // 检查是否扣减成功
      const stockResult = await rdbStore.querySql(
        'SELECT changes() as affected'
      );
      if (stockResult.goToFirstRow()) {
        const affected = stockResult.getLong(0);
        stockResult.close();
        if (affected === 0) {
          throw new Error(`商品 ${item.productId} 库存不足`);
        }
      } else {
        stockResult.close();
        throw new Error('库存检查失败');
      }
    }
    
    // 4. 提交事务
    await rdbStore.commit();
    console.info(`订单 ${order.orderId} 创建成功`);
    return order.orderId;
    
  } catch (err) {
    await rdbStore.rollBack();
    console.error('订单创建失败:', err);
    throw err;
  }
}

3.3 完整示例:并发安全与锁机制

下面看一个秒杀场景,演示如何处理并发:

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

/**
 * 商品表初始化
 */
async function initSeckillTable(rdbStore: relationalStore.RdbStore): Promise<void> {
  await rdbStore.executeSql(`
    CREATE TABLE IF NOT EXISTS seckill_products (
      id INTEGER PRIMARY KEY,
      name TEXT NOT NULL,
      stock INTEGER NOT NULL CHECK(stock >= 0),
      price REAL NOT NULL,
      version INTEGER DEFAULT 0
    )
  `);
  
  await rdbStore.executeSql(`
    CREATE TABLE IF NOT EXISTS seckill_orders (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      product_id INTEGER NOT NULL,
      user_id INTEGER NOT NULL,
      created_at INTEGER NOT NULL,
      FOREIGN KEY (product_id) REFERENCES seckill_products(id)
    )
  `);
  
  // 初始化测试商品
  await rdbStore.executeSql(`
    INSERT OR REPLACE INTO seckill_products (id, name, stock, price, version)
    VALUES (1, '秒杀商品', 10, 99.9, 0)
  `);
}

/**
 * 秒杀下单(乐观锁版本)
 * 乐观锁:不真正加锁,通过版本号检测冲突
 */
async function seckillOrderOptimistic(
  rdbStore: relationalStore.RdbStore,
  productId: number,
  userId: number
): Promise<{ success: boolean, message: string }> {
  const maxRetries = 3; // 最大重试次数
  
  for (let retry = 0; retry < maxRetries; retry++) {
    await rdbStore.beginTransaction();
    
    try {
      // 1. 查询商品信息和版本号
      const productResult = await rdbStore.querySql(
        'SELECT name, stock, version FROM seckill_products WHERE id = ?',
        [productId]
      );
      
      if (!productResult.goToFirstRow()) {
        productResult.close();
        await rdbStore.rollBack();
        return { success: false, message: '商品不存在' };
      }
      
      const productName = productResult.getString(0);
      const stock = productResult.getLong(1);
      const version = productResult.getLong(2);
      productResult.close();
      
      // 2. 检查库存
      if (stock <= 0) {
        await rdbStore.rollBack();
        return { success: false, message: '库存不足' };
      }
      
      // 3. 扣减库存(带版本号检查)
      // UPDATE ... WHERE version = 旧版本
      // 如果version不匹配,说明被其他事务修改过,affectedRows为0
      await rdbStore.executeSql(`
        UPDATE seckill_products 
        SET stock = stock - 1, version = version + 1 
        WHERE id = ? AND version = ?
      `, [productId, version]);
      
      // 4. 检查是否更新成功
      const updateResult = await rdbStore.querySql('SELECT changes() as affected');
      let affected = 0;
      if (updateResult.goToFirstRow()) {
        affected = updateResult.getLong(0);
      }
      updateResult.close();
      
      if (affected === 0) {
        // 版本冲突,回滚并重试
        await rdbStore.rollBack();
        console.info(`用户 ${userId} 遇到版本冲突,重试 ${retry + 1}/${maxRetries}`);
        continue; // 继续下一次重试
      }
      
      // 5. 创建订单
      const orderBucket: relationalStore.ValuesBucket = {
        'product_id': productId,
        'user_id': userId,
        'created_at': Date.now()
      };
      await rdbStore.insert('seckill_orders', orderBucket);
      
      // 6. 提交事务
      await rdbStore.commit();
      
      return { success: true, message: `抢购成功!商品: ${productName}` };
      
    } catch (err) {
      await rdbStore.rollBack();
      console.error('秒杀异常:', err);
      return { success: false, message: '系统异常' };
    }
  }
  
  return { success: false, message: '系统繁忙,请稍后重试' };
}

/**
 * 秒杀下单(悲观锁版本)
 * 悲观锁:先加锁,确保独占访问
 * SQLite不支持行级锁,这里用BEGIN EXCLUSIVE模拟
 */
async function seckillOrderPessimistic(
  rdbStore: relationalStore.RdbStore,
  productId: number,
  userId: number
): Promise<{ success: boolean, message: string }> {
  // 使用EXCLUSIVE事务,获取独占锁
  await rdbStore.executeSql('BEGIN EXCLUSIVE');
  
  try {
    // 1. 查询商品
    const productResult = await rdbStore.querySql(
      'SELECT name, stock FROM seckill_products WHERE id = ?',
      [productId]
    );
    
    if (!productResult.goToFirstRow()) {
      productResult.close();
      await rdbStore.executeSql('ROLLBACK');
      return { success: false, message: '商品不存在' };
    }
    
    const productName = productResult.getString(0);
    const stock = productResult.getLong(1);
    productResult.close();
    
    // 2. 检查库存
    if (stock <= 0) {
      await rdbStore.executeSql('ROLLBACK');
      return { success: false, message: '库存不足' };
    }
    
    // 3. 扣减库存
    await rdbStore.executeSql(
      'UPDATE seckill_products SET stock = stock - 1 WHERE id = ?',
      [productId]
    );
    
    // 4. 创建订单
    const orderBucket: relationalStore.ValuesBucket = {
      'product_id': productId,
      'user_id': userId,
      'created_at': Date.now()
    };
    await rdbStore.insert('seckill_orders', orderBucket);
    
    // 5. 提交
    await rdbStore.executeSql('COMMIT');
    
    return { success: true, message: `抢购成功!商品: ${productName}` };
    
  } catch (err) {
    await rdbStore.executeSql('ROLLBACK');
    console.error('秒杀异常:', err);
    return { success: false, message: '系统异常' };
  }
}

/**
 * 查询秒杀结果统计
 */
async function getSeckillStats(rdbStore: relationalStore.RdbStore): Promise<{
  stock: number,
  orderCount: number
}> {
  const stockResult = await rdbStore.querySql(
    'SELECT stock FROM seckill_products WHERE id = 1'
  );
  let stock = 0;
  if (stockResult.goToFirstRow()) {
    stock = stockResult.getLong(0);
  }
  stockResult.close();
  
  const orderResult = await rdbStore.querySql(
    'SELECT COUNT(*) FROM seckill_orders WHERE product_id = 1'
  );
  let orderCount = 0;
  if (orderResult.goToFirstRow()) {
    orderCount = orderResult.getLong(0);
  }
  orderResult.close();
  
  return { stock, orderCount };
}

// 并发测试UI
@Entry
@Component
struct SeckillDemo {
  @State stock: number = 10;
  @State orderCount: number = 0;
  @State messages: string[] = [];
  
  private rdbStore: relationalStore.RdbStore | null = null;

  async aboutToAppear() {
    const context = getContext(this) as common.UIAbilityContext;
    const config: relationalStore.StoreConfig = {
      name: 'Seckill.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };
    this.rdbStore = await relationalStore.getRdbStore(context, config);
    await initSeckillTable(this.rdbStore);
    await this.refreshStats();
  }

  async refreshStats() {
    if (!this.rdbStore) return;
    const stats = await getSeckillStats(this.rdbStore);
    this.stock = stats.stock;
    this.orderCount = stats.orderCount;
  }

  // 模拟并发抢购
  async simulateConcurrentPurchase() {
    if (!this.rdbStore) return;
    
    // 模拟10个用户同时抢购
    const promises: Promise<void>[] = [];
    
    for (let i = 1; i <= 10; i++) {
      const userId = i;
      promises.push(
        seckillOrderOptimistic(this.rdbStore!, 1, userId).then(result => {
          this.messages.unshift(`用户${userId}: ${result.message}`);
        })
      );
    }
    
    // 并发执行
    await Promise.all(promises);
    await this.refreshStats();
  }

  build() {
    Column({ space: 15 }) {
      Text('秒杀并发测试')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      Row() {
        Text(`库存: ${this.stock}`)
        Text(`订单数: ${this.orderCount}`)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)

      Button('模拟10人并发抢购')
        .width('100%')
        .onClick(() => {
          this.simulateConcurrentPurchase();
        })

      Button('重置数据')
        .width('100%')
        .onClick(async () => {
          if (!this.rdbStore) return;
          await this.rdbStore.executeSql('UPDATE seckill_products SET stock = 10, version = 0 WHERE id = 1');
          await this.rdbStore.executeSql('DELETE FROM seckill_orders');
          this.messages = [];
          await this.refreshStats();
        })

      // 结果列表
      List({ space: 5 }) {
        ForEach(this.messages, (msg: string, index: number) => {
          ListItem() {
            Text(msg)
              .fontSize(12)
              .fontColor(msg.includes('成功') ? '#4CAF50' : '#F44336')
          }
        })
      }
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

这个秒杀例子展示了两种并发控制策略:

乐观锁

  • 不真正加锁,通过版本号检测冲突
  • 冲突时重试,适合冲突不频繁的场景
  • 性能好,但需要业务层处理重试

悲观锁

  • 先获取独占锁,确保独占访问
  • 适合冲突频繁的场景
  • 性能较差,可能造成阻塞

四、踩坑与注意事项

1. 事务未正确结束

最常见的问题------开启事务后忘记提交或回滚:

arkts 复制代码
// ❌ 危险:事务未结束
async function badTransaction() {
  await rdbStore.beginTransaction();
  await rdbStore.insert('table', data);
  // 函数结束,事务还在!后续操作可能在这个事务中执行
}

// ✅ 正确:确保事务结束
async function goodTransaction() {
  await rdbStore.beginTransaction();
  try {
    await rdbStore.insert('table', data);
    await rdbStore.commit();
  } catch (err) {
    await rdbStore.rollBack();
    throw err;
  }
}

2. 嵌套事务问题

SQLite不支持真正的嵌套事务,内层BEGIN会被忽略:

arkts 复制代码
// ❌ 错误理解:以为有嵌套事务
async function nestedTransactionWrong() {
  await rdbStore.beginTransaction(); // 外层事务
  await rdbStore.beginTransaction(); // 这个BEGIN被忽略!
  await rdbStore.commit(); // 提交外层事务
  await rdbStore.commit(); // 错误:没有事务可提交
}

// ✅ 正确:使用保存点模拟嵌套
async function nestedTransactionCorrect() {
  await rdbStore.beginTransaction();
  
  await rdbStore.executeSql('SAVEPOINT inner'); // 创建保存点
  // 内层操作...
  await rdbStore.executeSql('RELEASE SAVEPOINT inner'); // 释放保存点
  
  await rdbStore.commit();
}

3. 长事务阻塞

事务时间过长会阻塞其他操作:

arkts 复制代码
// ❌ 危险:长事务
async function longTransaction() {
  await rdbStore.beginTransaction();
  
  // 大量耗时操作...
  for (let i = 0; i < 10000; i++) {
    await rdbStore.insert('table', data);
  }
  
  await rdbStore.commit(); // 这期间其他写操作全被阻塞
}

// ✅ 优化:分批提交
async function batchTransaction() {
  const batchSize = 100;
  
  for (let i = 0; i < 10000; i += batchSize) {
    await rdbStore.beginTransaction();
    try {
      for (let j = i; j < Math.min(i + batchSize, 10000); j++) {
        await rdbStore.insert('table', data);
      }
      await rdbStore.commit();
    } catch (err) {
      await rdbStore.rollBack();
      throw err;
    }
  }
}

4. 死锁风险

虽然SQLite的锁机制相对简单,但特定场景下仍可能死锁:

arkts 复制代码
// 理论上的死锁场景(SQLite通常能自动检测并处理)
// 事务A: 锁表X -> 等待锁表Y
// 事务B: 锁表Y -> 等待锁表X

// 预防措施:按固定顺序访问资源
async function safeMultiTableOperation() {
  await rdbStore.beginTransaction();
  try {
    // 始终按固定顺序:先表A后表B
    await rdbStore.insert('tableA', dataA);
    await rdbStore.insert('tableB', dataB);
    await rdbStore.commit();
  } catch (err) {
    await rdbStore.rollBack();
    throw err;
  }
}

5. 异常处理不完整

异常处理要覆盖所有可能的错误路径:

arkts 复制代码
// ❌ 不完整:只捕获了insert错误
async function incompleteErrorHandling() {
  await rdbStore.beginTransaction();
  await rdbStore.insert('table', data);
  await rdbStore.commit(); // 如果commit失败呢?
}

// ✅ 完整:所有操作都包在try-catch中
async function completeErrorHandling() {
  await rdbStore.beginTransaction();
  try {
    await rdbStore.insert('table', data);
    await rdbStore.commit();
  } catch (err) {
    try {
      await rdbStore.rollBack();
    } catch (rollbackErr) {
      console.error('回滚也失败了:', rollbackErr);
    }
    throw err;
  }
}

五、HarmonyOS 6适配说明

HarmonyOS 6对事务API做了增强:

API变更

变更项 HarmonyOS 5 HarmonyOS 6
事务API beginTransaction/commit/rollBack 新增beginTransWithLevel指定隔离级别
自动重试 新增executeInTransaction自动重试
事务状态 无查询接口 新增isTransactionActive查询状态

新增功能

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

// 指定隔离级别开启事务(HarmonyOS 6新增)
async function transactionWithIsolation(rdbStore: relationalStore.RdbStore) {
  // 指定READ_COMMITTED隔离级别
  await rdbStore.beginTransWithLevel(relationalStore.TransactionLevel.READ_COMMITTED);
  
  try {
    // 事务操作...
    await rdbStore.commit();
  } catch (err) {
    await rdbStore.rollBack();
    throw err;
  }
}

// 自动重试的事务执行(HarmonyOS 6新增)
async function autoRetryTransaction(rdbStore: relationalStore.RdbStore) {
  // 框架自动处理重试逻辑
  await rdbStore.executeInTransaction(async () => {
    // 这里的操作如果因冲突失败,框架会自动重试
    await rdbStore.insert('table', data);
    await rdbStore.update(bucket, predicates);
  }, {
    maxRetries: 3, // 最大重试次数
    retryDelay: 100 // 重试间隔(毫秒)
  });
}

// 查询事务状态(HarmonyOS 6新增)
async function checkTransactionState(rdbStore: relationalStore.RdbStore) {
  const isActive = rdbStore.isTransactionActive();
  console.info(`当前是否有活跃事务: ${isActive}`);
  
  if (isActive) {
    console.warn('注意:当前在事务中,操作不会立即提交');
  }
}

性能优化

HarmonyOS 6优化了事务性能:

  • 批量写入优化:事务内的批量写入性能提升约20%
  • 锁粒度优化:内部优化了锁的持有时间
  • WAL模式默认开启:Write-Ahead Logging模式提升并发性能

六、总结

事务是数据库操作的基石,保证了数据的ACID特性。在鸿蒙RDB中,事务API简洁易用,但要用好还是需要理解底层原理。

维度 评价
学习难度 ⭐⭐⭐⭐
使用频率 ⭐⭐⭐⭐
重要程度 ⭐⭐⭐⭐⭐
调试难度 ⭐⭐⭐⭐

记住几个关键点:

  • 事务要配对:beginTransaction必须有对应的commit或rollBack
  • 异常要处理:try-catch包住所有操作,确保异常时回滚
  • 并发要加锁:乐观锁适合低冲突,悲观锁适合高冲突
  • 长事务要拆分:避免阻塞其他操作

下一篇,我们会聊数据库迁移------应用版本升级时,如何保证数据库结构平滑演进,不丢数据、不崩应用。