0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python sqlite3の一括保存、例外を捕る位置で2件と0件

0
Posted at

3件を一括保存し、最後の1件を主キー重複にした。自分は保存処理を試していて、IntegrityError の後も2件残るのを確認した。例外を捕る位置を変えると、今度は0件になった。

「全部成功か、全部取り消しか」で扱うバッチなら、例外は with con: の外で捕りたい。内側でログだけ出すと、途中までのデータをコミットしてしまう。

sqlite3で2件と0件を再現する

LLMの処理結果を request_id ごとに保存する想定。3件目だけIDを重複させた。

CPython 3.14.8、SQLite 3.53.4で確認した。Python 3.12以降の標準ライブラリだけで動く。

import sqlite3

rows = [("r1", "回答A"), ("r2", "回答B"), ("r1", "回答C")]

def run(catch_inside):
    con = sqlite3.connect(
        ":memory:",
        autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL,
        isolation_level="DEFERRED",
    )
    con.execute("CREATE TABLE result (request_id TEXT PRIMARY KEY, body TEXT)")
    sql = "INSERT INTO result VALUES (?, ?)"
    label = "inside" if catch_inside else "outside"
    try:
        if catch_inside:
            with con:
                try:
                    con.executemany(sql, rows)
                except sqlite3.IntegrityError as e:
                    print(label, "error:", e)
        else:
            try:
                with con:
                    con.executemany(sql, rows)
            except sqlite3.IntegrityError as e:
                print(label, "error:", e)
        count = con.execute("SELECT COUNT(*) FROM result").fetchone()[0]
        print(label, "rows:", count)
    finally:
        con.close()

run(True)
run(False)
inside error: UNIQUE constraint failed: result.request_id
inside rows: 2
outside error: UNIQUE constraint failed: result.request_id
outside rows: 0

エラー文は同じ。保存件数だけが違う。

executemanyは、SQLを繰り返し実行する

executemany() は同じINSERTを各パラメータで繰り返す。今回の設定では、最初のINSERTでトランザクションが始まる。

1件目と2件目は成功する。3件目で主キー制約に違反し、既定の競合処理でそのINSERTが失敗する。先の2件はトランザクション内に残る。

ここで例外を捕る位置が効く。

3件目の主キー重複後、withの内側で例外を捕ると2件がコミットされ、外へ抜けるとロールバックで0件になる流れ。本文の接続設定を前提にした図。

inside は例外を処理してから with を抜ける。正常終了として2件がコミットされる。outside は例外が with の外へ抜けるため、終了処理でロールバックされて0件になる。

手元の sqlite3.Connection.__exit__.__doc__ にも、例外があればrollback、なければcommitとある。関連する一次情報はPython公式の説明とSQLiteの競合処理。

接続設定も確認する

同じ接続に先行する未コミットの更新があれば、それもロールバック対象になる。with con: より前の書き込みまで確認したい。

autocommit=True では、この with によるロールバックは効かない。コードでは LEGACY_TRANSACTION_CONTROL と DEFERRED を明示した。

with con: を抜けても接続は閉じない。サンプルの finally はその後始末用。

LLMのバッチを丸ごと再試行するなら、失敗時に保存が0件へ戻るところまで確認したい。内側でログを出す場合も、例外を再送出すればロールバックにつながる。再試行を足す前に、途中で失敗する入力を通すと、「失敗扱いなのに保存済み」という食い違いを拾える。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?