django ormを使用して通常のコードはどのように書かれていますか?
原則として、このピースは特定の機能(ビューなど)に入り、パラメーターを受け取り、これらのパラメーターに基づいて結果を形成します。
例として、次の基本的な状況を考えてみましょう。現在のユーザーが属しているグループの名前のリストを取得したいです。 そもそも頭に浮かぶ最も簡単で明白な方法は、関係を介してグループのリストを取得し、名前を見つけることです。
def myview(request): u = request.user a = [g.name for g in u.groups.all()] ...
ユーザーオブジェクトrequest.userはリクエストの予備処理の段階ですでに受信されていることに留意して、この作品のパフォーマンスがどうなるかを確認します。
テストグループを作成し、最初のユーザーをそれに参加させます。
>>> u = User.objects.all()[0] >>> g = Group(name='thetest') >>> g.save() >>> u.groups.add(g) >>> u.groups.all() [<Group: thetest>]
今後のすべてのテストでこのケースを使用します。 すべてがシェルを介して行われるため、この段階で取得した変数uも使用します。
したがって、テスト番号1では、想定されたコードを実行します。 検索リストが本当に返されるかどうかを確認します。
>>> a = [g.name for g in u.groups.all()] >>> a [u'thetest']
パフォーマンスを測定するには、1000回実行します。
>>> def test1(): ... import datetime ... t1 = datetime.datetime.now() ... for i in xrange(1000): ... a = [g.name for g in u.groups.all()] ... t2 = datetime.datetime.now() ... print "%s" % (t2 - t1) ... >>> test1() 0:00:01.437324
私たちのサイクルの1000回転には約1.5秒かかり、リクエストごとに1.5ミリ秒かかりました。
経験豊富なジャングル作家は、おそらくこの作品が最適とはほど遠いという事実に鼻を突くためにすでに集まっているでしょう。 実際、グループオブジェクトを構築し、データベースから本当に必要なデータのみを取得することなく、同じアクションを実行するより最適なコードを一目で作成できます。
>>> a = [g['name'] for g in u.groups.values('name')] >>> a [u'thetest']
さて、この作品を測定します。
>>> def test2(): ... import datetime ... t1 = datetime.datetime.now() ... for i in xrange(1000): ... a = [g['name'] for g in u.groups.values('name')] ... t2 = datetime.datetime.now() ... print "%s" % (t2 - t1) ... >>> test2() 0:00:01.752529
不自然に思えますが、コードの2番目のバージョンは最初のバージョンよりも最適ではありませんか?
実際には、これがそうです。 値の呼び出し()と要求の追加分析での損失は、Groupオブジェクトの構築とそのすべてのフィールドの値の取得で節約できる可能性よりも高いことが判明しました。
しかし、すみません? 実際、リクエストを再設計および分析するたびに、ビューで常に同じリクエストを実行し、このリクエストが実行されるユーザーオブジェクトのみが異なる場合、なぜでしょうか?
残念なことに、djangoでは最初にリクエストを事前に準備することはできません 。必要に応じて準備済みのリクエストを参照します。 対応する呼び出しはなく、クエリ生成構文はクエリパラメータとして特定の値のみを使用することを意味します。
ソースに少し登る必要があります。 この機会に、django_extensionsの開発者と彼らのすばらしいshell_plusチームに感謝したいと思います。
QuerySetオブジェクト(これは、例えばobjects.all()へのアクセス時に取得されるオブジェクトです)には、django.db.models.sql.query.Queryクラスのオブジェクトであるqueryプロパティがあります。 次にsql_with_params()メソッドがあります
このメソッドは、cursor.execute()に渡す準備が整った一連のパラメーター、つまりSQL式文字列と追加パラメーターを返します。 すばらしいのは、これらの非常に高度なパラメーターが、QuerySetの形成時にQuerySetに渡されるパラメーターであることです。
>>> u.groups.all().values('name').query.sql_with_params() ('SELECT `auth_group`.`name` FROM `auth_group` INNER JOIN `auth_user_groups` ON (`auth_group`.`id` = `auth_user_groups`.`group_id`) WHERE `auth_user_groups`.`user_id` = %s ', (1,))
これで、準備済みのSQLクエリを取得し、それにさまざまなパラメーター値を代入すると、クエリの準備にリソースを無駄にせずにクエリを実行できます。
これを行うには、コミットするハックのすべての詳細を隠す特別なクラスを作成します。
from django.db import connection from django.db.models.query import QuerySet,ValuesQuerySet import django from threading import local class PQuery(local): def __init__(self,query,connection=connection,**placeholders): self.query = query self.connection = connection self.placeholders = placeholders self.replaces = {} sql = None try: sql = self.query.query.sql_with_params() # 1.4 except AttributeError: sql = self.query.query.get_compiler(connection=self.connection).as_sql() # 1.3, lower? self.places = list(sql[1]) self.sql = sql[0] self.is_values = isinstance(query,ValuesQuerySet) self.cursor = None for i in xrange(len(self.places)): x = self.places[i] found = False for p in self.placeholders: v = self.placeholders[p] if x == v: found = True if not p in self.replaces: self.replaces[p] = [] self.replaces[p].append(i) if not found: raise AttributeError("The placeholder %(ph)s not found, please add some_name=%(ph)s to the list of constructor parameters" % { 'ph':repr(x) }) def execute(self,**kw): try: for k in kw: for i in self.replaces[k]: self.places[i] = kw[k] except KeyError,ex: raise TypeError("No such placeholder: %s" % k) if not self.cursor: self.cursor = self.connection.cursor() self.cursor.execute(self.sql,self.places) if not hasattr(self,'fldnms'): self.fldnms = [col[0] for col in self.cursor.description] if self.is_values: return [dict(zip(self.fldnms,row)) for row in self.cursor.fetchall()] return [self.query.model(**dict(zip(self.fldnms,row))) for row in self.cursor.fetchall()] def __call__(self,**kw): return self.execute(**kw) ParametrizedQuery = PQuery # compatibility issue
UPD:2012-08-06 19:20:00 MSK-マルチトレーディングとの互換性に関するコードを修正し、複雑なクエリを実行する際の小さなバグを修正し、使いやすさを改善しました。
以前のコードバージョン
from django.db import connection from django.db.models.query import QuerySet,ValuesQuerySet class ParametrizedQuery: def __init__(self,query,connection=connection,**placeholders): self.query = query self.connection = connection self.placeholders = placeholders self.replaces = {} sql = self.query.query.sql_with_params() self.places = list(sql[1]) self.sql = sql[0] self.is_values = isinstance(query,ValuesQuerySet) self.cursor = None for p in self.placeholders: v = self.placeholders[p] self.replaces[p] = self.places.index(v) def execute(self,**kw): for k in kw: self.places[self.replaces[k]] = kw[k] if not self.cursor: self.cursor = self.connection.cursor() self.cursor.execute(self.sql,self.places) if not hasattr(self,'fldnms'): self.fldnms = [col[0] for col in self.cursor.description] if self.is_values: return [dict(zip(self.fldnms,row)) for row in self.cursor.fetchall()] return [self.query.model(**dict(zip(self.fldnms,row))) for row in self.cursor.fetchall()]
このクラスは何をしますか? 彼は要求を受け取り、準備されたSQLとパラメーターを選択します。 このようなリクエストを作成して、代用する各パラメーターが事前にわかっている特別な値を持つようにすることができます。 これらの値を使用して、実行中に渡された値を置き換える場所を検索します。
いくつかの追加の実装の詳細も、リソースの節約に役立ちます。
- fldnmsプロパティには、リクエストの最初の実行中に取得されたフィールド名の配列が含まれます。 後続の呼び出しでは、準備された配列が使用されます。
- replacesプロパティには、パラメータ名への置換名のマッピングが含まれています。
- クラスの各オブジェクトは、独自のカーソルを保持します。 このステップからの潜在的な加速は、まず、カーソルの作成がかなり高価な操作の結果であり、次に、データベースのバックエンドとして使用できるpyodbc記述からの次のフレーズの結果です。同じSQLを異なるパラメーターで繰り返し実行します。 SQLは1回だけ準備されます。 (pyodbcは最後のステートメントのみを準備するため、ステートメントを切り替えると、それぞれが複数回準備されます。) "
- is_valuesプロパティは、クエリがモデルオブジェクトを返すべきでないことを判断するのに役立ち、結果を返すときにそのようなオブジェクトを作成するのを節約します。
元のリクエストを少し変更して、そこで置換をスリップできるようにします。
>>> q = Group.objects.filter(user__id=12345).values('name') >>> q.query.sql_with_params() ('SELECT `auth_group`.`name` FROM `auth_group` INNER JOIN `auth_user_groups` ON (`auth_group`.`id` = `auth_user_groups`.`group_id`) WHERE `auth_user_groups`.`user_id` = %s ', (12345,))
置換として値12345を使用します。
>>> p = ParametrizedQuery(q,user_id=12345) >>> [g['name'] for g in p.execute(user_id=u.id)] [u'thetest']
p.execute()リクエストが実行されると、ユーザーIDの実際の値が12345の置換場所に置換されました。
次に、コードのパフォーマンスがどのように変化するかを見てみましょう。
>>> def test3(): ... import datetime ... t1 = datetime.datetime.now() ... for i in xrange(1000): ... a = [g['name'] for g in p.execute(user_id=u.id)] ... t2 = datetime.datetime.now() ... print "%s" % (t2 - t1) ... >>> test3() 0:00:00.217270
これが結果です! クエリの実行時間は7倍に短縮されました 。
実際のコードで使用する方法は?
まず、準備されたリクエストを保存できる場所が必要です。 次に、ある時点で、この変数を入力する必要があります。 たとえば、機能コードの最初の実行時。 そして第三に、もちろん、クエリを直接実行する代わりに、パラメータ化されたクエリの呼び出しを使用します。
def myview(request): if not hasattr(myview,'query'): myview.query = ParametrizedQuery(Group.objects.filter(user__id=12345).values('name'),user_id=12345) a = [g['name'] for g in myview.query.execute(user_id=request.user.id)] ...
すべてのコードが実行された場所:
-django.VERSION =(1、4、0、 'final'、0)
-mysql DBMS(django.db.backends.mysql)
-テーブルエンジン= MYISAM
-ローカルホスト経由の接続
-Python 2.7.2+(デフォルト、2011年10月4日、20:03:08)[GCC 4.6.1] on linux2
-Linuxホストseva 3.0.0-22-generic#36-Ubuntu SMP Tue Jun 12 17:13:04 UTC 2012 i686 athlon i386 GNU / Linux
専門家からのコメントを歓迎します。