2011-01-22 26 views
18

Tengo una consulta de MySQL que se está generando por un script PHP, la consulta se verá algo como esto:MySQL Query IN() Cláusula lento en columna indexada

SELECT * FROM Recipe_Data WHERE 404_Without_200 = 0 AND Failures_Without_Success = 0 AND RHD_No IN (10, 24, 34, 41, 43, 51, 57, 59, 61, 67, 84, 90, 272, 324, 402, 405, 414, 498, 500, 501, 510, 559, 562, 595, 632, 634, 640, 643, 647, 651, 703, 714, 719, 762, 765, 776, 796, 812, 814, 815, 822, 848, 853, 855, 858, 866, 891, 920, 947, 956, 962, 968, 1049, 1054, 1064, 1065, 1070, 1100, 1113, 1119, 1130, 1262, 1287, 1292, 1313, 1320, 1327, 1332, 1333, 1335, 1340, 1343, 1344, 1346, 1349, 1352, 1358, 1362, 1365, 1482, 1495, 1532, 1533, 1537, 1549, 1550, 1569, 1571, 1573, 1574, 1596, 1628, 1691, 1714, 1720, 1735, 1755, 1759, 1829, 1837, 1844, 1881, 1919, 2005, 2022, 2034, 2035, 2039, 2054, 2076, 2079, 2087, 2088, 2089, 2090, 2091, 2092, 2154, 2155, 2156, 2157, 2160, 2162, 2164, 2166, 2169, 2171, 2174, 2176, 2178, 2179, 2183, 2185, 2186, 2187, 2201, 2234, 2236, 2244, 2245, 2250, 2255, 2260, 2272, 2280, 2281, 2282, 2291, 2329, 2357, 2375, 2444, 2451, 2452, 2453, 2454, 2456, 2457, 2460, 2462, 2464, 2465, 2467, 2468, 2469, 2470, 2473, 2474, 2481, 2485, 2487, 2510, 2516, 2519, 2525, 2540, 2545, 2547, 2553, 2571, 2579, 2580, 2587, 2589, 2597, 2602, 2611, 2629, 2660, 2662, 2700, 2756, 2825, 2833, 2835, 2858, 2958, 2963, 2964, 3009, 3090, 3117, 3118, 3120, 3121, 3122, 3123, 3126, 3127, 3129, 3130, 3133, 3135, 3137, 3138, 3139, 3141, 3142, 3145, 3146, 3147, 3151, 3152, 3155, 3193, 3201, 3204, 3219, 3221, 3222, 3223, 3224, 3225, 3226, 3227, 3228, 3229, 3231, 3232, 3233, 3234, 3235, 3237, 3239, 3246, 3250, 3253, 3259, 3261, 3291, 3315, 3328, 3377, 3381, 3383, 3384, 3385, 3387, 3388, 3389, 3390, 3396, 3436, 3463, 3465, 3467, 3470, 3471, 3484, 3507, 3515, 3554, 3572, 3641, 3672, 3683, 3689, 3690, 3692, 3693, 3694, 3697, 3698, 3705, 3711, 3713, 3715, 3716, 3717, 3719, 3720, 3722, 3726, 3727, 3732, 3737, 3763, 3767, 3770, 3771, 3772, 3773, 3803, 3810, 3812, 3816, 3846, 3847, 3848, 3851, 3874, 3882, 3902, 3903, 3906, 3908, 3916, 3924, 3967, 3987, 4006, 4030, 4043, 4045, 4047, 4058, 4067, 4107, 4108, 4114, 4115, 4131, 4132, 4133, 4137, 4138, 4139, 4140, 4141, 4142, 4146, 4150, 4151, 4152, 4153, 4157, 4158, 4160, 4163, 4166, 4167, 4171, 4179, 4183, 4221, 4225, 4242, 4257, 4435, 4437, 4438, 4443, 4446, 4449, 4450, 4451, 4452, 4454, 4460, 4550, 4557, 4618, 4731, 4775, 4804, 4972, 5025, 5026, 5039, 5042, 5294, 5578, 5580, 5599, 5602, 5649, 5726, 5779, 5783, 5931, 5934, 5936, 5939, 5940, 5941, 5978, 6044, 6056, 6113, 6116, 6118, 6122, 6123, 6125, 6127, 6128, 6129, 6130, 6131, 6135, 6141, 6145, 6147, 6150, 6152, 6153, 6154, 6160, 6166, 6169); 

La columna RHD_No es la clave principal para esta base de datos, y hay alrededor de 400,000 filas en total. El problema es que la consulta es extremadamente lento, a menudo alrededor de 2 segundos, pero lo he visto llegar, siempre y 10.

Cuando intento de explicar la consulta, todo parece que debería estar bien:

+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+ 
| id | select_type | table  | type | possible_keys | key  | key_len | ref | rows | Extra  | 
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+ 
| 1 | SIMPLE  | Recipe_Data | range | PRIMARY  | PRIMARY | 4  | NULL | 420 | Using where | 
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+ 

Cuando la consulta del perfil me sale:

mysql> show profile; 
+--------------------------------+----------+ 
| Status       | Duration | 
+--------------------------------+----------+ 
| starting      | 0.000015 | 
| checking query cache for query | 0.000266 | 
| Opening tables     | 0.000009 | 
| System lock     | 0.000004 | 
| Table lock      | 0.000006 | 
| init       | 0.000115 | 
| optimizing      | 0.000038 | 
| statistics      | 0.000797 | 
| preparing      | 0.000047 | 
| executing      | 0.000002 | 
| Sending data     | 2.675270 | 
| end       | 0.000007 | 
| query end      | 0.000003 | 
| freeing items     | 0.000071 | 
| logging slow query    | 0.000002 | 
| logging slow query    | 0.000058 | 
| cleaning up     | 0.000005 | 
+--------------------------------+----------+ 

he estado trabajando en este problema desde hace mucho tiempo y no he sido capaz de encontrar una solución. ¿Hay algo abiertamente erróneo con esta consulta? No veo cómo mirar 420 filas debería tomar más de 2 segundos.

+0

Solo una corazonada, pero ¿cómo se ven todos los índices en 'Recipe_Data'? –

+0

Tengo una clave principal, un RHD_No, y una clave ÚNICA en otra columna. – zmbush

+0

No claves (per se), pero índices. No soy un experto en MySQL, así que esta no es una respuesta, pero ¿ha intentado agregar un índice en '404_Without_200, Failures_Without_Success'? Consulte: http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html para ver el uso en las consultas, y http://dev.mysql.com/doc/refman/5.0/en/create -index.html para crear índices. –

Respuesta

23

Usted está accediendo a 420 filas por clave primaria que probablemente dará lugar a un camino de acceso índice. Esto podría acceder a 2 páginas de índice y una página de datos por clave. Si están en caché, la consulta debería ejecutarse rápidamente. De lo contrario, cada acceso a la página que vaya al disco incurrirá en la latencia habitual del disco. Si suponemos una latencia de disco de 5 ms y un 80% de aciertos de caché, llegamos a 420 * 3 * 0.2 * 5ms = 1.2 segundos, que es del orden de lo que está viendo.

+15

¿Una explicación para el rendimiento de la base de datos basada en métricas y números concretos en lugar de generalizaciones? Calma mi corazon latiente. –

+2

Entonces, ¿cómo mejoraría mi consulta? – zmbush

+0

@Conspicuous Compiler - LOL. +1 – DVK

8

Debe transformar las cláusulas IN en las cláusulas INNER JOIN.

Puede transformar una consulta como esta:

SELECT foo 
FROM bar 
WHERE bar.stuff IN 
     (SELECT stuff FROM asdf) 

En una consulta como esta otra:

SELECT b.foo 
FROM ( 
     SELECT DISTINCT stuff 
     FROM asdf) a 
JOIN bar b 
ON  b.stuff = a.stuff 

Usted va a obtener una gran cantidad de rendimiento.

Como php generar la consulta, pruebe algún tipo de truco como una tabla temporal para los elementos dentro de la cláusula IN. Siempre trate de evitar las cláusulas IN si puede, ya que consumen mucho tiempo.

+2

¿Podría quizás proporcionar un enlace que explique por qué y/o mide cuánto en cláusulas son más lentas que las uniones? –

+1

Por supuesto: http://explainextended.com/2009/08/18/passing-parameters-in-mysql-in-list-vs-temporary-table/ – Jonathan

+0

Esto es dorado. – tfont

10

El problema es que IN se trata, básicamente, como un montón de OR s (por ejemplo

col IN (1,2,3) 

es

col = 1 OR col = 2 OR col = 3 

Esto es mucho más lento que una combinación.

Lo que debe hacer es generar el código SQL que crea la tabla temporal, lo rellena con los valores en la cláusula "IN", y luego se une con esa tabla temporal

CREATE TEMPORARY TABLE numbers (n INT) 

Luego, en un bucle, añadir

INSERT numbers VALUES ($next_number) 

Luego, al final

SELECT * FROM numbers, Recipe_Data 
WHERE numbers.n = RHD_No 
+1

No olvides soltar la tabla temporal al final, obviamente :) – DVK

+0

Creo que el rendimiento de las cláusulas 'IN' debe variar mucho de un servidor a otro. Esta pregunta está etiquetada como "mysql", así que tal vez a eso te refieres. En mi experiencia con SQL Server 2000/2005, las grandes cláusulas 'IN' para búsquedas de claves primarias eran extremadamente rápidas (la base de datos y las consultas en la aplicación en las que estoy pensando estaban bastante optimizadas, era todo código en un alto tráfico , aplicación web de gran volumen). – Pointy

+0

@Pointy - Puede ser específico del servidor, sé que Sybase tenía ese comportamiento de rendimiento con IN, al menos en versiones anteriores (pre 12). – DVK

1

Voy a jugar aquí y sugieren que la ejecución de la siguiente consulta sólo una vez para crear un índice adecuado para su búsqueda debería reducir el tiempo de consulta por lo menos un segundo ...

CREATE INDEX returnstatus ON Recipe_Data(404_Without_200,Failures_Without_Success) 

Ver: http://dev.mysql.com/doc/refman/5.0/en/create-index.html para crear índices, y http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html para ver cómo se usan los índices en las consultas.

De lo contrario, vea todos los procesos en ejecución en mysql para ver si una consulta actualmente en ejecución de cualquier fuente solo se niega a morir mientras consume todo el tiempo del servidor y lo mata. Ver: http://dev.mysql.com/doc/refman/5.0/en/kill.html

En su defecto, determine qué más cada registro puede tener en común para evitar tener que hacer referencia a cada uno individualmente por número de identificación en su declaración IN. Si es necesario, agregue otra columna de tabla para rastrear esa comunidad. Luego, agregue columna (s) que tengan esa característica común al índice anterior y filtren por eso en su cláusula WHERE en lugar de usar la declaración IN. Por ejemplo, si desea que sólo los números de identificación de imprimir en la página, tener una columna visible como tipo: tinyint con valor 0 excluir, y el valor 1 a incluir en los resultados de la búsqueda, a continuación, añadir visible columna a sus indexs yWHERE cláusula para acelerar la consulta. No necesitaría esa declaración IN en absoluto.

Tal vez su declaración in se construye dinámicamente utilizando una consulta previa. Si ese es el caso, intente tirar todas las filas con Recipe_Data WHERE 404_Without_200 = 0 AND Failures_Without_Success = 0. Luego, en su script PHP, simplemente descarte un registro en su ciclo de búsqueda si el RHD_No no concuerda con un valor esperado.