Pregunta

La "N+1 selecciona problema" es, generalmente, se expresa como un problema de mapeo Objeto-Relacional (ORM) los debates, y entiendo que tiene algo que ver con tener que hacer un montón de consultas de base de datos para algo que parece simple en el objeto del mundo.

¿Alguien tiene una explicación más detallada del problema?

¿Fue útil?

Solución

Digamos que usted tiene una colección de Car (objetos de la base de datos de filas), y cada Car tiene una colección de Wheel los objetos (también de las filas).En otras palabras, Car -> Wheel es un 1-a-muchos de relación.

Ahora, digamos que usted necesita para iterar a través de todos los coches, y para cada uno, imprima una lista de las ruedas.El ingenuo O/R de aplicación sería el siguiente:

SELECT * FROM Cars;

Y, a continuación, para cada Car:

SELECT * FROM Wheel WHERE CarId = ?

En otras palabras, debe seleccionar para los Coches, y luego N adicional selecciona, donde N es el número total de vehículos.

Alternativamente, uno podría tener todas las ruedas y realizar las búsquedas en la memoria:

SELECT * FROM Wheel

Esto reduce el número de viajes de ida y vuelta a la base de datos de N+1 a 2.La mayoría de los ORM herramientas de darle varias formas para prevenir N+1 selecciona.

Referencia: Persistencia Java con Hibernate, el capítulo 13.

Otros consejos

SELECT 
table1.*
, table2.*
INNER JOIN table2 ON table2.SomeFkId = table1.SomeId

Que usted obtiene un conjunto de resultados donde el niño filas en la tabla2 provocar la duplicación mediante la devolución de la tabla1 resultados para cada niño de la fila en la tabla2.O/R de miembros de la comunidad deben diferenciar tabla1 instancias basado en un único campo de clave, a continuación, utilizar todos los tabla2 columnas para llenar las instancias secundarias.

SELECT table1.*

SELECT table2.* WHERE SomeFkId = #

El N+1, donde la primera consulta rellena el objeto principal y la segunda consulta rellena todos los objetos de cada una de las únicas primarias de los objetos devueltos.

Considere la posibilidad de:

class House
{
    int Id { get; set; }
    string Address { get; set; }
    Person[] Inhabitants { get; set; }
}

class Person
{
    string Name { get; set; }
    int HouseId { get; set; }
}

y las tablas con una estructura similar.Una sola consulta para la dirección "22 Valley St" puede devolver:

Id Address      Name HouseId
1  22 Valley St Dave 1
1  22 Valley St John 1
1  22 Valley St Mike 1

La O/RM debe rellenar una instancia de Casa con ID=1, Dirección="22 Valley St" y, a continuación, rellenar los Habitantes de la matriz con la Gente instancias para Dave, Juan, y Mike con sólo una consulta.

A N+1 consulta para la misma dirección utilizada anteriormente, el resultado es:

Id Address
1  22 Valley St

con una consulta independiente como

SELECT * FROM Person WHERE HouseId = 1

y lo que resulta en un conjunto de datos separado como

Name    HouseId
Dave    1
John    1
Mike    1

y el resultado final es el mismo que el anterior con la única consulta.

Las ventajas de selección única es que usted obtenga todos los datos de la parte delantera que puede ser lo que deseamos.Las ventajas para N+1 es la complejidad de la consulta se reduce y se puede utilizar la carga diferida en el que el niño conjuntos de resultados sólo se cargan en la primera solicitud.

Proveedor con una relación de uno a muchos relación con el Producto.Un Proveedor ha (suministros) muchos Productos.

***** Table: Supplier *****
+-----+-------------------+
| ID  |       NAME        |
+-----+-------------------+
|  1  |  Supplier Name 1  |
|  2  |  Supplier Name 2  |
|  3  |  Supplier Name 3  |
|  4  |  Supplier Name 4  |
+-----+-------------------+

***** Table: Product *****
+-----+-----------+--------------------+-------+------------+
| ID  |   NAME    |     DESCRIPTION    | PRICE | SUPPLIERID |
+-----+-----------+--------------------+-------+------------+
|1    | Product 1 | Name for Product 1 |  2.0  |     1      |
|2    | Product 2 | Name for Product 2 | 22.0  |     1      |
|3    | Product 3 | Name for Product 3 | 30.0  |     2      |
|4    | Product 4 | Name for Product 4 |  7.0  |     3      |
+-----+-----------+--------------------+-------+------------+

Factores:

  • Modo vago para el Proveedor establece en "true" (por defecto)

  • Recuperar el modo utilizado para consultar sobre el Producto es Seleccionar

  • Fetch modo (por defecto):El proveedor se accede a la información

  • El almacenamiento en caché no juega un papel importante por primera vez el

  • El proveedor de acceso es

Fetch modo de Seleccionar Fetch (predeterminado)

// It takes Select fetch mode as a default
Query query = session.createQuery( "from Product p");
List list = query.list();
// Supplier is being accessed
displayProductsListWithSupplierName(results);

select ... various field names ... from PRODUCT
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?

Resultado:

  • 1 seleccione declaración de Producto
  • N seleccione las declaraciones de los Proveedores

Este es N+1 seleccione problema!

No puedo comentar directamente en otras respuestas, porque no tengo la suficiente reputación.Pero vale la pena señalar que el problema esencialmente sólo surge debido a que, históricamente, una gran cantidad de dbms han sido bastante buenos a la hora de manejar une (MySQL ser particularmente notable ejemplo).Entonces n+1 ha, a menudo, ha sido notablemente más rápido que una combinación.Y, a continuación, hay maneras de mejorar en n+1, pero todavía sin necesidad de unirse, que es lo que el problema original se refiere a.

Sin embargo, MySQL es ahora mucho mejor de lo que solía ser cuando se trata de combinaciones.Cuando me enteré de MySQL, he utilizado une mucho.Entonces descubrí lo lentos que son, y pasó a n+1 en el código de lugar.Pero, recientemente, he estado moviendo de nuevo a las combinaciones, debido a que MySQL está ahora en una situación mucho mejor en el manejo de ellos, que fue cuando empecé a usarlo.

En estos días, una simple combinación en una indexe correctamente conjunto de tablas rara vez es un problema, en términos de rendimiento.Y si se da un impacto en el rendimiento, entonces el uso del índice de sugerencias a menudo se resuelve.

Esto se discute aquí por uno de MySQL, el equipo de desarrollo:

http://jorgenloland.blogspot.co.uk/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html

Así que el resumen es:Si usted ha estado evitando une en el pasado, debido a MySQL el deplorable desempeño con ellos, a continuación, inténtelo de nuevo en las últimas versiones.Usted probablemente va a ser una grata sorpresa.

Nos alejamos de el ORM de Django, porque de este problema.Básicamente, si tratas de hacer

for p in person:
    print p.car.colour

El ORM estará feliz de regresar a todas las personas (normalmente como instancias de un objeto de la Persona), pero, será necesario consultar el coche de la tabla para cada Persona.

Un sencillo y muy eficaz enfoque es algo que yo llamo "fanfolding"que evita la absurda idea de que los resultados de la consulta de una base de datos relacional debe mapa de nuevo a las tablas originales de que la consulta se compone.

Paso 1:Amplia seleccione

  select * from people_car_colour; # this is a view or sql function

Esto devolverá algo como

  p.id | p.name | p.telno | car.id | car.type | car.colour
  -----+--------+---------+--------+----------+-----------
  2    | jones  | 2145    | 77     | ford     | red
  2    | jones  | 2145    | 1012   | toyota   | blue
  16   | ashby  | 124     | 99     | bmw      | yellow

Paso 2:Objetivar

Chupar los resultados en un objeto genérico creador con un argumento a split, después de que el tercer elemento.Esto significa que "jones" objeto no se hizo más de una vez.

Paso 3:Render

for p in people:
    print p.car.colour # no more car queries

Ver esta página web para una aplicación de fanfolding para python.

Supongamos que usted tiene de la EMPRESA y el EMPLEADO.La COMPAÑÍA tiene muchos EMPLEADOS (es decir,El EMPLEADO tiene un campo COMPANY_ID).

En algunos S/R configuraciones, cuando usted tiene un mapeado de la Empresa objeto y vaya a acceder a sus Empleados objetos, el O/R de la herramienta va a hacer uno de seleccionar para cada empleado, mientras que si usted se acaba de hacer las cosas en la recta SQL, usted podría select * from employees where company_id = XX.Por lo tanto N (número de empleados) más 1 (de la empresa)

Esta es la forma en las versiones iniciales de los Beans de Entidad EJB trabajado.Creo que las cosas como Hibernate han hecho lejos con esto, pero no estoy muy seguro.La mayoría de las herramientas suelen incluir información como su estrategia para la asignación.

He aquí una buena descripción del problema - https://web.archive.org/web/20160310145416/http://www.realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=why-lazy

Ahora que usted entiende el problema generalmente puede evitarse haciendo un join fetch en su consulta.Básicamente, esto obliga a la recuperación de las perezoso objeto cargado de manera de recuperar los datos de una consulta en lugar de n+1 consultas.Espero que esto ayude.

En mi opinión el artículo escrito en Hibernate Escollo:Por Qué Las Relaciones Deben Ser Perezoso es exactamente lo contrario de la real N+1 problema.

Si usted necesita corregir la explicación, por favor consulte Hibernate - Capítulo 19:La Mejora De Rendimiento - Estrategias De Recuperación

Seleccione buscar (el valor predeterminado) es extremadamente vulnerables a N+1 selecciona problemas, por lo que puede que desee habilitar únete a ir a buscar

Compruebe Ayende post sobre el tema: La lucha contra el Select N + 1 Problema En NHibernate

Básicamente, cuando el uso de un ORM como NHibernate o EntityFramework, si usted tiene un uno-a-muchos (maestro-detalle) de la relación, y quiero una lista de todos los detalles de cada registro maestro, usted tiene que N + 1 consulta de llamadas a la base de datos, "N" es el número de registros maestros:1 consulta para obtener todos los registros principales, y N consultas, uno por registro maestro, para obtener todos los detalles de cada registro maestro.

Más de la base de datos de consulta de llamadas --> más tiempo de latencia --> disminución de la aplicación/base de datos de rendimiento.

Sin embargo, ORM, tiene opciones para evitar este problema, principalmente mediante el uso de "une".

El N+1 consulta el problema ocurre cuando se olvida a buscar una asociación y entonces usted necesita para acceder a ella:

List<PostComment> comments = entityManager.createQuery(
    "select pc " +
    "from PostComment pc " +
    "where pc.review = :review", PostComment.class)
.setParameter("review", review)
.getResultList();

LOGGER.info("Loaded {} comments", comments.size());

for(PostComment comment : comments) {
    LOGGER.info("The post title is '{}'", comment.getPost().getTitle());
}

Que genera las siguientes sentencias SQL:

SELECT pc.id AS id1_1_, pc.post_id AS post_id3_1_, pc.review AS review2_1_
FROM   post_comment pc
WHERE  pc.review = 'Excellent!'

INFO - Loaded 3 comments

SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 1

INFO - The post title is 'Post nr. 1'

SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 2

INFO - The post title is 'Post nr. 2'

SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 3

INFO - The post title is 'Post nr. 3'

En primer lugar, Hibernate ejecuta la consulta JPQL, y una lista de PostComment las entidades se recupera.

A continuación, para cada PostComment, el asociado post propiedad se utiliza para generar un mensaje de registro que contiene el Post título.

Debido a que el post la asociación no se ha inicializado, Hibernate debe recuperar la Post entidad con una segunda consulta, y para N PostComment entidades, N más consultas se van a ejecutar (por lo tanto, el N+1 consulta problema).

En primer lugar, usted necesita adecuado de registro de SQL y monitoreo así que usted puede ver este problema.

En segundo lugar, este tipo de asuntos es mejor ser capturados por las pruebas de integración.Usted puede utilizar un automático JUnit valer para validar la esperada recuento de instrucciones SQL generadas.El db-unidad de proyecto ya proporciona esta funcionalidad, y es de código abierto.

Cuando usted identificó a N+1 consulta problema, usted necesita usar un JOIN FETCH para que el niño las asociaciones que se obtienen en una consulta, en lugar de N.Si usted necesita para capturar varias niño asociaciones, es mejor obtener una colección en la consulta inicial y la segunda con un secundario de consultas SQL.

El enlace proporcionado tiene una muy simple ejemplo de las n + 1 problema.Si usted aplica para Hibernar es básicamente hablando de lo mismo.Cuando usted consulta a un objeto, la entidad está cargado, pero cualquier asociaciones (a menos que se configure de otra forma) será cargado ligeramente.Por tanto, una consulta para la raíz de objetos y otra consulta para la carga de las asociaciones para que cada uno de estos.100 objetos devueltos significa una consulta inicial y, a continuación, 100 consultas adicionales para obtener la asociación de cada uno, n + 1.

http://pramatr.com/2009/02/05/sql-n-1-selects-explained/

Un millonario tiene N los coches.Desea obtener todos (4) ruedas.

Una (1) consulta de las cargas de todos los coches, pero para cada uno de los (N) alquiler de una consulta independiente es presentado para la carga de las ruedas.

Costos:

Asumir los índices de ajuste en la memoria ram.

1 + N de la consulta y análisis de cepillado + índice de búsqueda Y 1 + N + (N * 4) de la placa de acceso para la carga de la carga útil.

Asumir los índices no caben en la memoria ram.

Los gastos adicionales en el peor de los casos 1 + N de la placa de accesos para la carga de índice.

Resumen

Cuello de botella se encuentra en la placa de acceso (ca.70 veces por segundo acceso aleatorio en el disco duro) Con ganas de unirse seleccione también el acceso de la placa 1 + N + (N * 4) veces la carga útil.Así que si los índices de ajuste en la memoria ram - no hay problema, su rápido porque sólo de ram operaciones de que se trate.

Es mucho más rápido para el problema 1 de la consulta que devuelve el 100 resultados de 100 consultas que cada retorno 1 resultado.

N+1 seleccione problema es un dolor, y tiene sentido para detectar este tipo de casos en la unidad de pruebas.He desarrollado una pequeña biblioteca para verificar el número de consultas ejecutadas por un determinado método de prueba o simplemente un bloque de código arbitrario - JDBC Sniffer

Sólo añadir un especial JUnit regla para la clase de prueba y lugar de anotación con el número esperado de las consultas en los métodos de prueba:

@Rule
public final QueryCounter queryCounter = new QueryCounter();

@Expectation(atMost = 3)
@Test
public void testInvokingDatabase() {
    // your JDBC or JPA code
}

El problema, como otros han dicho de forma más elegante es que un producto Cartesiano de los OneToMany columnas o estás haciendo N+1 Selecciona.Es posible gigantesco conjunto de resultados o hablador con la base de datos, respectivamente.

Me sorprende que esto no es mencionado, pero esto cómo me he metido en torno a este tema... Hago un semi-temporal de los identificadores de tabla. También hago esto cuando usted tiene el IN () cláusula de limitación.

Esto no funciona para todos los casos (probablemente ni siquiera una mayoría) pero funciona especialmente bien si usted tiene un montón de objetos secundarios tales que el producto Cartesiano va a ir de las manos (es decir, un montón de OneToMany columnas el número de resultados será una multiplicación de las columnas) y sus más de un lote como de trabajo.

Primero inserte el padre de identificadores de objeto como proceso por lotes en un identificadores de tabla.Este batch_id es algo que generamos en nuestra app y aferrarse.

INSERT INTO temp_ids 
    (product_id, batch_id)
    (SELECT p.product_id, ? 
    FROM product p ORDER BY p.product_id
    LIMIT ? OFFSET ?);

Ahora para cada OneToMany la columna que usted acaba de hacer un SELECT en el id de la tabla INNER JOINción de la tabla con un niño WHERE batch_id= (o viceversa).Usted sólo quiere asegurarse de que usted ordenar por la columna id, ya que la fusión de las columnas de resultado más fácil (de lo contrario tendrá un HashMap/Mesa para todo el conjunto de resultados que pueden no ser tan malo).

A continuación, sólo limpie periódicamente el id de la tabla.

Esto también funciona especialmente bien si el usuario selecciona decir más de 100 artículos distintos para algún tipo de procesamiento a granel.Poner el 100 distintos identificadores en la tabla temporal.

Ahora el número de consultas que se están haciendo es por el número de OneToMany columnas.

Tomar Mate Solnit ejemplo, imagine que usted definir una asociación entre el Coche y las Ruedas como PEREZOSO y necesitas un poco de las Ruedas de los campos.Esto significa que después de la primera selección, hibernate va a hacer "Select * from Ruedas donde car_id = :id" PARA CADA Coche.

Esto hace que el primer select y más 1 seleccione por cada N de coche, por eso se llama n+1 problema.

Para evitar esto, hay que hacer la asociación explorar como con ganas, así que hibernan cargas de datos con una combinación.

Pero atención, si muchas veces de no tener acceso asociados Ruedas, es mejor mantenerlo PEREZOSO o cambiar fetch tipo con Criterio.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top