django-pyodbcでselect_for_updateは機能しないので要注意 (2015/4時点)
django-pyodbcはselect_for_updateメソッドを呼び出しても普通のSELECT文を発行する
Django1.4以上でモデルマネージャやクエリセットにselect_for_updateというメソッドが使えるけれども、django-pyodbc(=SQLServer)だとこれが使えないので注意が必要。
select_for_updateを使っても普通のSELECT文しか生成してくれない
わかりにくいのが、select_for_updateメソッドを呼んでも単なるSELECT文を発行してしまう点。
(NotImplementedErrorなど実行時エラーを出してくれるわけではない)
django-pyodbcを使ってSQLServerをデータベースバックエンドに指定して以下のプログラムを動かすとそれが判る。
# -*- coding:utf-8 -*- import django.db.models as models class Foo(models.Model): class Meta: app_label = 'hello' db_table = 'foo' name = models.CharField(max_length=32) age = models.IntegerField(null=True) print(Foo.objects.select_for_update().filter(pk=1).query)
結果
SELECT [foo].[id], [foo].[name], [foo].[age] FROM [foo] WHERE [foo].[id] = 1
実際どう処理しているのか?
django-pyodbcのソースコードを見るとテストコードでいかにもselect_for_updateをサポートしているように見えるが、skipUnlessDBFeatureデコレータが付いている点が落とし穴で、実はこのテストはスキップされるようだ。
@skipUnlessDBFeature('has_select_for_update') def test_for_update_sql_generated(self): """ Test that the backend's FOR UPDATE variant appears in generated SQL when select_for_update is invoked. """ list(Person.objects.all().select_for_update()) self.assertTrue(self.has_for_update_sql(connection))
実際にconnectionのfeaturesを調べてみると、Falseを返す。
from django.db import connection print(connection.features.has_select_for_update) # => False
SQLServerのSELECT FOR UPDATEに対応する構文
SQLServerでSELECT FOR UPDATEをやろうとすると WITH (UPDLOCK) というようにヒントを加えるらしい(以下URL)。
例:
SELECT * FROM ACCOUNT WITH (UPDLOCK) WHERE ID = 100;
これがOracleやMySQLだと末尾に FOR UPDATE と付けるだけ。
SELECT * FROM ACCOUNT WHERE ID = 100 FOR UPDATE;
LIMIT/OFFSETのやり方もSQLServerだけ全然違って、ORマッパーでうまいこと構文通りにSELECT文をジェネレートするのが難しいから放置されているんだろう。