И аз засегнах този проблем. По същество това се свежда до наличието на променлив брой стойности във вашата IN клауза и Hibernate, опитвайки се да кешира тези планове за заявка.
Има две страхотни публикации в блога на тази тема. Първата:
Използване на Hibernate 4.2 и MySQL в проект със заявка в клауза, като например:
select t from Thing t where t.id in (?)
Hibernate кешира тези анализирани HQL заявки. По-конкретно Hibernate
SessionFactoryImpl
имаQueryPlanCache
сqueryPlanCache
иparameterMetadataCache
. Но това се оказа проблем, когато броят на параметрите за вложената клауза е голям и варира.Тези кешове нарастват за всяка отделна заявка. Така че тази заявка с 6000параметри не е същата като 6001.
Заявката в клауза се разширява до броя на параметрите в колекцията. Метаданните са включени в плана на заявката за всеки параметър в заявката, включително генерирано име като x10_, x11_ и т.н.
Представете си 4000 различни вариации в броя на преброяванията на параметри в клауза, всеки от които със средно 4000 параметъра. Метаданните на заявката за всеки параметър бързо се събират в паметта, запълвайки купчината, тъй като не може да се събира боклук.
Това продължава, докато всички различни вариации в броя на параметрите на заявката не бъдат кеширани или JVM не изчерпи паметта си и не започне throwingjava.lang.OutOfMemoryError:Java heap пространство.
Избягването на вложени клаузи е опция, както и използването на фиксиран размер на колекцията за параметъра (или поне по-малък размер).
За конфигуриране на максималния размер на кеша на плана на заявките вижте свойството
hibernate.query.plan_cache_max_size
, като по подразбиране е2048
(лесно инструментариум за заявки с много параметри).
И второ (също споменато от първото):
Hibernate вътрешно използва кеш, който съпоставя HQL изрази (asstrings) към планове за заявки. Кешът се състои от ограничена карта, ограничена по подразбиране до 2048 елемента (конфигурируеми). Всички HQL заявки се зареждат през този кеш. В случай на пропуск, записът автоматично се добавя към кеша. Това го прави много податлив на разбиване - сценарий, при който ние постоянно поставяме нови записи в кеша, без никога да ги използваме повторно и по този начин предотвратяваме кеша да донесе каквито и да било печалби в производителността (той дори добавя някои допълнителни разходи за управление на кеша). За да се влошат нещата, е трудно да се открие случайно тази ситуация - трябва изрично да профилирате кеша, за да забележите, че имате проблем там. Ще кажа няколко думи за това как може да стане това по-късно.
Така че разбиването на кеша е резултат от генерирането на нови заявки при thigh скорости. Това може да бъде причинено от множество проблеми. Двете най-често срещани, които съм виждал, са - грешки в хибернация, които карат параметрите да се изобразяват в оператора JPQL вместо да се предават като параметри и използването на клауза "in".
Поради някои неясни грешки в хибернация, има ситуации, когато параметрите не се обработват правилно и се изобразяват в JPQLquery (като пример вижте HHH-6280). Ако имате заявка, която е засегната от такива дефекти и се изпълнява с високи скорости, тя ще разбие кеша на плана на заявката ви, тъй като всяка генерирана JPQL заявка е почти уникална (съдържаща идентификатори на вашите обекти например).
Вторият проблем е в начина, по който хибернацията обработва заявки с клауза "в" (например, дайте ми всички физически субекти, чието идентификационно поле на компанията е едно от 1, 2, 10, 18). За всеки отделен брой параметри в клаузата "in", hibernate ще произведе различна заявка - напр.
select x from Person x where x.company.id in (:id0_)
за 1 параметър,select x from Person x where x.company.id in (:id0_, :id1_)
за 2 параметъра и така нататък. Всички тези заявки се считат за различни, що се отнася до кеша на плана на заявките, което води отново до кеша. Вероятно бихте могли да заобиколите този проблем, като напишете клас за помощ, за да произведете само определен брой параметри - напр. 1,10, 100, 200, 500, 1000. Ако например предадете 22 параметъра, той ще върне списък от 100 елемента с 22 параметъра, включени в init и останалите 78 параметъра, зададени на невъзможна стойност (напр. -1 за идентификатори използвани за външни ключове). Съгласен съм, че това е грозен хак, но може да свърши работата. В резултат на това ще имате само най-много 6 уникални заявки във вашия кеш и по този начин ще намалите разбиването.И така, как да разберете, че имате проблем? Можете да напишете някакъв допълнителен код и да изложите показатели с броя на записите в кеша, напр. през JMX, настройте регистрирането и анализирайте регистрационните файлове и т.н. Ако не искате (или не можете) да модифицирате приложението, можете просто да изхвърлите купчината и да изпълните тази OQL заявка срещу нея (например с помощта на mat):
SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l
. Той ще изведе всички заявки, които в момента се намират във всеки кеш на плана на заявки във вашата купчина. Би трябвало да е доста лесно да забележите дали сте засегнати от някой от гореспоменатите проблеми.Що се отнася до въздействието върху производителността, е трудно да се каже, тъй като зависи от твърде много фактори. Виждал съм много тривиална заявка, която причинява 10-20 ms режийни разходи, изразходвани за създаване на нов план за HQL заявка. Като цяло, ако някъде има кеш, трябва да има основателна причина за това - грешката вероятно е скъпа, така че трябва да се опитате да избягвате пропуски колкото е възможно повече. Не на последно място, вашата база данни ще трябва да обработва и големи количества уникални SQL изрази – което я кара да ги анализира и може би да създава различни планове за изпълнение за всеки един от тях.