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を指定しましょう。例えばhogeDBに保存されたモデル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を指定しない場合、defaultDBに対してトランザクションを開始します。そして、defaultではない他のDBに保存されているモデルに対してselect_for_update()を使用したため、トランザクション外でのselect_for_update()となりエラーが起きてしまいました。
考えてみると当然で、何も指定せずtransaction.atomic()を使ってもどのDBでトランザクションを開始すればいいかDjango側では判断ができないですよね。