ログオフ
開発2026-06-06

expo-sqlite の withTransactionSync はネストできない

withTransactionSync の中に withTransactionSync を入れたらクラッシュした。ネストは非対応。フラットなトランザクション設計に変える必要がある。

withTransactionSync の中でさらに withTransactionSync を呼んだらクラッシュした。ネストトランザクションは非対応だった。

事象

  • expo-sqlite の withTransactionSync を使ったコードを書いた
  • ある関数の中で withTransactionSync を呼び、その関数をさらに別の withTransactionSync の中から呼んでいた
  • 実行するとアプリがクラッシュする、またはエラーが投げられる
  • トランザクション外から呼ぶと問題なく動く

原因

expo-sqlite の withTransactionSync(および非同期版 withTransactionAsync)はネストに対応していない

// ❌ クラッシュするパターン

function saveItem(db, item) {
  db.withTransactionSync(() => {          // 内側のトランザクション
    db.runSync('INSERT INTO items ...', [item]);
  });
}

db.withTransactionSync(() => {            // 外側のトランザクション
  saveItem(db, itemA);                    // ← ここで内側が起動 → クラッシュ
  saveItem(db, itemB);
});

SQLite 自体はネストトランザクション(SAVEPOINT)をサポートしているが、expo-sqlite の withTransactionSync API はそれを扱わない設計になっている。

解決策

トランザクションをネストせず、フラットに設計する。

// ✅ フラットなトランザクション

function saveItem(db, item) {
  // トランザクションを自分で張らない。呼び出し側に委ねる
  db.runSync('INSERT INTO items ...', [item]);
}

db.withTransactionSync(() => {
  saveItem(db, itemA);   // トランザクション内で直接実行
  saveItem(db, itemB);
});

トランザクションの境界を一か所に集め、内側の関数には DB 操作だけを持たせる。トランザクション管理は呼び出し側が担う設計にすると、ネストが発生しない。

補足

  • どうしても動的にトランザクションを張りたい場合は execSyncBEGIN/COMMIT/ROLLBACK を直接発行するか、SAVEPOINT 構文を使う手もあるが、expo-sqlite の抽象レイヤーを外れるため保守コストが上がる
  • 同様の制約は withTransactionAsync にも当てはまる
  • クラッシュのエラーメッセージが分かりにくく、ネストが原因と気づきにくい。「トランザクション内から別のトランザクションを使う関数を呼んでいないか」を最初に疑う

※ 本記事にはアフィリエイトリンクが含まれます。

開発 一覧へ