BF编程式事务使用指南
快速开始
在脚本中使用事务,只需要调用 context.client.db.transaction() 方法:
JavaScript
await context.client.db.transaction(async (tx) => {
// 在这里执行数据库操作
// 正常结束自动提交,抛出异常自动回滚
});使用规范
1.必须使用 await
JavaScript
// ❌ 错误:忘记使用 await
context.client.db.transaction(async (tx) => {
tx.sql.execute({ sqlCode: 'xxx', params: {} }); // 缺少 await
});
// ✅ 正确:所有异步操作都要使用 await
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
});2. 事务函数必须是 async
JavaScript
// ❌ 错误:事务函数不是 async
await context.client.db.transaction((tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
});
// ✅ 正确:事务函数必须声明为 async
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
});3.不要在事务外使用 tx 对象
JavaScript
// ❌ 错误:在事务外使用 tx
let txRef;
await context.client.db.transaction(async (tx) => {
txRef = tx;
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
});
await txRef.sql.execute({ sqlCode: 'yyy', params: {} }); // 错误!
// ✅ 正确:所有操作都在事务内完成
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
await tx.sql.execute({ sqlCode: 'yyy', params: {} });
});4.不要在事务中执行耗时的非数据库操作
JavaScript
// ❌ 错误:事务中包含耗时操作
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
await sleep(5000); // 不要在事务中 sleep
const data = await fetchExternalApi(); // 不要在事务中调用外部 API
await tx.sql.execute({ sqlCode: 'yyy', params: data });
});
// ✅ 正确:耗时操作放在事务外
const data = await fetchExternalApi();
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
await tx.sql.execute({ sqlCode: 'yyy', params: data });
});5.必须正确处理异常
JavaScript
// ❌ 错误:吞掉异常,导致事务状态不明确
await context.client.db.transaction(async (tx) => {
try {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
} catch (e) {
console.log(e); // 只打印日志,不抛出异常
}
});
// ✅ 正确:异常要向上抛出,让事务回滚
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
// 让异常自然抛出,事务会自动回滚
});
// ✅ 正确:或者在外层捕获异常
try {
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'xxx', params: {} });
});
} catch (e) {
// 在这里处理异常
return { success: false, error: e.message };
}基础用法
1.执行自定义
JavaScript
await context.client.db.transaction(async (tx) => {
const result = await tx.sql.execute({
sqlCode: 'insertUser', // SQL 配置的 code
params: { // SQL 参数
nickname: 'Alice',
phone: '13800138000'
}
});
});2. 使用数据集 API
Python
await context.client.db.transaction(async (tx) => {
// 创建记录
const id = await tx.models.dataset_xxx.create({
name: 'Bob',
age: 25
});
// 查询记录
const user = await tx.models.dataset_xxx.findOne({ id: id });
// 更新记录
await tx.models.dataset_xxx.update({ id: id, age: 26 });
});3.混合使用
JavaScript
await context.client.db.transaction(async (tx) => {
// 使用自定义 SQL
await tx.sql.execute({
sqlCode: 'insertUser',
params: { nickname: 'Charlie', phone: '13700137000' }
});
// 使用数据集 API
await tx.models.dataset_xxx.create({
name: 'David',
age: 30
});
});事务回滚
INFO
当事务中抛出异常时,所有操作会自动回滚:
JavaScript
try {
await context.client.db.transaction(async (tx) => {
// 插入用户
await tx.sql.execute({
sqlCode: 'insertUser',
params: { nickname: 'Test', phone: '13800138000' }
});
// 业务校验失败,抛出异常
if (someCondition) {
throw new Error('业务校验失败');
}
// 这行代码不会执行
await tx.sql.execute({
sqlCode: 'insertOrder',
params: { userId: 123 }
});
});
} catch (e) {
// 事务已回滚,数据库中不会有任何记录
console.log('操作失败:' + e.message);
}嵌套事务
TIP
嵌套事务会加入外层事务,作为一个整体提交或回滚:
JavaScript
await context.client.db.transaction(async (tx1) => {
// 外层事务操作
await tx1.sql.execute({ sqlCode: 'sql1', params: {} });
// 嵌套事务
await context.client.db.transaction(async (tx2) => {
await tx2.sql.execute({ sqlCode: 'sql2', params: {} });
// 如果这里抛出异常,外层事务也会回滚
});
// 继续外层事务操作
await tx1.sql.execute({ sqlCode: 'sql3', params: {} });
});INFO
重要提示:
- 内层事务不是独立的,会加入外层事务
- 任何一层抛出异常,整个事务都会回滚
- 只有最外层事务结束时才真正提交
多数据源事务
JavaScript
可以在同一个事务中操作多个数据源:
await context.client.db.transaction(async (tx) => {
// 操作数据源 A
await tx.sql.execute({
sqlCode: 'insertUserA', // 绑定到数据源 A
params: { phone: '13800138000' }
});
// 操作数据源 B
await tx.sql.execute({
sqlCode: 'insertUserB', // 绑定到数据源 B
params: { phone: '13900139000' }
});
// 两个数据源的操作在同一个事务中
// 任一失败,全部回滚
});常见场景示例
场景 1:用户注册(单数据源)
JavaScript
await context.client.db.transaction(async (tx) => {
// 1. 插入用户信息
const userId = await tx.sql.execute({
sqlCode: 'insertUser',
params: {
nickname: params.nickname,
phone: params.phone
}
});
// 2. 初始化用户积分
await tx.sql.execute({
sqlCode: 'initUserPoints',
params: { userId: userId, points: 0 }
});
// 3. 发送欢迎消息
await tx.sql.execute({
sqlCode: 'insertMessage',
params: { userId: userId, content: '欢迎注册' }
});
return { success: true, userId: userId };
});场景 2:订单创建(多表操作)
JavaScript
wait context.client.db.transaction(async (tx) => {
// 1. 创建订单
const orderId = await tx.models.dataset_order.create({
userId: params.userId,
totalAmount: params.amount,
status: 'pending'
});
// 2. 创建订单明细
for (const item of params.items) {
await tx.models.dataset_order_item.create({
orderId: orderId,
productId: item.productId,
quantity: item.quantity,
price: item.price
});
}
// 3. 扣减库存
await tx.sql.execute({
sqlCode: 'decreaseStock',
params: { items: params.items }
});
return { success: true, orderId: orderId };
});场景 3:转账操作(带校验)
JavaScript
await context.client.db.transaction(async (tx) => {
// 1. 查询转出账户余额
const fromAccount = await tx.models.dataset_account.findOne({
id: params.fromAccountId
});
// 2. 余额校验
if (fromAccount.balance < params.amount) {
throw new Error('余额不足');
}
// 3. 扣减转出账户
await tx.models.dataset_account.update({
id: params.fromAccountId,
balance: fromAccount.balance - params.amount
});
// 4. 增加转入账户
const toAccount = await tx.models.dataset_account.findOne({
id: params.toAccountId
});
await tx.models.dataset_account.update({
id: params.toAccountId,
balance: toAccount.balance + params.amount
});
// 5. 记录转账流水
await tx.sql.execute({
sqlCode: 'insertTransferLog',
params: {
fromAccountId: params.fromAccountId,
toAccountId: params.toAccountId,
amount: params.amount
}
});
return { success: true };
});注意事项
1.事务范围尽量小
JavaScript
// ✅ 好的做法:先准备数据,再开启事务
const data = await fetchExternalApi();
await context.client.db.transaction(async (tx) => {
await tx.sql.execute({ sqlCode: 'insert', params: data });
});2.合理处理异常
JavaScript
try {
await context.client.db.transaction(async (tx) => {
// 事务操作
});
} catch (e) {
// 记录日志或返回错误信息
return { success: false, error: e.message };
}避免的做法
事务中调用外部 API
JavaScript
// ❌ 不好的做法:事务中包含外部调用
await context.client.db.transaction(async (tx) => {
const data = await fetchExternalApi(); // 外部 API 很慢
await tx.sql.execute({ sqlCode: 'insert', params: data });
});过深的嵌套
JavaScript
// ❌ 避免过深的嵌套
await context.client.db.transaction(async (tx1) => {
await context.client.db.transaction(async (tx2) => {
await context.client.db.transaction(async (tx3) => {
// 嵌套太深
});
});
});