🧊

Cloudflare D1の、`D1_ERROR: too many SQL variables`について

Cloudflare D1には、SQL文において使用できる?の数が100までという制限がある。

https://developers.cloudflare.com/d1/platform/limits/

このページにある、Maximum bound parameters per queryってやつがそれ。

workerdのコードでも、ランタイムの制約に指定してあるのがわかる。

sqlite3_limit(db, SQLITE_LIMIT_VARIABLE_NUMBER, 100); https://github.com/cloudflare/workerd/blob/fae4178326fb9709dea737f51337cda1d1551f25/src/workerd/util/sqlite.c%2B%2B#L731

問題のケース

思ってた以上によく引っかかる。

たとえばカラムが10あるテーブルがあったとすると、そのテーブルに11件をまとめてINSERTするとすぐにこのエラーになる。(もちろん発行されるSQLによるけど)

ORMなんかを使ってると、複数のレコードをINSERTするSQLはこんな風に発行されがち。

INSERT INTO items VALUES (?, ?, ?), (?, ?, ?), ..., (?, ?, ?);

で、この1文で登場する?が100を超えるとエラーになっちゃう。

?自体は、INSERT文のVALUESだけでなく、WHERE句とかにも出番があるけど、まあ数が問題になるのはだいたいINSERTな気がする。

どうする

D1の制約であるなら、どうしようもない。

クエリを調整して、?の数を100以下にするしかない。

愚直に、複数のレコードを一括挿入するINSERT文をやめて、複数回のINSERT文にしてしまう。

INSERT INTO items VALUES (?, ?, ?);
INSERT INTO items VALUES (?, ?, ?);
...
INSERT INTO items VALUES (?, ?, ?);

たとえばDrizzle ORMを使ってるとすると、こんな具合に書き直す。

const db = drizzle(env.DB);

// Before
await db.insert(items).values(itemsToInsert);

// After
await db.batch([
  ...itemsToInsert.map((item) => db.insert(items).values(item));
]);

元の挙動とあわせるために、batch()で一括実行するようにしておく。batch()内で使われる?の合計数は、別に100を超えても問題ない。

他にも、そもそもプリペアドステートメントを使わず、SQL文字列を自分で組み上げる方法もあるかも。 この場合はbatch()を使わなくて良いので1クエリのままいけるけど、なんだかなあ・・・。

(100カラムある(そしてDEFAULT指定なし)テーブルがあったとすると、どうしたら良いんやろうね?)