SQLite_D1_大量データの落とし穴
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 TABLE → CREATE TABLE で入れ替える作りだと、
手作業で ALTER TABLE ADD COLUMN した列が毎回消える。
そして「列が無い」ことに気づくのは、その列を読む画面が落ちたとき。
→ 後付けした列は、必ず同期スクリプトの CREATE 文にも書き足す。
入れ替え後に値を入れ直す処理まで含めて1本にする。
落とし穴⑤:WAL でも書き込み同士はぶつかる。busy_timeout が無いと即死
長時間のクロールが「スリープのせいで死んだ」と思っていたら、
ログの最後は sqlite3.OperationalError: database is locked だった。
journal_mode=WAL は「読みと書きが同時にできる」だけで、書き込み同士は排他。
そして既定の busy_timeout は 0=待たずに即例外。
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行に頼らない。
関連
- Claude_Code_GoogleDrive上のシェル操作落とし穴
- SCALE LIST のスタートカード …
~/dev/scale-list/CLAUDE.md