Jag har också stött på det här problemet. Det handlar i princip om att ha ett variabelt antal värden i din IN-sats och Hibernate försöker cachelagra dessa frågeplaner.
Det finns två bra blogginlägg om detta ämne. Det första:
Använda Hibernate 4.2 och MySQL i ett projekt med en fråga i klausulen som:
select t from Thing t where t.id in (?)
Hibernate cachar dessa analyserade HQL-frågor. Närmare bestämt Hibernate
SessionFactoryImpl
harQueryPlanCache
medqueryPlanCache
ochparameterMetadataCache
. Men detta visade sig vara ett problem när antalet parametrar för in-klausulen är stort och varierar.Dessa cachar växer för varje distinkt fråga. Så den här frågan med 6000parametrar är inte densamma som 6001.
Frågan i klausulen utökas till antalet parametrar i samlingen. Metadata ingår i frågeplanen för varje parameter i frågan, inklusive ett genererat namn som x10_, x11_ osv.
Föreställ dig 4000 olika variationer i antalet in-clause parametercounts, var och en av dessa med ett genomsnitt på 4000 parametrar. Frågemetadata för varje parameter läggs snabbt ihop i minnet och fyller upp högen, eftersom den inte kan samlas in som skräp.
Detta fortsätter tills alla olika varianter av frågeparameterantal cachelagras eller tills JVM tar slut på högminne och börjar throwingjava.lang.OutOfMemoryError:Java-högutrymme.
Att undvika in-klausuler är ett alternativ, liksom att använda en fast samlingsstorlek för parametern (eller åtminstone en mindre storlek).
För att konfigurera frågeplanens maxstorlek för cache, se egenskapen
hibernate.query.plan_cache_max_size
, som standard är2048
(enkelt verktyg för frågor med många parametrar).
Och för det andra (även refererat från den första):
Hibernate använder internt en cache som mappar HQL-satser (asstrings) för att fråga planer. Cachen består av en avgränsad karta begränsad som standard till 2048 element (konfigurerbar). Alla HQL-frågor laddas via denna cache. Vid missar läggs posten automatiskt till i cachen. Detta gör det mycket känsligt för thrashing - ascenario där vi ständigt lägger in nya poster i cachen utan att någonsin återanvända dem och därmed förhindrar cachen från att ge några prestandavinster (det lägger till och med en del cachehanteringsoverhead). För att göra saken värre är det svårt att upptäcka den här situationen av en slump - du måste explicit profilera cachen för att märka att du har ett problem där. Jag kommer att säga några ord om hur detta skulle kunna göras senare.
Så cache-trashningen är resultatet av nya frågor som genereras i höga hastigheter. Detta kan orsakas av en mängd problem. De två vanligaste som jag har sett är - buggar i viloläge som gör att parametrar renderas i JPQL-satsen istället för att skickas som parametrar och användningen av en "in" - sats.
På grund av några oklara buggar i viloläge, finns det situationer när parametrar inte hanteras korrekt och renderas i JPQLquery (kolla till exempel HHH-6280). Om du har en fråga som påverkas av sådana defekter och den exekveras med höga hastigheter, kommer den att krossa din frågeplans cache eftersom varje JPQL-fråga som genereras är nästan unik (som innehåller till exempel ID:n för dina enheter).
Den andra frågan handlar om hur viloläge behandlar frågor med en "in"-klausul (till exempel ge mig alla personenheter vars företags-id-fält är ett av 1, 2, 10, 18). För varje distinkt antal parametrar i "in"-satsen kommer hibernate att producera en annan fråga - t.ex.
select x from Person x where x.company.id in (:id0_)
för 1 parameterselect x from Person x where x.company.id in (:id0_, :id1_)
för 2 parametrar och så vidare. Alla dessa frågor anses olika, vad gäller frågeplanens cache, vilket återigen resulterar i cachethrashing. Du skulle förmodligen kunna kringgå det här problemet genom att skriva en hjälpklass för att bara producera ett visst antal parametrar - t.ex. 1,10, 100, 200, 500, 1000. Om du till exempel skickar 22 parametrar kommer det att returnera en lista med 100 element med de 22 parametrarna inkluderade och de återstående 78 parametrarna inställda på ett omöjligt värde (t.ex. -1 för ID:n) används för främmande nycklar). Jag håller med om att det här är ett fult hack men skulle kunna få jobbet gjort. Som ett resultat kommer du bara att ha högst 6unika frågor i din cache och på så sätt minska thrashing.Så hur får du reda på att du har problemet? Du kan skriva ytterligare en kod och visa statistik med antalet poster i cachen, t.ex. över JMX, justera loggning och analysera loggarna, etc. Om du inte vill (eller inte kan) modifiera applikationen kan du bara dumpa högen och köra denna OQL-fråga mot den (t.ex. med mat):
SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l
. Det kommer att mata ut alla frågor som för närvarande finns i valfri frågeplanscache på din hög. Det borde vara ganska lätt att upptäcka om du påverkas av något av de ovan nämnda problemen.När det gäller prestandapåverkan är det svårt att säga eftersom det beror på för många faktorer. Jag har sett en mycket trivial fråga som orsakar 10-20 msof overhead som spenderas på att skapa en ny HQL-frågeplan. I allmänhet, om det finns en cache någonstans, måste det finnas en bra anledning till det - amiss är förmodligen dyrt så du bör försöka undvika missar så mycket som möjligt. Sist men inte minst kommer din databas att behöva hantera stora mängder unika SQL-satser också - vilket får den att analysera dem och kanske skapa olika exekveringsplaner för var och en av dem.