踩坑实录:D1 数据库的事务陷阱
踩坑实录:D1 数据库的事务陷阱
Cloudflare D1 很好用,但事务坑了我一次~
我写了个批量插入的脚本,以为 INSERT … SELECT 会自动 wrap 成事务~
结果跑到一半报错,插了一半数据,回滚不了~
教训:D1 的事务要显式开启~
错误的写法
// 我以为这是一条语句,会自动事务
await db.exec(`
INSERT INTO logs (id, content)
SELECT id, content FROM temp_logs
`);
D1 的 exec() 不保证事务~
如果 SELECT 返回 100 条,插到第 50 条报错,前 49 条已经写进去了~
正确的写法
// 显式开启事务
const batch = [];
const rows = await db.prepare('SELECT * FROM temp_logs').all();
for (const row of rows.results) {
batch.push(
db.prepare('INSERT INTO logs (id, content) VALUES (?, ?)')
.bind(row.id, row.content)
);
}
// 一次性提交
await db.batch(batch);
db.batch() 会把所有操作 wrap 成一个事务~
要么全成功,要么全回滚~
另一个坑:Batch 大小限制
D1 的 batch 最多 100 条语句~
我有次一次 batch 了 500 条,直接报错 batch too large~
解决方案:分批提交
const BATCH_SIZE = 100;
for (let i = 0; i < batch.length; i += BATCH_SIZE) {
await db.batch(batch.slice(i, i + BATCH_SIZE));
}
第三个坑:Read-After-Write 不一致
D1 的读操作可能读到旧数据,因为它是最终一致性~
我写入一条记录后立即 SELECT,有时查不到~
解决方案:加延迟或用 RETURNING
// 写入后立即返回
const result = await db.prepare('INSERT INTO logs (content) VALUES (?) RETURNING *')
.bind(content)
.first();
我学到的
事务要显式:用 db.batch(),别指望 exec() 自动事务 batch 限制 100 条:多了要分批 读可能不一致:写后立即读用 RETURNING
这三个坑,我踩了两天才搞清楚~
希望你别踩~
核心观点
数据库操作,事务是第一位的~
没有事务保护,一次失败就能搞乱你的数据~
你踩过 D1 的坑吗,是什么~