踩坑记录24:数据库操作的并发与事务安全

阅读时长:13分钟 | 难度等级:高级 | 适用版本:HarmonyOS NEXT (API 12+)
关键词:数据库、事务安全、并发控制、RdbStore
声明:本文基于真实项目开发经历编写,所有代码片段均来自实际踩坑场景。

欢迎加入开源鸿蒙PC社区https://harmonypc.csdn.net/
项目 Git 仓库https://atomgit.com/Dgr111-space/HarmonyOS


在这里插入图片描述
在这里插入图片描述

📖 前言导读

当你的 HarmonyOS 项目需要踩坑记录24:数据库操作并发与事务安全时,本文提供的一套完整方案可以帮你少走弯路。所有代码均来自生产环境验证,涵盖正常流程和异常边界情况的处理。

踩坑记录24:数据库操作的并发与事务安全

严重程度:⭐⭐⭐⭐ | 发生频率:中
涉及模块:@ohos.data.relationalStore、SQLite、并发控制

一、问题现象

  1. 数据写入后读取不到最新值
  2. 多次快速操作导致数据库锁定(database is locked)
  3. 数据不一致——关联表更新了一半

二、常见问题代码

// ❌ 问题一:并发写入无保护
async function updateUserInfo(user: User) {
  // 请求 A 和请求 B 同时到达
  await dbExecute(`UPDATE users SET name='${user.name}' WHERE id=${user.id}`)
  await dbExecute(`UPDATE profiles SET avatar='${user.avatar}' WHERE user_id=${user.id}`)
  // 如果两次请求交错执行:
  // 请求A写name → 请求B写name → 请求A写avatar → 请求B写avatar
  // 结果:name来自B,avatar来自A → 数据错乱!
}

// ❌ 问题二:忘记关闭连接/Rset
async function queryUsers(): Promise<User[]> {
  const rset = await db.query('SELECT * FROM users')
  // ⚠️ 如果这里抛异常,rset 永远不会被 close → 连接泄漏
  const users = []
  while (rset.goToNextRow()) {
    users.push(parseUser(rset))
  }
  // rset.close() 被遗漏
  return users
}

// ❌ 问题三:在主线程做数据库操作
function loadData() {
  const result = db.querySync('SELECT * FROM large_table')  // 同步查询!
  // UI 冻结...
}

三、安全的数据库封装

import { relationalStore, RdbStore } from '@ohos.data.relationalStore'
import { common } from '@kit.AbilityKit'
import { valuesBucket } from '@ohos.data.valuesBucket'

/** RdbStore 包装类,提供安全的 CRUD 操作 */
export class DatabaseHelper {
  private static instance: DatabaseHelper
  private store: RdbStore | null = null
  private readonly dbName: string = 'app.db'
  private readonly dbVersion: number = 1
  private isInitializing: boolean = false

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

  /** 异步初始化数据库 */
  async initialize(): Promise<RdbStore> {
    if (this.store) return this.store
    if (this.isInitializing) {
      // 防止重复初始化:等待已有初始化完成
      return new Promise((resolve) => {
        const check = setInterval(() => {
          if (this.store) {
            clearInterval(check)
            resolve(this.store!)
          }
        }, 50)
      })
    }

    this.isInitializing = true
    try {
      const context = getContext() as common.UIAbilityContext
      
      this.store = await relationalStore.getRdbStore(context, {
        name: this.dbName,
        securityLevel: relationalStore.SecurityLevel.S1
      })

      await this.createTables()
      
      console.log(`[DB] initialized: ${this.dbName}`)
      return this.store!
    } catch (e) {
      console.error('[DB] initialization failed:', e)
      throw e
    } finally {
      this.isInitializing = false
    }
  }

  private async createTables(): Promise<void> {
    if (!this.store) throw new Error('DB not initialized')

    const sqlStatements: string[] = [
      `CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE,
        created_at INTEGER,
        updated_at INTEGER
      )`,
      
      `CREATE TABLE IF NOT EXISTS todos (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        title TEXT NOT NULL,
        completed INTEGER DEFAULT 0,
        priority INTEGER DEFAULT 0,
        created_at INTEGER,
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
      )`,
      
      `CREATE INDEX IF NOT EXISTS idx_todos_user_id ON todos(user_id)`
    ]

    await this.store.executeSql(sqlStatements.join(';'))
  }

  // ===== 基础 CRUD 操作 =====

  /**
   * 插入数据
   */
  async insert(table: string, data: Record<string, any>): Promise<number> {
    const store = await this.ensureStore()
    const bucket = this.toValuesBucket(data)
    const rowId = await store.insert(table, bucket)
    console.log(`[DB] insert into ${table}, rowId=${rowId}`)
    return rowId
  }

  /**
   * 查询多条数据
   */
  async queryAll<T>(
    table: string,
    columns: string[] = ['*'],
    whereClause: string = '',
    args: any[] = [],
    orderBy: string = '',
    limit: number = 0
  ): Promise<T[]> {
    const store = await this.ensureStore()
    
    let sql = `SELECT ${columns.join(', ')} FROM ${table}`
    const bindArgs: any[] = [...args]
    
    if (whereClause) {
      sql += ` WHERE ${whereClause}`
    }
    if (orderBy) {
      sql += ` ORDER BY ${orderBy}`
    }
    if (limit > 0) {
      sql += ` LIMIT ${limit}`
    }

    const rset = await store.query(sql, bindArgs)
    const results: T[] = []

    try {
      while (rset.goToNextRow()) {
        results.push(rset.row as unknown as T)
      }
    } finally {
      rset.close()  // ✅ 确保 ResultSet 关闭
    }

    return results
  }

  /**
   * 更新数据
   */
  async update(
    table: string,
    data: Record<string, any>,
    whereClause: string,
    args: any[] = []
  ): Promise<number> {
    const store = await this.ensureStore()
    const bucket = this.toValuesBucket(data)
    const affectedRows = await store.update(table, bucket, whereClause, args)
    console.log(`[DB] update ${table}, affected=${affectedRows}`)
    return affectedRows
  }

  /**
   * 删除数据
   */
  async delete(
    table: string,
    whereClause: string,
    args: any[] = []
  ): Promise<number> {
    const store = await this.ensureStore()
    const affectedRows = await store.delete(table, whereClause, args)
    console.log(`[DB] delete from ${table}, affected=${affectedRows}`)
    return affectedRows
  }

  // ===== 事务操作 =====

  /**
   * 在事务中执行一系列操作 —— 保证原子性
   */
  async transaction<T>(operations: () => Promise<T>): Promise<T> {
    const store = await this.ensureStore()
    
    try {
      await store.beginTransaction()
      console.log('[DB] transaction started')
      
      const result = await operations()
      
      await store.commit()
      console.log('[DB] transaction committed')
      return result
    } catch (error) {
      console.error('[DB] transaction failed, rolling back:', error)
      await store.rollback()
      throw error
    }
  }

  // ===== 工具方法 =====

  private async ensureStore(): Promise<RdbStore> {
    if (this.store) return this.store
    return this.initialize()
  }

  private toValuesBucket(data: Record<string, any>): valuesBucket.ValuesBucket {
    const bucket: valuesBucket.ValuesBucket = {}
    for (const [key, value] of Object.entries(data)) {
      if (value !== undefined && value !== null) {
        bucket[key] = value
      }
    }
    return bucket
  }

  /** 关闭数据库连接 */
  async close(): Promise<void> {
    if (this.store) {
      await this.store.close()
      this.store = null
      console.log('[DB] connection closed')
    }
  }
}

四、使用示例

事务保证原子性

// 创建用户并同时初始化其默认数据
async function createUserWithDefaults(userInfo: { name: string; email: string }): Promise<number> {
  const db = DatabaseHelper.getInstance()
  
  return db.transaction(async () => {
    // 1. 插入用户
    const userId = await db.insert('users', {
      name: userInfo.name,
      email: userInfo.email,
      created_at: Date.now(),
      updated_at: Date.now()
    })

    // 2. 为用户创建默认待办事项
    await db.insert('todos', {
      user_id: userId,
      title: '欢迎使用!点击编辑你的第一个待办事项',
      priority: 0,
      created_at: Date.now()
    })

    return userId
    // 两个操作要么全部成功,要么全部回滚
  })
}

安全的批量操作

// 批量更新待办事项状态
async function batchCompleteTodos(todoIds: number[]): Promise<void> {
  const db = DatabaseHelper.getInstance()
  
  // 使用 IN 子句而非循环逐条更新
  if (todoIds.length === 0) return
  
  const placeholders = todoIds.map(() => '?').join(',')
  await db.update(
    'todos',
    { completed: 1, updated_at: Date.now() },
    `id IN (${placeholders})`,
    todoIds
  )
}

五、性能注意事项

操作 建议
大量插入 使用事务批量提交
查询大表 建立索引 + LIMIT 分页
频繁读写 考虑内存缓存 + 定期同步
表结构变更 onConfigure 中进行版本升级迁移
长时间持有 ResultSet 必须手动 close()
跨线程访问 SQLite 是文件锁机制,注意串行化

DB Safety = Transaction + ParamBinding + ResourceCleanup \text{DB Safety} = \text{Transaction} + \text{ParamBinding} + \text{ResourceCleanup} DB Safety=Transaction+ParamBinding+ResourceCleanup


参考资源与延伸阅读

官方文档

> 系列导航:本文是「HarmonyOS 开发踩坑记录」系列的第 24 篇。该系列共 30 篇,涵盖 ArkTS 语法、组件开发、状态管理、网络请求、数据库、多端适配等全方位实战经验。

工具与资源### 工具与资源


👇 如果这篇对你有帮助,欢迎点赞、收藏、评论!

你的支持是我持续输出高质量技术内容的动力 💪

Logo

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

更多推荐