鸿蒙PC链接数据库操作的并发与事务安全
当你的 HarmonyOS 项目需要踩坑记录24:数据库操作并发与事务安全时,本文提供的一套完整方案可以帮你少走弯路。所有代码均来自生产环境验证,涵盖正常流程和异常边界情况的处理。
·
踩坑记录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、并发控制
一、问题现象
- 数据写入后读取不到最新值
- 多次快速操作导致数据库锁定(database is locked)
- 数据不一致——关联表更新了一半
二、常见问题代码
// ❌ 问题一:并发写入无保护
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 语法、组件开发、状态管理、网络请求、数据库、多端适配等全方位实战经验。
工具与资源### 工具与资源
- DevEco Studio 官方下载 — HarmonyOS 官方IDE
- HarmonyOS 开发者社区 — 技术问答与经验分享
👇 如果这篇对你有帮助,欢迎点赞、收藏、评论!
你的支持是我持续输出高质量技术内容的动力 💪
更多推荐


所有评论(0)