MySQL: optimice UNION con "ORDER BY" en consultas internas

9

Acabo de configurar un sistema de registro que consta de varias tablas con el mismo diseño.

Hay una tabla para cada fuente de datos.

Para el visor de registros, quiero

  • UNION todas las tablas de registro ,
  • filtrarlos por cuenta ,
  • agregue una pseudo columna para la identificación de la fuente,
  • ordenarlos por tiempo ,
  • y limitarlos a la paginación .

Todas las tablas contienen un campo llamado zeitpunktque es una columna de fecha / hora indexada.

Mi primer intento fue:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730)

ORDER BY zeit DESC LIMIT 10;

El optimizador no puede usar los índices aquí porque todas las filas de ambas tablas son devueltas por las subconsultas y ordenadas después de UNION.

Mi solución fue la siguiente:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

ORDER BY zeit DESC LIMIT 10;

Esperaba que el motor de consultas usara los índices aquí ya que ambas subconsultas deberían estar ordenadas y limitadas antes de la UNION, que luego combina y ordena las filas.

Realmente pensé que sería así, pero ejecutar EXPLAINla consulta me dice que las subconsultas aún buscan en ambas tablas.

EXPLAINinglas subconsultas mismas me muestran la optimización deseada pero UNIONingjuntas no lo hacen.

¿Me he perdido algo?

Sé que las ORDER BYcláusulas dentro de las UNIONsubconsultas se ignoran sin a LIMIT, pero hay un límite.

Editar: en
realidad, probablemente también habrá consultas sin laaccount_idcondición.

Las tablas ya existen y están llenas de datos. Puede haber cambios en el diseño dependiendo de la fuente, por lo que quiero mantenerlos divididos. Además, los clientes de registro utilizan diferentes credenciales por una razón.

Tengo que mantener una especie de capa entre los lectores de registro y las tablas reales.

Aquí están los planes de ejecución para toda la consulta y la primera subconsulta, así como el diseño de la tabla en detalle:

https://gist.github.com/ca8fc1093cd95b1c6fc0

Lukas
fuente
1
El mejor índice para esto sería el compuesto (account_id, zeitpunkt). ¿Tienes ese índice? El segundo mejor sería (creo) el sencillo (zeitpunkt), pero la eficiencia si se usa depende de la frecuencia con la que account_id=730aparecen las filas .
ypercubeᵀᴹ
2
Y por qué UNION DISTINCT? No es necesario forzar una ordenación distinta, ya que los resultados serán diferentes entre las subconsultas, debido a la columna de identificación adicional. Uso UNION ALL.
ypercubeᵀᴹ
1
Además de la sugerencia de @ypercube, tengo una pregunta: ¿no sería mejor tener todos esos registros en la misma tabla, con la adición de la sourcecolumna? De esta manera, podría evitar UNIONsy usar índices en todos sus datos.
dezso
1
@ypercube En realidad, probablemente también habrá consultas sin la condición account_id . El distintivo DISTINCT es una relicción de intentos anteriores y en realidad es inútil porque los resultados siempre diferirán y porque DISTINCT es el comportamiento de dafualt. Las tablas ya existen y están llenas de datos. De todos modos, puede haber cambios en el diseño según la fuente, por lo que quiero mantenerlos divididos. Además, los clientes de registro utilizan diferentes credenciales por una razón. Tengo que mantener una especie de capa entre los lectores de registro y las tablas reales.
Lukas
OK, pero verifique si cambiar a UNION ALLproduce un plan de ejecución diferente.
ypercubeᵀᴹ

Respuestas:

8

Solo por curiosidad, ¿puedes probar esta versión? Puede engañar al optimizador para que use los mismos índices que las subconsultas usarían por separado:

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10) 
    AS a

UNION ALL

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)
    AS b

ORDER BY zeit DESC LIMIT 10;

Sigo pensando que el mejor índice que podrías tener es el compuesto (account_id, zeitpunkt). Produciría las 10 filas rápidamente, y no se necesitarían trucos.

ypercubeᵀᴹ
fuente
Su modificación resultó para obtener los resultados deseados. ¡Gracias! Solo como una nota al margen: por ahora no estoy seguro de qué índice será mejor. Incluso podría usar ambos. Tendré que comprobar cómo log entries / userescala el número de usuarios y la cantidad de usuarios .
Lukas
Si va a necesitar consultas con y sin consultas account_id=?, conserve ambas.
ypercubeᵀᴹ
@ypercube, +1 ¡esto es muy inteligente y funcionó en mi situación (similar) también! ¿Puede explicar por qué envolver las consultas unidas en un ficticio SELECT * FROMengaña a MySQL para que use los índices?
dkamins
@dkamins: El optimizador MySQL no es muy inteligente, generalmente cuando hay una tabla derivada como esta (SELECT ...) AS a, intenta evaluar y optimizar la tabla derivada por separado de las otras tablas derivadas y luego toda la consulta.
ypercubeᵀᴹ
@Lukas, en realidad, ya que necesita asegurarse de que se utiliza el índice, el uso / la adición force indexle dará una mejor solución.
Pacerier