📚 ナレッジベース

SQLite_D1_大量データの落とし穴

最終更新 2026年08月10日 / 21_ナレッジベース/プロダクト開発/SQLite_D1_大量データの落とし穴.md

SQLite / Cloudflare D1:大量データの落とし穴

SCALE LIST(503万社)で実際に踏んだものだけ。どれも「動いてはいるが、遅い/黙って落ちる」という顔で現れるので気づきにくい。
数十万行を超えるテーブルを扱う全システムで再発しうる。

落とし穴①:索引を作っても、ANALYZE を打たないと使われない

2026-08-10 に踏んだ。業界ごとの件数を数えるだけで1クエリ30秒近くかかっていた。
industry_mid の索引はちゃんと有る。にもかかわらず遅い。

真因: SQLite は統計情報(sqlite_stat1)が無いと、どの索引が「効く」かを判断できない。
このケースでは is_active の索引を選んでいた。is_active=1 は503万行のうち500万行が該当するので、
実質そのまま全件走査していた。

ANALYZE;   -- 503万行のテーブルで81秒。一度打つだけ
クエリ
業界の件数 30秒近く 1.2秒
業界の関連レコード数(JOIN) 30秒近く 0.3秒

打つタイミング: データを大きく増やしたあと(取り込み・クロール反映のあと)。
索引を足した直後にも打つ。索引を作った=速くなった、と思い込まない。

選択率の低い列(is_active・フラグ列)の索引は、統計が無いと害になる
「絞り込みに使うから」と何でも索引を張るより、ANALYZE とセットで考える。

落とし穴②:D1 は ?(バインド変数)を100個までしか受け取らない

101個目で Worker が error 1101 で落ちる。しかもそのAPIだけが死ぬので、
他の画面が生きていると気づけない(SCALE LIST では「件数」と「詳細」は生きたまま
企業一覧だけが真っ白になった)。

// IN 句に渡す配列は必ず100個ずつに割る
for (let i = 0; i < ids.length; i += 100) {
  const chunk = ids.slice(i, i + 100)
  await db.prepare(`SELECT ... WHERE id IN (${chunk.map(() => '?').join(',')})`)
          .bind(...chunk).all()
}

「表示件数を200に増やしたら落ちた」は、たいていこれ。

落とし穴③:1 INSERT 文あたり 100行 を超えると SQLITE_TOOBIG

D1 へまとめて投入するとき。行数だけでなく1文の長さも効くので、
100行ずつに区切るのを既定にしておく。

索引作成も1本ずつ流す。7本まとめて流すと D1 DB exceeded its CPU time limit で失敗する。

落とし穴④:本番D1に後付け ALTER した列は、表を作り直すと消える

同期スクリプトが DROP TABLECREATE TABLE で入れ替える作りだと、
手作業で ALTER TABLE ADD COLUMN した列が毎回消える
そして「列が無い」ことに気づくのは、その列を読む画面が落ちたとき。

後付けした列は、必ず同期スクリプトの CREATE 文にも書き足す。
入れ替え後に値を入れ直す処理まで含めて1本にする。

落とし穴⑤:WAL でも書き込み同士はぶつかる。busy_timeout が無いと即死

長時間のクロールが「スリープのせいで死んだ」と思っていたら、
ログの最後は sqlite3.OperationalError: database is locked だった。

journal_mode=WAL は「読みと書きが同時にできる」だけで、書き込み同士は排他
そして既定の busy_timeout0=待たずに即例外

conn = sqlite3.connect(DB)
conn.execute("PRAGMA busy_timeout = 60000")   # 60秒待つ。全スクリプトに入れる

長時間まわすときは caffeinate -dimsu も併用(スリープも別途効く)。

落とし穴⑥:--src で絞る前に、その列の出どころ列が実在するか確かめる

「値」と「出どころ(<列>_src)」を対にして持つ設計にしていると、
出どころ列を持たない列に --src を付けた瞬間に no such column で落ちる。

しかも本番反映のような一括処理では、大量のログに 失敗 の1行が埋もれる。
2026-08-07 に、5列全部が失敗していたのに「反映しました」と報告してしまった。

落ちた段は最後にまとめて出す。 途中の1行に頼らない。

関連