ロヌドされたプロゞェクトで効果的な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は、倉曎たたは補完が簡単か぀迅速です。 たた、合理的に簡単なリク゚ストを䜜成すれば、読みやすく理解しやすくなりたす。

ごめんなさい、みんな 䜕ずか...コメント、提案、質問、提案を埅っおいたす

Source: https://habr.com/ru/post/J175727/


All Articles