sql >> Databáze >  >> RDS >> PostgreSQL

Spring + Hibernate:Využití mezipaměti plánu dotazů

Narazil jsem i na tento problém. V podstatě se to scvrkává na to, že máte v klauzuli IN proměnlivý počet hodnot a hibernace se snaží tyto plány dotazů uložit do mezipaměti.

Na toto téma jsou dva skvělé blogové příspěvky. První:

Použití Hibernate 4.2 a MySQL v projektu s dotazem v klauzuli, jako je:select t from Thing t where t.id in (?)

Hibernate ukládá tyto analyzované HQL dotazy do mezipaměti. Konkrétně HibernateSessionFactoryImplQueryPlanCache pomocí queryPlanCache aparameterMetadataCache . To se však ukázalo jako problém, když je počet parametrů pro klauzuli velký a mění se.

Tyto mezipaměti rostou pro každý odlišný dotaz. Takže tento dotaz s 6000parametry není stejný jako 6001.

Dotaz v klauzuli je rozšířen na počet parametrů v kolekci. Metadata jsou součástí plánu dotazů pro každý parametr v dotazu, včetně vygenerovaného názvu jako x10_, x11_ atd.

Představte si 4000 různých variací v počtu počtů parametrů v klauzuli, z nichž každý má v průměru 4000 parametrů. Metadata dotazu pro každý parametr se rychle sčítají v paměti a zaplňují hromadu, protože nelze shromažďovat odpadky.

Toto pokračuje, dokud nejsou všechny různé varianty v parametru dotazu uloženy do mezipaměti nebo dokud JVM nedojde paměť haldy a nezačne throwingjava.lang.OutOfMemoryError:Java halda prostor.

Možností je vyhnout se klauzulím a také použít pevnou velikost kolekce pro parametr (nebo alespoň menší velikost).

Pro konfiguraci maximální velikosti mezipaměti plánu dotazů viz vlastnosthibernate.query.plan_cache_max_size , výchozí nastavení je 2048 (snadno nástroj pro dotazy s mnoha parametry).

A druhý (také odkazovaný z prvního):

Hibernate interně používá mezipaměť, která mapuje příkazy HQL (asstring) na plány dotazů. Mezipaměť se skládá z ohraničené mapy, která je standardně omezena na 2048 prvků (lze konfigurovat). Všechny dotazy HQL se načítají prostřednictvím této mezipaměti. V případě netrefení se záznam automaticky přidá do mezipaměti. Díky tomu je velmi náchylná k thrashingu – scénáři, ve kterém neustále vkládáme nové položky do mezipaměti, aniž bychom je znovu použili, a tím bráníme, aby mezipaměť přinesla jakékoli zvýšení výkonu (dokonce přidává určitou režii správy mezipaměti). A co je horší, je těžké tuto situaci náhodou odhalit - musíte si cache explicitně vyprofilovat, abyste si všimli, že tam máte problém. Později řeknu pár slov o tom, jak by se to dalo udělat.

Takže vyrovnávání mezipaměti je výsledkem nových dotazů, které jsou generovány s vysokou rychlostí. To může být způsobeno řadou problémů. Dvě nejběžnější, které jsem viděl, jsou - chyby v režimu spánku, které způsobují, že se parametry vykreslují v příkazu JPQL namísto předávání jako parametry a použití klauzule "in".

Kvůli některým nejasným chybám v hibernaci dochází k situacím, kdy parametry nejsou zpracovány správně a jsou vykreslovány do JPQLquery (jako příklad se podívejte na HHH-6280). Pokud máte dotaz, který je ovlivněn takovými defekty a je spouštěn vysokou rychlostí, rozbije vaši mezipaměť plánu dotazů, protože každý vygenerovaný dotaz JPQL je téměř jedinečný (například obsahující ID vašich entit).

Druhý problém spočívá ve způsobu, jakým hibernace zpracovává dotazy s klauzulí „in“ (např. dejte mi všechny entity osob, jejichž pole id společnosti je jedno z 1, 2, 10, 18). Pro každý odlišný počet parametrů v klauzuli "in" vytvoří hibernace jiný dotaz - např.select x from Person x where x.company.id in (:id0_) pro 1 parametr,select x from Person x where x.company.id in (:id0_, :id1_) pro 2 parametry a tak dále. Všechny tyto dotazy jsou považovány za odlišné, pokud jde o mezipaměť plánu dotazů, což opět vede k cachethrashingu. Tento problém byste pravděpodobně mohli obejít tak, že napíšete třídu autility, která vytvoří pouze určitý počet parametrů - např. 1,10, 100, 200, 500, 1000. Pokud například předáte 22 parametrů, vrátí seznam 100 prvků s 22 parametry zahrnutými init a zbývajících 78 parametrů nastavených na nemožnou hodnotu (např. -1 pro ID používané pro cizí klíče). Souhlasím, že je to ošklivý hack, ale mohl by to udělat. V důsledku toho budete mít ve své mezipaměti pouze maximálně 6 jedinečných dotazů, a tím omezíte mlácení.

Jak tedy zjistíte, že máte problém? Můžete napsat nějaký další kód a vystavit metriky s počtem záznamů v mezipaměti, např. přes JMX, vyladit protokolování a analyzovat protokoly atd. Pokud nechcete (nebo nemůžete) aplikaci upravovat, můžete prostě vypsat haldu a spustit proti ní tento dotaz OQL (např. pomocí mat):SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l . Vypíše všechny dotazy aktuálně umístěné v libovolné mezipaměti plánu dotazů na vaší hromadě. Mělo by být docela snadné zjistit, zda se vás netýká některý z výše uvedených problémů.

Co se týče dopadu na výkon, těžko říct, protože to závisí na příliš mnoha faktorech. Viděl jsem velmi triviální dotaz, který způsobil 10-20 msof režii vynaloženou na vytvoření nového plánu dotazů HQL. Obecně platí, že pokud je někde keška, musí to mít dobrý důvod - chyba je pravděpodobně drahá, takže byste se měli pokusit co nejvíce vyvarovat chyb. V neposlední řadě bude vaše databáze muset zpracovat také velké množství jedinečných příkazů SQL – což způsobí, že je bude analyzovat a možná pro každý z nich vytvoří různé plány provádění.



  1. Klauzule ORDER BY je neplatná v pohledech, vložených funkcích, odvozených tabulkách, poddotazech a běžných tabulkových výrazech.

  2. SQL Server 2016:Vytvořte tabulku

  3. Nainstalujte Mtop (Monitorování databázového serveru MySQL) v RHEL/CentOS 6/5/4, Fedora 17-12

  4. Vytvoření databáze programově v SQL Server