2009-02-25 19 views
42

Estoy tratando de optimizar algunas de las consultas de la base de datos en mi aplicación Rails y tengo varias que me han dejado perplejo. Todos están usando un IN en la cláusula WHERE y todos realizan escaneos de tabla completa, a pesar de que parece existir un índice apropiado.MySQL que no usa índices con la cláusula WHERE IN?

Por ejemplo:

SELECT `user_metrics`.* FROM `user_metrics` WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

realiza un escaneo completo de tabla y explicar dice:

select_type: simple 
type: all 
extra: using where 
possible_keys: index_user_metrics_on_user_id (which is an index on the user_id column) 
key: (none) 
key_length: (none) 
ref: (none) 
rows: 208 

no se utilizan índices cuando se utiliza una declaración en o tengo que hacer algo diferente? Las consultas aquí las está generando Rails para poder volver a ver cómo se definen mis relaciones, pero primero pensé que comenzaría con posibles soluciones en el nivel de base de datos.

+0

¿Qué significan tus N? ¿Son constantes literales, columnas o variables? Eso es importante. – Quassnoi

+0

Lo siento, esos fueron pegados desde la salida de mi complemento query_reviewer. Las consultas reales tienen enteros allí, es decir. IN (25, 26, 27) – jasonlong

+0

@blackant: ¿has analizado en tus mesas? – vladr

Respuesta

0

¿Se pone mejor si elimina los soportes redundantes alrededor de la cláusula where?

Aunque podría ser solo porque solo tienes unas 200 filas, decidió que una exploración de tabla sería más rápida. Pruebe con una tabla con más registros en ella.

+0

Pareners adicionales no parecen importar. Además, el pequeño conjunto de datos no parece importar: agregué 5000 registros adicionales y aún los escaneo todos. – jasonlong

7

intentar forzar este índice:

SELECT `user_metrics`.* 
FROM `user_metrics` FORCE INDEX (index_user_metrics_on_user_id) 
WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

acabo de comprobar, sí utiliza un índice en exactamente misma consulta:

EXPLAIN EXTENDED 
SELECT * FROM tests WHERE (test IN ('test 1', 'test 2', 'test 3', 'test 4', 'test 5', 'test 6', 'test 7', 'test 8', 'test 9')) 

1, 'SIMPLE', 'tests', 'range', 'ix_test', 'ix_test', '602', '', 9, 100.00, 'Using where' 
+2

Particularmente doloroso de implementar en Rails – vladr

+0

Sí, eso parece obligarlo a usar el índice. Como dice Vlad, una pena hacer en Rails. – jasonlong

+0

@blackant, ¿has ejecutado analizar en las tablas? sigue teniendo el mismo plan de explicacion? – vladr

37

Ver How MySQL Uses Indexes.

También valide si MySQL todavía realiza un full table scan después de agregar filas adicionales de 2000 o así a su tabla user_metrics. En las tablas pequeñas, el acceso por índice es en realidad más caro (I/O-wise) que un escaneo de tabla, y el optimizador de MySQL podría tener esto en cuenta.

Contrariamente a mi anterior post, resulta que MySQL es también using a cost-based optimizer, que es una muy buena noticia - es decir, siempre y cuando ejecuta su ANALYZE al menos una vez cuando se cree que el volumen de datos en su base de datos es representante del uso diario futuro.

Cuando se trata de optimizadores basados ​​en costos (Oracle, Postgres, etc.), debe asegurarse de ejecutar periódicamente ANALYZE en sus diversas tablas, ya que su tamaño aumenta en más del 10-15%. (Postgres lo hará automáticamente por usted, de forma predeterminada, mientras que otros RDBMS dejarán esta responsabilidad a un DBA, es decir, usted). A través del análisis estadístico, ANALYZE ayudará al optimizador a tener una mejor idea de la cantidad de E/S (y otras recursos, tales como CPU, necesarios, por ejemplo, para la clasificación) estarán involucrados al elegir entre varios planes de ejecución candidatos. Si no se ejecuta ANALYZE puede dar lugar, las decisiones de planificación a veces desastrosas muy pobres (por ejemplo milisegundos-consultas tomar, a veces, horas debido a malas bucles anidados en JOIN s.)

Si el rendimiento sigue siendo insatisfactoria después de ejecutar ANALYZE, a continuación, por lo general, podrá evitar el problema mediante el uso de sugerencias, por ejemplo, FORCE INDEX, mientras que en otros casos es posible que haya tropezado con un error de MySQL (por ejemplo, este older one, que podría haber mordido usted fue para utilizar Rails 'nested_set).

Ahora, ya que estás en una aplicación Rails, va a ser complicado (y derrotar el propósito de ActiveRecord) para emitir sus consultas personalizadas con toques en vez de continuar el uso de los ActiveRecord los -generated.

había mencionado que en la solicitud de nuestros rieles todosSELECT consultas cayeron por debajo de 100 ms después de cambiar a Postgres, mientras que algunos de los complejos combinaciones que genera por ActiveRecord que de vez en cuando tomar tanto como 15 años o más con MySQL 5.1 a causa de bucles anidados con escaneos internos de la tabla, incluso cuando los índices estaban disponibles. Ningún optimizador es perfecto, y debe conocer las opciones. Otros posibles problemas de rendimiento a tener en cuenta, además de la optimización del plan de consulta, son el bloqueo. Sin embargo, esto está fuera del alcance de su problema.

+0

Gracias Vlad . Espero resolver esto sin demasiado dolor con nuestra configuración actual, pero agradezco saber de tu éxito con Postgres. – jasonlong

+0

I segundo Postgres. Es una excelente base de datos. –

+0

Hola, blackant, ¿has hecho algún progreso con este problema de MySQL? – vladr

6

A veces, MySQL no utiliza un índice, incluso si uno está disponible. Una circunstancia bajo la cual esto ocurre es cuando el optimizador estima que usar el índice requeriría que MySQL tenga acceso a un porcentaje muy grande de las filas en la tabla. (En este caso, es probable que un escaneo de tabla sea mucho más rápido porque requiere menos búsquedas).

¿Qué porcentaje de filas coincide con su cláusula IN?

+0

Mis pruebas iniciales estaban en una tabla con solo ~ 200 filas, por lo que el porcentaje es relativamente alto. Sin embargo, he agregado 5000 filas más para probar y todavía realiza escaneos completos de tablas. El porcentaje ahora sería bastante minúsculo. – jasonlong

+0

En mi experiencia, el límite es, por lo general, entre 10% y 30%. –

3

Sé que llego tarde a la fiesta. Pero espero poder ayudar a alguien más con un problema similar.

Últimamente, estoy teniendo el mismo problema. Luego decido usar self-join-thing para resolver mi problema. El problema no es MySQL. El problema somos nosotros El tipo de devolución de la subconsulta es una diferencia de nuestra tabla. Por lo tanto, debemos convertir el tipo de subconsulta en el tipo de columna de selección. A continuación se código de ejemplo:

select `user_metrics`.* 
from `user_metrics` um 
join (select `user_metrics`.`user_id` in (N, N, N, N)) as temp 
on um.`user_id` = temp.`user_id` 

O mi propio código:

antigua: (No se utiliza en el índice: ~ 4s)

SELECT 
    `jxm_character`.* 
FROM 
    jxm_character 
WHERE 
    information_date IN (SELECT DISTINCT 
      (information_date) 
     FROM 
      jxm_character 
     WHERE 
      information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) 
     AND `jxm_character`.`ranking_type` = 1 
     AND `jxm_character`.`character_id` = 3146089; 

Nueva: (Usar índice: ~ 0.02s)

SELECT 
    * 
FROM 
    jxm_character jc 
     JOIN 
    (SELECT DISTINCT 
     (information_date) 
    FROM 
     jxm_character 
    WHERE 
     information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) AS temp 
     ON jc.information_date = STR_TO_DATE(temp.information_date, '%Y-%m-%d') 
     AND jc.ranking_type = 1 
     AND jc.character_id = 3146089; 

jxm_character:

  • Records: ~ 3,5 M
  • PK: jxm_character (information_date, ranking_type, character_id)

SHOW VARIABLES LIKE '%version%';

'protocol_version', '10' 
'version', '5.1.69-log' 
'version_comment', 'Source distribution' 

Última nota: Asegúrese de que entiende índice de MySQL más a la izquierda regla.

P/s: Perdón por mi mal inglés. Publiqué mi código (producción, por supuesto) para borrar mi solución: D.

+0

'IN (SELECT ...)' ha sido notorio por estar mal optimizado. Y hiciste lo "correcto" para convertirlo en un "JOIN". –

Cuestiones relacionadas