DjangoでSQLのSLECT FOR UPDATEをするときはQuerySetのselect_for_update()
を利用しますが、これを利用してもエラーが出たので少々調べました。
経緯
select_for_update()
はトランザクション内、Djangoのtransaction.atomic()
内でしか使えません。
もしトランザクション外でselect_for_update()
を使うと
django.db.transaction.TransactionManagementError: select_for_update cannot be used outside of a transaction.
とエラーが起きます。
今回、レプリカDBがあるためMultiple Databasesの環境でDjangoを使用していましたが、このMultiple Databasesの環境のトランザクション内でselect_for_update()
を使用したら上記のエラーが起きました。
transaction.atomic()
のコンテキスト内なのになぜ?となり、少々このあたりのコードを読みました。
結論
Multiple Databasesの環境かつトランザクションを開始するDBがdefault
ではないときはtransaction.atomic
の引数using
を指定しましょう。例えばhoge
DBに保存されたモデルHige
を更新するなら、
with transaction.atomic(using="hoge"):
hoge = Hige.objects.select_for_update().get(pk=1)
のような感じです。
なぜエラーが起きたのか
Djangoではsettings.pyのDATABASES
に追加することでDBを複数利用できますが、色々なところでdefault
のDBが選択されます。今回もそのケースです。
transaction.atomic()
はusingでDBを指定しない場合、default
DBに対してトランザクションを開始します。そして、default
ではない他のDBに保存されているモデルに対してselect_for_update()
を使用したため、トランザクション外でのselect_for_update()
となりエラーが起きてしまいました。
考えてみると当然で、何も指定せずtransaction.atomic()
を使ってもどのDBでトランザクションを開始すればいいかDjango側では判断ができないですよね。