ロードされたプロジェクトで効果的なDjango-ORMリクエストを作成するための注意

以来書かれています 別のHolivarは、高負荷プロジェクト(HL)のSQL vs ORMに関するコメントに登場しました



前文



メモでは、いくつかの場所でありふれたものを見つけることができます。 それらのほとんどはドキュメントで利用できますが、現代人はしばしば表面的にすべてを手に入れたいです。 はい、そして多くは単にHLプロジェクトで自分自身をテストする機会を持っていませんでした。

この記事を読んでいるとき、覚えておいてください:



そして、そして、そして、はい、この記事は、3年以上のDjangoでの作業で遭遇したORMの理解の誤りを示します。





理解されていないORM



私はかなり長い間私を悩ませていた古典的な間違いから始めます。 ウルグアイ猿の部族の信仰について。 Django ORMの全能性、つまり

Klass.objects.all()
      
      





例えば:

 all_result = Klass.objects.all() result_one = all_result.filter(condition_field=1) result_two = all_result.filter(condition_field=2)
      
      







私の夢では、次のように考えていました。



おそらく、魔法の猿はいないと推測しているでしょう。この場合、3つのクエリがあります。 しかし、私はあなたを失望させます。 この場合、さらに2つのクエリがあり、さらに正確に言うと、このスクリプトの結果に基づいたクエリは1つではありません(もちろん、将来的にはそうはなりません)。 なぜですか?

順番に説明します。 このコードには3つのクエリがあることを証明しましょう。



やった! 3つのリクエストがあることを証明しました。 しかし、主なフレーズは「計算で」です。 実際、2番目の部分、つまりリクエストが2つしかないことの証明に移ります。

この問題では、次のORMの理解(2文)が役立ちます。



したがって、最初の行では、関心のあるクエリで変数all_resultを指定しました-すべて選択します。

2行目と3行目で、追加の選択のリクエストを指定します。 条件。 さて、したがって、2つの要求を受け取りました。 どちらを証明すべきか

気配りのある読者(前の段落をもう一度見たのはなぜですか?)リクエストをしていないことをすでに推測しているはずです。 そして、2行目と3行目では、同様に関心のあるリクエストを作成しましたが、データベースに連絡しませんでした。

だから私たちはナンセンスをしていた。 そして、計算は、例えば、下流のコードの最初の行から始まります:

 for result in result_one: print result.id
      
      







必ずしも必要な機能と合理的な選択ではない


テンプレートと、一部の人々が愛している__unicode __()関数試してみましょう。

あなたが知っている-クールな機能! どこでも、いつでも、どんな状況でも、興味のある名前を取得できます。 いいね! そして、それまでは、出力にForeignKeyが表示されるまで。 表示されたらすぐに、すべてがなくなったことを考慮してください。

小さな例を考えてみましょう。 ニュースは1行でお知らせします。 このニュースが関連付けられている地域があります。

 class RegionSite(models.Model): name = models.CharField(verbose_name="", max_length=200,) def __unicode__(self): return "%s" % self.name class News(models.Model): region = models.ForeignKey(RegionSite, verbose_name="") date = models.DateField(verbose_name="", blank=True, null=True, ) name = models.CharField(verbose_name="", max_length=255) def __unicode__(self): return "%s (%s)" % (self.name, self.region)
      
      





Newsで定義されている名前で、最新の10個のニュースを印刷する必要があります。.__ unicode __()

袖を開けて、書いてください:

 news = News.objects.all().order_by("-date")[:10]
      
      





テンプレート内:

 {% for n in news %} {{ n }} {% endfor %}
      
      





そして、ここで自分たちのために穴を掘りました。 これがニュースではない場合、または10個ではなく1万個ある場合は、10,000件のリクエスト+ 1を受信するという事実に備えてください。すべてはForeignKey mudbloodによるものです。

余分な1万件のクエリの例(小さなモデルをお持ちいただきありがとうございます-すべてのフィールドとモデル値は、10または50フィールドであっても、この方法で選択されます):

 SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1 SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1 SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 2 SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1 -- 
      
      





なぜこれが起こっているのですか? すべてが性器に簡単です。 ニュースの名前を取得するたびに、 __ unicode __()値を返すようにRegionSiteに要求し、ニュースの地域の名前を表示するために括弧で置き換えます。

同様に、たとえばORMを使用するテンプレートで、たとえば次のように必要な値に到達しようとすると、悪い状況が始まります。

 {{ subgroup.group.megagroup.name }}
      
      





難しい要求があるかもしれないとは思わないでしょう:)テンプレートにそのようなサンプルを何十個も含めることができるとは言っていません!

あなたはあまり私たちを連れて行かないでしょう-私たちはすすり泣き 、ORMの次の素晴らしい機会を利用しました-.values()

魔法のキーボードのようなコード行は次のようになります。

 news = News.objects.all().values("name", "region__name").order_by("-date")[:10]
      
      





テンプレート:

 {% for n in news %} {{ n.name }} ({{ n.region__name }}) {% endfor %}
      
      





二重アンダースコアに注意してください。 すぐに役立つでしょう。 (知らない人のために-二重下線、つまり、モデル間の接続、大まかに言って)

これらの簡単な操作により、1万件のリクエストを取り除き、1件のリクエストのみを残しました。 ところで、はい、JOINおよび選択したフィールドで動作します!

 SELECT `news_news`.`name`, `seo_regionsite`.`name` FROM `news_news` INNER JOIN `seo_regionsite` ON (`news_news`.`region_id` = `seo_regionsite`.`id`) LIMIT 10
      
      





とてもうれしいです! 結局のところ、私たちはORMオプティマイザーになりました:) お知らせします:)この最適化は、ニュースが1万件になるまでの最適化です。 しかし、私たちはもっと速くそれを行うことができます!

これを行うには、リクエストの数に関する偏見を考慮し、リクエストの数を緊急に2倍に増やしてください! つまり、データを準備します。

 regions = RegionSite.objects.all().values("id", "name") region_info = {} for region in regions: region_info[region["id"]] = region["name"] news = News.objects.all().values("name", "region_id").order_by("-date")[:10] for n in news: n["name"] = "%s (%s)" % (n["name"], region_info[n["region_id"]])
      
      





そして、新しく設定した変数のテンプレートの出力:

 {% for n in news %} {{ n.name }} {% endfor %}
      
      





はい、わかりました...これらの行では、MVTの概念に違反しました。 ただし、これは単なる例であり、MVT標準に違反しない行に簡単に変換できます。

私たちは何をしましたか?

  1. 地域に関するデータを準備し、それらに関する情報を辞書に入力しました。

     SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite`
          
          





  2. ニュースから選択したものは、私たちが興味を持っているものすべてに加えて、単一の下線に注意を払います。

     SELECT `news_news`.`name`, `news_news`.`region_id` FROM `news_news` LIMIT 10
          
          





    データベース内のリンクの直接的な意味を選択したのは、単一の下線です。
  3. 2つのモデルをpythonで接続しました。


私を信じて、単一のForeignKeysでは、速度の増加にほとんど気付かないでしょう(特に選択可能なフィールドが少ない場合)。 ただし、モデルが複数のモデルとの鍛造を通じて通信する場合、これがこの決定のお祝いの始まりです。

二重下線と単一下線に悩まされ続けましょう。

バナリティのポイントに簡単な例を考えてみましょう:

 item.group_id vs. item.group.id
      
      





クエリを作成するときだけでなく、結果を処理するときにも、この機能を実行できます。

例:

 for n in News.objects.all(): print n.region_id
      
      





リクエストは1つだけです-ニュースを選択するとき

例2:

 for n in News.objects.all(): print n.region.id
      
      





リクエストは1万+ 1になります。 各反復で、idのリクエストがあります。 同様になります:

 SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1
      
      





これは、1つの記号によるこのような違いです。

現在、多くの高度なdzhangovodyが私のコードでVoodoo人形を指しています。 そして同時に、彼らは私に質問をします-データ準備でブリザードのために何をしていますか、そして、 values_list( "id"、flat = True)はどこにありますか?

value_listを使用する際の正確性の必要性を示す素晴らしい例を見てみましょう。

 regions_id = RegionSite.objects.filter(id__lte=10).values_list("id", flat=True) for n in News.objects.filter(region__id__in=regions_id): print n.region_id
      
      





これらのコード行を使用して、次のことを行います。

  1. いくつかの抽象的な条件によって、関心のある地域のid-shnikのリストを準備しています。
  2. 結果の結果がニュースクエリに挿入され、次の結果が得られます。

     SELECT `news_news`.`id`, `news_news`.`region_id`, `news_news`.`date`, `news_news`.`name` FROM `news_news` WHERE `news_news`.`region_id` IN (SELECT U0.`id` FROM `seo_regionsite` U0 WHERE U0.`id` <= 10 )
          
          





リクエストでリクエスト! Uuuuh、私は大好きです:)特に、IN(10,000 ID)の埋め込みselectで10,000ニュースを選択します

これが何を脅かすのか確かに理解していますか? :)そうでない場合-その後、理解-何も、絶対に良い何も!

この問題の解決策も天才シンプルです。 記事の冒頭を思い出してください-変数を評価しないとクエリは表示されません。 そして、たとえば、コードの2行目にコメントを付けます。

 for n in News.objects.filter(region__id__in=list(regions_id)):
      
      





このソリューションでは、2つの単純なクエリを取得します。 添付なし。

あなたはまだ私たちのために店にいるろくでなしORMから息をしていませんか? その後、さらに深くドロップします。 コードを考慮してください:

 regions_id = list(News.objects.all().values_list("region_id", flat=True)) print RegionSite.objects.filter(id__in=regions_id)
      
      





これらの2行で、ニュースのある地域のリストを選択します。 このコードのすべては、1つのポイント、つまり結果のリクエストを除いて素晴らしいものです。

 SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` IN (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9) LIMIT 21
      
      





あはは、ORM、やめて! 何してるの!

それだけでなく、すべてのニュース(この例では256個のようです)から、彼はリージョンのIDを選択し、それらを単に置換したので、どこかから制限21を取得しました。私は別の言い訳を見つけませんでした)、しかし、ここに価値があると明らかに待ち伏せがあります。

前の例のように、解決策は簡単です。

 print RegionSite.objects.filter(id__in=set(regions_id)).values("id", "name")
      
      





set()を使用して余分な要素を削除すると、予想どおり、非常に適切なリクエストを受け取りました。

 SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` IN (1, 2, 3, 4, 9) LIMIT 21
      
      





誰もが幸せで、誰もが幸せです。

歴史的に書かれたコードを少し見ながら、知っておくべき別のパターンを強調します。 そして再び、サンプルコード:

 region = RegionSite.objects.get(id=1) t = datetime.datetime.now() for i in range(1000): list(News.objects.filter(region__in=[region]).values("id")[:10]) # list(News.objects.filter(region__id__in=[region.id]).values("id")[:10]) # list(News.objects.filter(region__in=[1]).values("id")[:10]) # list(News.objects.filter(region__id__in=[1]).values("id")[:10]) print datetime.datetime.now() - t
      
      





各反復行は順番に含まれていました(1つだけが機能するように)。 合計で、次の概数を取得できます。



差異は最小限ですが、目に見えます。 優先される2および4オプション(主に4mを使用)。 時間の主な無駄は、リクエストを作成する速さです。 些細ですが、重要だと思います。 読者はそれぞれ独立して結論を​​出します。

そして、私たちは怖い言葉でこの記事を終わります。

特別な場合:



1対2の更新/挿入が行われます

  1. データを挿入し、データを更新するために、2つの辞書を準備します
  2. 関数に入れる各辞書
  3. 利益!


実際の更新関数の例:

 @transaction.commit_manually def update_region_price(item_prices): """     """ from idea.catalog.models import CatalogItemInfo try: for ip in item_prices: CatalogItemInfo.objects.filter( item__id=ip["item_id"], region__id=ip["region_id"] ).update( kost=ip["kost"], price=ip["price"], excharge=ip["excharge"], zakup_price=ip["zakup_price"], real_zakup_price=ip["real_zakup_price"], vendor=ip["vendor"], srok=ip["srok"], bonus=ip["bonus"], rate=ip["rate"], liquidity_factor=ip["liquidity_factor"], fixed=ip["fixed"], ) except Exception, e: print e transaction.rollback() return False else: transaction.commit() return True
      
      







実際の加算関数の例:

 @transaction.commit_manually def insert_region_price(item_prices): """     """ from idea.catalog.models import CatalogItemInfo try: for ip in item_prices: CatalogItemInfo.objects.create(**ip) except Exception, e: print e transaction.rollback() return False else: transaction.commit() return True
      
      





これらの点を知っていると、Django ORMを使用して、SQLコードに適合しない効果的なアプリケーションを構築できます。



質問への回答:


そのようなダンスはなくなったので、ORMを使用する価値がある場合とそうでない場合に書いてください。 (c) lvo

ORMは単純な場合は常に使用すべきだと思います。 ORMを肩にかけないでください。さらに、次のような基本クエリを実行しないでください。
 User.objects.values('username', 'email').annotate(cnt=Count('id')).filter(cnt__gt=1).order_by('-cnt')
      
      





特にHL生産。 お腹がすいた別のシステムサーバーを自分で入手してください。

単純な「ORMクエリ」を記述できない場合は、問題を解決するためのアルゴリズムを変更してください。

たとえば、IMのクライアントには、正規表現を使用した特性によるフィルタリングがあります。 サイト訪問者が非常に多くなるまで、クールで柔軟なこと。 標準のClient-ORM-Base-ORM-Clientの代わりにアプローチを変更し、Client-MongoDB-Python-Clientに書き直しました。 MongoDBのデータは、システムサーバー上のORMによって生成されます。 前に述べたように、HLはORMだけを操作しても達成できません



なぜジャンゴなのかしら。 他のフレームワーク/テクノロジーと比較して、このフレームワーク(およびそのORM)の利点は何ですか。 (c) アンジェンサン

歴史的に。 PythonはDjangoで勉強を始めました。 そして、その使用技術に関する知識を最大限に活用します。 ピラミッドの並行研究中。 これまでのところ、PHPとそのフレームワークであるcmsとのみ比較できます。 私はおそらく一般的なフレーズを言うでしょう-PHPで書いたとき、私は時間を無駄に無駄にしました

これで、Django 1.3.4のいくつかの重大な欠陥に名前を付けることができます。

  1. ベースとの永続的な接続/切断(古いバージョンで修正済み)
  2. テンプレートプロセッサの速度。 ネットワークで見つかったテストによると、それは十分に小さいです。 変更する必要があります:)


一般に、テンプレートプロセッサの生成速度を上げる方法には、1つのクールなトリックがあります。

ローカル変数を介してテンプレートに変数を渡さない() -バルク関数と中間変数を使用すると、静かでゆっくりと動く死のモンスターが得られます:)



SQLクエリを書くのが難しいプログラマはどんな人ですか? (c) andreynikishaev

Base-Codeデータ処理間の相​​互作用の手段ではなく、プログラムコードに時間をかけるプログラマー。 SQLを知っている必要があります-多くの場合、データベースコンソールを直接操作します。 しかし、コードでは-ORM。 ORMは、変更または補完が簡単かつ迅速です。 また、合理的に簡単なリクエストを作成すれば、読みやすく理解しやすくなります。



ごめんなさい、みんな! (何とか...コメント、提案、質問、提案を待っています)



All Articles