java - una - hql
¿Se puede usar Hibernate en aplicaciones sensibles al rendimiento? (4)
¿Has probado la estrategia de búsqueda "join" para las colecciones?
Estoy viendo problemas de rendimiento al recuperar múltiples instancias de objetos que tienen muchas relaciones con otros objetos. Estoy usando la implementación de JPA de Spring and Hibernate con MySQL. El problema es que al ejecutar una consulta JPA, Hibernate no se une automáticamente a otras tablas. Esto da como resultado n * r + 1 consultas SQL, donde n es la cantidad de objetos que se recuperan y r es el número de relaciones.
Ejemplo, una persona vive en una dirección, tiene muchos pasatiempos y ha visitado muchos países:
@Entity
public class Person {
@Id public Integer personId;
public String name;
@ManyToOne public Address address;
@ManyToMany public Set<Hobby> hobbies;
@ManyToMany public Set<Country> countriesVisited;
}
Cuando realizo una consulta JPA para obtener todas las Personas llamadas Bob, y hay 100 Bobs en la base de datos:
SELECT p FROM Person p WHERE p.name=''Bob''
Hibernate traduce esto a 301 consultas SQL:
SELECT ... FROM Person WHERE name=''Bob''
SELECT ... FROM Address WHERE personId=1
SELECT ... FROM Address WHERE personId=2
...
SELECT ... FROM Hobby WHERE personId=1
SELECT ... FROM Hobby WHERE personId=2
...
SELECT ... FROM Country WHERE personId=1
SELECT ... FROM Country WHERE personId=2
...
De acuerdo con las preguntas frecuentes de Hibernate ( here y here ), la solución es especificar LEFT JOIN o LEFT OUTER JOIN (para muchos a muchos) en la consulta. Así que ahora mi consulta se ve así:
SELECT p, a, h, c FROM Person p
LEFT JOIN p.address a LEFT OUTER JOIN p.hobbies h LEFT OUTER JOIN p.countriesVisited c
WHERE p.name = ''Bob''
Esto funciona, pero parece haber un error si hay más de una UNIÓN EXTREMA IZQUIERDA, en cuyo caso Hibernate está buscando incorrectamente una columna inexistente:
could not read column value from result set: personId69_2_; Column ''personId69_2_'' not found.
El comportamiento de los errores parece ser abordado por el error Hibernate Core HHH-3636 . Lamentablemente, la corrección no forma parte de ningún HAR de Hibernate lanzado. He ejecutado mi aplicación contra la compilación de instantáneas, pero el comportamiento de los errores aún está presente. También construí mi Hibernate Core JAR desde el último código en el repositorio y el comportamiento de los errores aún está presente. Entonces, tal vez HHH-3636 no aborda esto.
Esta limitación de rendimiento de Hibernate es muy frustrante. Si consulto 1000 objetos, se realizarán 1000 consultas SQL * 1 + en la base de datos. En mi caso tengo 8 relaciones, así que recibo 8001 consultas SQL, lo que resulta en un rendimiento horrible. La solución oficial de Hibernate para esto es unir todas las relaciones. Pero esto no es posible con más de una relación de muchos a muchos debido al comportamiento de los errores. Así que estoy atascado con combinaciones de la izquierda para las relaciones de muchos a uno y n * r + 1 consultas debido a las relaciones de muchos a muchos. Planeo enviar el problema LEFT OUTER JOIN como un error de Hibernate, pero mientras tanto, mi cliente necesita una aplicación que tenga un rendimiento razonable. Actualmente uso una combinación de búsqueda por lotes (BatchSize), ehcache y memoria caché personalizada en la memoria, pero el rendimiento es aún bastante pobre (mejoró la recuperación de 5000 objetos de 30 a 8 segundos). La conclusión es que demasiadas consultas SQL están llegando a la base de datos.
Entonces, mis preguntas, ¿es posible usar Hibernate en aplicaciones sensibles al rendimiento donde las tablas tienen relaciones múltiples entre sí? Me encantaría saber qué tan exitoso Hibernate usa el rendimiento de las direcciones. ¿Debo estar escribiendo SQL a mano (lo cual de alguna manera frustra el propósito de usar Hibernate)? ¿Debo normalizar mi esquema de base de datos para reducir el número de tablas unidas? ¿No debería utilizar Hibernate si necesito un rendimiento de consulta rápido? ¿Hay algo más rápido?
Además de la estrategia de "búsqueda", también puede intentar configurar el tamaño de búsqueda por lotes en propiedades de hibernación, por lo que ejecutará las consultas de unión no una por una sino en lotes.
En su appContext.xml:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
...
<property name="hibernateProperties">
<props>
...
<prop key="hibernate.default_batch_fetch_size">32</prop>
</props>
</property>
</bean>
Entonces, en lugar de:
SELECT ... FROM Hobby WHERE personId=1
SELECT ... FROM Hobby WHERE personId=2
Conseguirás:
SELECT ... FROM Hobby WHERE personId in (1,2,...,32);
SELECT ... FROM Hobby WHERE personId in (33,34,...,64);
Si necesita una función de Hibernate y esta función tiene errores, tiene dos opciones: a) Enviar una solicitud de error y usar una solución alternativa (ejecución lenta o sql manuscrita) hasta que se solucione el error, lo que llevará un tiempo b) Enviar una solicitud de error junto con una corrección de errores y pruebas. (Por supuesto, podría usar la corrección de errores y omitir la parte bugrequest y prueba).
Vea mi respuesta a su otra pregunta , si leyó la totalidad de las preguntas frecuentes a las que se vinculó:
¡Sigue la guía de mejores prácticas! Asegúrese de que todas las asignaciones especifiquen lazy = "true" en Hibernate2 (este es el nuevo valor predeterminado en Hibernate3). Utilice HQL LEFT JOIN FETCH para especificar qué asociaciones necesita recuperar en el SQL SELECT inicial.
Una segunda forma de evitar el problema de n + 1 selecciona es usar fetch = "subselect" en Hibernate3.
Si aún no está seguro, consulte la documentación de Hibernate e Hibernate en acción.
Vea los consejos para mejorar el rendimiento . Si no tiene cuidado con las uniones, terminará con problemas de productos cartesianos .