2010-11-17 21 views
7

Tienen las siguientes tablas (Oracle 10g):Cómo otimize de selección de varias tablas con millones de filas

catalog (
    id NUMBER PRIMARY KEY, 
    name VARCHAR2(255), 
    owner NUMBER, 
    root NUMBER REFERENCES catalog(id) 
    ... 
) 
university (
    id NUMBER PRIMARY KEY, 
    ... 
) 
securitygroup (
    id NUMBER PRIMARY KEY 
    ... 
) 
catalog_securitygroup (
    catalog REFERENCES catalog(id), 
    securitygroup REFERENCES securitygroup(id) 
) 
catalog_university (
    catalog REFERENCES catalog(id), 
    university REFERENCES university(id) 
) 

catálogo: 500 000 filas, catalog_university: 500 000, catalog_securitygroup: 1 500 000.

Necesito seleccionar las 50 filas del catálogo con la raíz especificada ordenada por nombre para la universidad actual y el grupo de seguridad actual. Hay una consulta:

SELECT ccc.* FROM (
    SELECT cc.*, ROWNUM AS n FROM (
     SELECT c.id, c.name, c.owner 
     FROM catalog c, catalog_securitygroup cs, catalog_university cu 
     WHERE c.root = 100 
      AND cs.catalog = c.id 
      AND cs.securitygroup = 200 
      AND cu.catalog = c.id 
      AND cu.university = 300 
     ORDER BY name 
    ) cc 
) ccc WHERE ccc.n > 0 AND ccc.n <= 50; 

Dónde 100 - algunos de catálogo, 200 - algunos SecurityGroup, 300 - alguna universidad. Esta consulta devuelve 50 filas de ~ 170 000 en 3 minutos.

Pero la próxima consulta de Retorno Este filas de 2 segundos:

SELECT ccc.* FROM (
    SELECT cc.*, ROWNUM AS n FROM (
     SELECT c.id, c.name, c.owner 
     FROM catalog c 
     WHERE c.root = 100 
     ORDER BY name 
    ) cc 
) ccc WHERE ccc.n > 0 AND ccc.n <= 50; 

construyo siguientes índices: (catalog.id, catalog.name, catalog.owner), (catalog_securitygroup.catalog, catalog_securitygroup.index), (catalog_university.catalog, catalog_university.university).

Plan para la primera consulta (utilizando PLSQL Developer): Plan de

http://habreffect.ru/66c/f25faa5f8/plan2.jpg

para la segunda consulta:

http://habreffect.ru/f91/86e780cc7/plan1.jpg

¿Cuáles son las formas de optimizar la consulta que tengo?

+0

+1 por completness, ¿puede agregar EXPLAIN PLANS para ambas consultas? – Unreason

+0

Como escribí anteriormente, necesito seleccionar 50 (0-50 o 50-100 o 100-150 etc.) filas del catálogo con la raíz especificada ordenada por nombre para la universidad actual y el grupo de seguridad actual. Cada catálogo puede ser accesible para ciertas universidades y grupos de seguridad. Y cada catálogo muestra 50 resultados por página. –

+1

LMFAO, @ Antón, ¿se supone que ese último comentario es un PLAN DE EXPLICACIÓN? Creo que sí ... no es una explicación de un plan, es el "mapa de ruta" de los optimizadores sobre cómo ejecutar la consulta. ¿Qué herramienta está usando y podemos decirle cómo obtener el plan? –

Respuesta

2

Primero asume que las tablas de la universidad y son SecurityGroup más bien pequeño. Ha publicado el tamaño de las tablas grandes, pero en realidad son los demás tamaños los que forman parte del problema

Su problema proviene del hecho de que no puede unir las tablas más pequeñas primero. Su orden de unión debe ser de pequeño a grande. Pero debido a que las tablas de asignación no incluyen una tabla de grupo de seguridad a universidad, primero no puede unirse a las más pequeñas.Entonces terminas con uno u otro, en una gran mesa, en otra gran mesa y luego con ese gran resultado intermedio tienes que ir a una mesa pequeña.

Si siempre tiene current_univ y current_secgrp y rootee como entradas, debe usarlas para filtrar lo antes posible. La única forma de hacerlo es cambiar el esquema. De hecho, puede dejar las tablas existentes en su lugar si es necesario, pero estará agregando espacio con esta sugerencia.

Has normalizado los datos muy bien. Eso es genial para la velocidad de actualización ... no es tan bueno para consultar. Nos desnormalizamos para acelerar la consulta (esa es toda la razón para los datawarehouses (vale eso y el historial)). Construya una sola tabla de mapeo con las siguientes columnas.

Univ_id, SecGrp_ID, Root, catalog_id. Haz que sea una tabla organizada por índice de las primeras 3 columnas como pk.

Ahora cuando consulta ese índice con los tres valores de PK, terminará ese análisis de índice con una lista completa del Id. De catálogo permitido, ahora solo tiene que unirse a la tabla de gatos para obtener los detalles del elemento de gato y usted Está fuera de una carrera.

+0

¡Ayudó!) Ahora la consulta se ejecuta en ~ 3.5 segundos. Haré un par de pruebas –

+0

Pero también agrego catalog_id en PK. –

+1

@Stephanie, no es realmente correcto decirlo de la tabla más pequeña a la tabla más grande; es mejor decir desde las condiciones de mayor selectividad hacia las más bajas. Y eso es lo que hará el cepillo, analizar el costo de los posibles caminos. – Unreason

-1

tratan de declarar un cursor. No sé oráculo, pero en SqlServer se vería así:

declare @result 
table ( 
    id numeric, 
    name varchar(255) 
); 

declare __dyn_select_cursor cursor LOCAL SCROLL DYNAMIC for 

--Select 
select distinct 
    c.id, c.name 
From [catalog] c 
    inner join university u 
    on  u.catalog = c.id 
     and u.university = 300 
    inner join catalog_securitygroup s 
    on  s.catalog = c.id 
     and s.securitygroup = 200 
Where 
    c.root = 100 
Order by name 

--Cursor 
declare @id numeric; 
declare @name varchar(255); 

open __dyn_select_cursor; 

fetch relative 1 from __dyn_select_cursor into @id,@name declare @maxrowscount int 

set @maxrowscount = 50 

while (@@fetch_status = 0 and @maxrowscount <> 0) 
begin 
    insert into @result values (@id, @name); 
    set @maxrowscount = @maxrowscount - 1; 
    fetch next from __dyn_select_cursor into @id, @name; 
end 
close __dyn_select_cursor; 
deallocate __dyn_select_cursor; 


--Select temp, final result 
select 
id, 
name 
from @result; 
+0

Creo que está publicando una respuesta de SQL Server. Nunca visto @@ en Oracle. –

+0

La idea es válida, sin embargo. Oracle admite cursores. – TMN

+0

Nah, la idea no es válida hasta que se haya intentado correctamente el enfoque basado en conjuntos, y al mirar los PLAN publicados, no fue así. – Unreason

0

El optimizador basado en costos de Oracle hace uso de toda la información que tiene que decidir cuáles son las mejores vías de acceso son para los datos y lo que el menos métodos costosos son para obtener esa información. A continuación hay algunos puntos aleatorios relacionados con su pregunta.

Los primeros tres tablas que se ha enumerado todos tienen claves primarias. ¿Las otras tablas (catalog_university y catalog_securitygroup) también tienen claves principales en ellas? Una clave principal define una columna o conjunto de columnas que no son nulas y únicas, y son muy importantes en una base de datos relacional.

Oracle generalmente hace cumplir una clave principal mediante la generación de un índice único en las columnas dadas. Es más probable que Oracle Optimizer haga uso de un índice único si está disponible, ya que es más probable que sea más selectivo.

Si es posible, un índice que contenga valores únicos debe definirse como único (CREATE UNIQUE INDEX...) y esto proporcionará más información al optimizador.

Los índices adicionales que usted ha proporcionado no son más selectivos que los índices existentes. Por ejemplo, el índice en (catalog.id, catalog.name, catalog.owner) es único pero es menos útil que el índice de clave primaria existente en (catalog.id). Si una consulta se escribe en seleccionar en la columna de la catalog.name, es posible hacer y el índice de exploración con omisión pero esto empieza a ser costosa (y la mayoría incluso no ser posible en este caso).

Puesto que usted está tratando de seleccionar con base en la columna de la catalog.root, podría valer la pena añadir un índice en esa columna.Esto significa que podría encontrar rápidamente las filas relevantes de la tabla de catálogo. El tiempo para la segunda consulta podría ser un poco engañoso. Puede llevar 2 segundos encontrar 50 filas coincidentes del catálogo, pero fácilmente podrían ser las primeras 50 filas de la tabla del catálogo ... encontrar 50 que coincidan con todas sus condiciones puede llevar más tiempo, y no solo porque necesita únete a otras tablas para obtenerlas. Siempre usaría create table as select sin restringirme a rownum cuando intente sintonizar el rendimiento. Con una consulta compleja, generalmente me importa cuánto tardan en recuperar todas las filas ... y una simple selección con rownum puede ser engañosa

Todo sobre el ajuste de rendimiento de Oracle se trata de proporcionar al optimizador información suficiente y el derecho herramientas (índices, restricciones, etc.) para hacer su trabajo correctamente. Por esta razón, es importante obtener estadísticas del optimizador usando algo como DBMS_STATS.GATHER_TABLE_STATS(). Los índices deben tener estadísticas recopiladas automáticamente en Oracle 10g o posterior.

De alguna manera esto se convirtió en un largo respuesta sobre el optimizador de Oracle. Espero que algo de eso responda tu pregunta. He aquí un resumen de lo que se ha dicho anteriormente:

  • Dale el optimizador mayor cantidad de información posible, por ejemplo, si el índice es único, entonces declarar como tal.
  • Agregue índices en sus rutas de acceso
  • Busque los tiempos correctos para consultas sin limitar rowwnum. Siempre será más rápido para encontrar los primeros 50 M & Ms en un frasco que encontrar el primer 50 M roja & Sra
  • Reunir optimizador estadísticas
  • Añadir claves únicas/primarias en todas las mesas donde existan.
+0

¡Eso terminó siendo mucho más largo de lo que pretendía! Y probablemente menos útil de lo que esperaba. ¡Oh bien! –

3

Los índices que pueden ser útiles y deben considerarse trato con

WHERE c.root = 100 
     AND cs.catalog = c.id 
     AND cs.securitygroup = 200 
     AND cu.catalog = c.id 
     AND cu.university = 300 

Así los siguientes campos pueden ser interesantes para los índices

c: id, root 
cs: catalog, securitygroup 
cu: catalog, university 

lo tanto, intentar crear

(catalog_securitygroup.catalog, catalog_securitygroup.securitygroup) 

y

(catalog_university.catalog, catalog_university.university) 

EDIT: me perdí el ORDER BY - también deben considerarse estos campos, por lo

(catalog.name, catalog.id) 

podría ser beneficioso (o algún otro índice compuesto que podría ser utilizado para clasificar y las condiciones - posiblemente (catalog.root, catalog.name, catalog.id))

EDIT2 Aunque otro qu es aceptada, proporcionaré algo más de reflexión. He creado algunos datos de prueba y ejecuto algunos puntos de referencia.

Los casos de prueba son mínimos en términos de ancho de registro (en catalog_securitygroup y catalog_university las claves principales son (catalog, securitygroup) y (catalog, university)).Aquí es el número de registros por tabla:

test=# SELECT (SELECT COUNT(*) FROM catalog), (SELECT COUNT(*) FROM catalog_securitygroup), (SELECT COUNT(*) FROM catalog_university); 
?column? | ?column? | ?column? 
----------+----------+---------- 
    500000 | 1497501 | 500000 
(1 row) 

base de datos se Postgres 8.4, por defecto de instalación de Ubuntu, i5 hardware, 4GRAM

Primera Reescribí la consulta a

SELECT c.id, c.name, c.owner 
FROM catalog c, catalog_securitygroup cs, catalog_university cu 
WHERE c.root < 50 
    AND cs.catalog = c.id 
    AND cu.catalog = c.id 
    AND cs.securitygroup < 200 
    AND cu.university < 200 
ORDER BY c.name 
LIMIT 50 OFFSET 100 

nota: las condiciones se convierten en menos que para mantener un número comparable de filas intermedias (la consulta anterior devolvería 198,801 filas sin la cláusula LIMIT)

Si se ejecuta como se indica anteriormente, sin ningún índice adicional (excepto para PK y claves externas) se ejecuta en 556 ms en una base de datos fría (esto es en realidad una indicación de que simplifiqué demasiado los datos de muestra de alguna manera; sería más feliz si tuviera 2-4 aquí sin recurrir a menos que los operadores)

Esto me lleva a mi punto - cualquier consulta directa que solo se une y filtra (cierto número de tablas) y devuelve solo un cierto número de los registros debe ejecutarse en 1s en cualquier base de datos decente sin necesidad de utilizar cursores o para desnormalizar datos (uno de estos días tendré que escribir una publicación sobre eso).

Además, si una consulta devuelve solo 50 filas y realiza uniones simples de igualdad y condiciones de igualdad restrictivas, debería ejecutarse incluso con mayor rapidez.

Ahora vamos a ver si añado algunos índices, el mayor potencial en las consultas de este tipo es por lo general el orden de clasificación, así que vamos a tratar de que:

CREATE INDEX test1 ON catalog (name, id); 

Esto hace que el tiempo de ejecución de la consulta - 22ms en una fría base de datos.

Y que es el punto - si usted está tratando de obtener sólo una página de datos, sólo se debe obtener una página de datos y tiempos de ejecución de consultas de este tipo en datos normalizados con adecuados índices debe toma menos de 100ms en hardware decente.

Espero no haber simplificado demasiado el caso hasta el punto de no haber comparación (como dije antes, está presente cierta simplificación ya que no sé la cardinalidad de las relaciones entre el catálogo y las tablas de muchos a muchos).

Por lo tanto, la conclusión es

  • si yo fuera usted, no dejarían de ajustar los índices (y el SQL) hasta que consiga el rendimiento de la consulta a ir por debajo de 200 ms como regla del pulgar.
  • sólo si me gustaría encontrar una explicación objetiva por qué no puede ir por debajo de dicho valor que recurriría a Desnormalización y/o cursores, etc ...
+0

La base de datos con la que trabajo muy grande: 585 tablas. Tengo un catálogo que tiene 180 000 niños.Las siguientes tablas que utilicé para la prueba: catálogo con pk (id) construir usando índice (id, nombre, propietario) - 500 000 filas, catalog_university con pk (catálogo, universidad) - 500 000 filas (para cada catálogo - 1 universidad), catalog_securitygroup con pk (catalog, securitygroup) - 1 500 000 filas (para cada catálogo - 3 grupos de seguridad). Primera consulta (para seleccionar 180 000 hijos) de mi pregunta ejecutada en 3.6 segundos con esta configuración. Con la desnormalización se ejecuta en 1.3 segundos. –

+0

Quiero decir que el problema ahora es ordenar 180 000 filas por nombre. La consulta de clasificación de Wihout se ejecuta en 62 ms –

+0

@Anton, el número de tablas no importa y creé el mismo número de filas que tiene (aunque podría tener muchas más columnas, lo que ralentizaría el tiempo de ejecución por algún factor, si no todas, las columnas cubiertos por índices, también la cardinalidad de las relaciones puede ser diferente en mi muestra, pero traté de compensar eso, ver la respuesta, pero me repito a mí mismo, veo la respuesta de nuevo, la conclusión es que usted debería poder ir por debajo de 1s sin desnormalización: su cifra de 62 ms es el tipo de rendimiento que debería esperar, siempre que planificador use el índice para ordenar ... – Unreason

0

El uso de rownum es incorrecto y hace que se procesen todas las filas. Procesará todas las filas, les asignará un número de fila y luego las encontrará entre 0 y 50. Cuando desee buscar en el plan de explicación, vaya al COUNT STOPKEY en lugar de solo contar

La siguiente consulta debe ser una mejora ya que sólo va a las primeras 50 filas ... pero aún queda la cuestión de la une a mirar demasiado:

SELECT ccc.* FROM (
    SELECT cc.*, ROWNUM AS n FROM (
     SELECT c.id, c.name, c.owner 
     FROM catalog c 
     WHERE c.root = 100 
     ORDER BY name 
    ) cc 
    where rownum <= 50 
) ccc WHERE ccc.n > 0 AND ccc.n <= 50; 

Además, suponiendo esto para una página web o algo similar, tal vez hay una mejor Manera de manejar esto que simplemente ejecutar la consulta nuevamente para obtener los datos para la página siguiente.

+0

Edité mi pregunta, necesito seleccionar las 50 filas ordenadas por nombre, no solo primero. –

+0

Si cambia los 50 en ambos lugares, esta consulta aún hará menos trabajo que el original, probablemente incluso al procesar el penúltimo página. –

Cuestiones relacionadas