Jeg har også ramt dette problem. Det går dybest set ned til at have et variabelt antal værdier i din IN-klausul og Hibernate, der prøver at cache disse forespørgselsplaner.
Der er to gode blogindlæg om dette emne. Det første:
Brug af Hibernate 4.2 og MySQL i et projekt med en in-clause-forespørgsel som:select t from Thing t where t.id in (?)
Hibernate cacher disse parsede HQL-forespørgsler. Specifikt HibernateSessionFactoryImpl
har QueryPlanCache
med queryPlanCache
og parameterMetadataCache
. Men dette viste sig at være et problem, når antallet af parametre for in-clausen er stort og varierer.
Disse caches vokser for hver enkelt forespørgsel. Så denne forespørgsel med 6000 parametre er ikke det samme som 6001.
In-clause-forespørgslen udvides til antallet af parametre i samlingen. Metadata er inkluderet i forespørgselsplanen for hver parameter i forespørgslen, inklusive et genereret navn som x10_, x11_ osv.
Forestil dig 4000 forskellige variationer i antallet af in-clause parametertællinger, hver af disse med et gennemsnit på 4000 parametre. Forespørgselsmetadataene for hver parameter lægges hurtigt op i hukommelsen og fylder dyngen op, da den ikke kan opsamles skrald.
Dette fortsætter, indtil alle forskellige variationer i forespørgselsparametertællingen er cachelagret, eller JVM løber tør for heap-hukommelse og begynder at throwingjava.lang.OutOfMemoryError:Java-heap-plads.
At undgå in-klausuler er en mulighed, såvel som at bruge en fast samlingsstørrelse for parameteren (eller i det mindste en mindre størrelse).
Se egenskabenhibernate.query.plan_cache_max_size
for at konfigurere forespørgselsplanens cache-maks. , der som standard er 2048
(let værktøj til forespørgsler med mange parametre).
Og for det andet (også refereret fra den første):
Hibernate bruger internt en cache, der kortlægger HQL-sætninger (asstrings) til at forespørge planer. Cachen består af et afgrænset kort, der som standard er begrænset til 2048 elementer (kan konfigureres). Alle HQL-forespørgsler indlæses gennem denne cache. I tilfælde af en glip føjes posten automatisk til cachen. Dette gør det meget modtageligt for thrashing - ascenario, hvor vi konstant lægger nye poster ind i cachen uden nogensinde at genbruge dem og dermed forhindrer cachen i at give nogen præstationsgevinster (det tilføjer endda nogle cache-administration overhead). For at gøre tingene værre, er det svært at opdage denne situation tilfældigt - du skal udtrykkeligt profilere cachen for at bemærke, at du har et problem der. Jeg vil sige et par ord om, hvordan dette kunne gøres senere.
Så cache-tæsk er resultatet af nye forespørgsler, der genereres med høje hastigheder. Dette kan skyldes en lang række problemer. De to mest almindelige, som jeg har set, er - fejl i dvaletilstand, som forårsager, at parametre gengives i JPQL-sætningen i stedet for at blive sendt asparametre og brugen af en "in" - klausul.
På grund af nogle obskure fejl i dvaletilstand, er der situationer, hvor parametre ikke håndteres korrekt og gengives i JPQLquery (tjek som et eksempel HHH-6280). Hvis du har en forespørgsel, der er påvirket af sådanne defekter, og den udføres med høje hastigheder, vil den ødelægge din forespørgselsplans cache, fordi hver JPQL-forespørgsel, der genereres, er næsten unik (indeholder f.eks. ID'er for dine enheder).
Det andet problem ligger i den måde, at dvale behandler forespørgsler med en "in"-klausul (giv mig f.eks. alle personenheder, hvis virksomheds-id-felt er et af 1, 2, 10, 18). For hvert enkelt antal parametre i "in"-klausulen, vil hibernate producere en anden forespørgsel - f.eks.select x from Person x where x.company.id in (:id0_)
for 1 parameter,select x from Person x where x.company.id in (:id0_, :id1_)
for 2 parametre og så videre. Alle disse forespørgsler betragtes som forskellige, hvad angår forespørgselsplanens cache, hvilket igen resulterer i cachethrashing. Du kunne sikkert løse dette problem ved at skrive en hjælpeklasse til kun at producere et bestemt antal parametre - f.eks. 1,10, 100, 200, 500, 1000. Hvis du f.eks. sender 22 parametre, vil det returnere en liste med 100 elementer med de 22 parametre inkluderet i det og de resterende 78 parametre sat til en umulig værdi (f.eks. -1 for ID'er bruges til fremmednøgler). Jeg er enig i, at dette er et grimt hack, men kunne få arbejdet gjort. Som et resultat vil du højst have 6 unikke forespørgsler i din cache og dermed reducere thrashing.
Så hvordan finder du ud af, at du har problemet? Du kan skrive en ekstra kode og afsløre metrics med antallet af poster i cachen, f.eks. over JMX, tune logning og analysere logfilerne osv. Hvis du ikke vil (eller ikke kan) ændre applikationen, kan du bare dumpe heapen og køre denne OQL-forespørgsel mod den (f.eks. ved at bruge mat):SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l
. Det vil udlæse alle forespørgsler, der i øjeblikket er placeret i enhver forespørgselsplan-cache på din heap. Det burde være ret nemt at få øje på, om du er berørt af nogen af de førnævnte problemer.
Hvad angår præstationspåvirkningen, er det svært at sige, da det afhænger af for mange faktorer. Jeg har set en meget triviel forespørgsel, der forårsager 10-20 msof overhead brugt på at skabe en ny HQL-forespørgselsplan. Generelt, hvis der er en cache et eller andet sted, skal der være en god grund til det - amiss er sandsynligvis dyrt, så du bør prøve at undgå glip så meget som muligt. Sidst, men ikke mindst, skal din database også håndtere store mængder af unikke SQL-sætninger - hvilket får den til at analysere dem og måske oprette forskellige eksekveringsplaner for hver enkelt af dem.