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包住所有操作,确保异常时回滚
- 并发要加锁:乐观锁适合低冲突,悲观锁适合高冲突
- 长事务要拆分:避免阻塞其他操作
下一篇,我们会聊数据库迁移------应用版本升级时,如何保证数据库结构平滑演进,不丢数据、不崩应用。