2009-08-04 20 views
7

Tenemos dos tablas se asemeja a una estructura simple etiqueta a grabar la siguiente manera (en realidad es mucho más compleja, pero esta es la esencia del problema):MySQL no usar índice con JOIN, WHERE y ORDER

tag (A.a) | recordId (A.b) 
1   | 1 
2   | 1 
2   | 2 
3   | 2 
.... 

y

recordId (B.b) | recordData (B.c) 
1    | 123 
2    | 666 
3    | 1246 

El problema es obtener registros ordenados con una etiqueta específica. La manera obvia de hacerlo es con una combinación sencilla e índices en (PK) (Aa, Ab), (Ab), (PK) (Bb), (Bb, Bc) como tal:

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c; 

Sin embargo , esto da el resultado desagradable de un filesort:

+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          | 
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+ 
| 1 | SIMPLE  | A  | ref | PRIMARY,b  | PRIMARY | 4  | const  | 94 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | B  | ref | PRIMARY,b  | b  | 4  | booli.A.b | 1 | Using index         | 
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+ 

Usando una "vista materializada" enorme y muy redundante, podemos obtener un rendimiento bastante bueno, pero esto a costa de complicar la lógica de negocio, algo que le gustaría evite, especialmente porque las tablas A y B ya son MV: s (y son necesarias para otras consultas, y de hecho las mismas consultas usan UNION).

create temporary table C engine=innodb as (select A.a, A.b, B.c from A join B on A.b = B.b); 
explain select a, b, c from C where a = 44 order by c; 

Para complicar aún más la situación es el hecho de que tenemos los condicionales en la tabla B-rango, tales como filtros.

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 AND B.c > 678 order by c; 

Pero estamos seguros de que podemos manejar esto si el problema del archivador desaparece.

¿Alguien sabe por qué la unión simple en el bloque de código 3 anterior no usará el índice para la clasificación y si podemos resolver el problema de alguna manera sin crear un nuevo MV?

A continuación se muestra la lista completa de SQL que estamos utilizando para la prueba.

DROP TABLE IF EXISTS A; 
DROP TABLE IF EXISTS B; 
DROP TABLE IF EXISTS C; 
CREATE TEMPORARY TABLE A (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a, b), INDEX idx_A_b (b)) ENGINE=INNODB; 
CREATE TEMPORARY TABLE B (b INT NOT NULL, c INT NOT NULL, d VARCHAR(5000) NOT NULL DEFAULT '', PRIMARY KEY(b), INDEX idx_B_c (c), INDEX idx_B_b (b, c)) ENGINE=INNODB; 

DELIMITER $$ 
CREATE PROCEDURE prc_filler(cnt INT) 
BEGIN 
     DECLARE _cnt INT; 
     SET _cnt = 1; 
     WHILE _cnt <= cnt DO 
       INSERT IGNORE INTO A SELECT RAND()*100, RAND()*10000; 
       INSERT IGNORE INTO B SELECT RAND()*10000, RAND()*1000, ''; 
       SET _cnt = _cnt + 1; 
     END WHILE; 
END 
$$ 
DELIMITER ; 

START TRANSACTION; 
CALL prc_filler(100000); 
COMMIT; 
DROP PROCEDURE prc_filler; 

CREATE TEMPORARY TABLE C ENGINE=INNODB AS (SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b); 
ALTER TABLE C ADD (PRIMARY KEY(a, b), INDEX idx_C_a_c (a, c)); 

EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE 1 ORDER BY B.c; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b where A.a = 44 ORDER BY B.c; 
EXPLAIN EXTENDED SELECT a, b, c FROM C WHERE a = 44 ORDER BY c; 
-- Added after Quassnois comments 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10; 
+0

La clasificación de archivos se produce en su cláusula ORDER BY. ¿Cómo se indexa 'B.c'? – jason

+0

@jason: He actualizado el SQL en la publicación para que sea un poco más legible. La indexación debería ser clara ahora. – Paso

Respuesta

9

Cuando trato de reproducir esta consulta utilizando las secuencias de comandos:

SELECT A.a, A.b, B.c 
FROM A 
JOIN B 
ON  A.b = B.b 
WHERE a = 44 
ORDER BY 
     c 

, se completa en 0.0043 seconds (inmediatamente), devuelve 930 filas y produce este plan:

1, 'SIMPLE', 'A', 'ref', 'PRIMARY', 'PRIMARY', '4', 'const', 1610, 'Using index; Using temporary; Using filesort' 
1, 'SIMPLE', 'B', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.A.b', 1, '' 

¡Es bastante eficiente para tal consulta.

Para realizar una consulta de este tipo, no puede usar un único índice para filtrar y ordenar.

Lee este artículo en mi blog para explicaciones más detalladas:

Si espera que su consulta para devolver pocos registros, se debe utilizar el índice en A para el filtrado y luego especie usando filesort (como hace la consulta anterior)

Si espera que devuelva muchos registros (y LIMIT ellos), es necesario utilizar el índice para clasificar y luego filtro:

CREATE INDEX ix_a_b ON a (b); 
CREATE INDEX ix_b_c ON b (c) 

SELECT * 
FROM B FORCE INDEX (ix_b_c) 
JOIN A 
ON  A.b = B.b 
ORDER BY 
     b.c 
LIMIT 10; 

1, 'SIMPLE', 'B', 'index', '', 'ix_b_c', '4', '', 2, 'Using index' 
1, 'SIMPLE', 'A', 'ref', 'ix_a_b', 'ix_a_b', '4', 'test.B.b', 4, 'Using index' 
+0

Con los datos reales, la tabla de registros es bastante grande (tanto en ancho como en número de filas, con muchos VARCHAR (255): s) y, por lo tanto, la tabla temporal cuesta más ya que hay muchos más datos para copiar. En nuestro db de producción (xeon de 8 núcleos con todo en la memoria) la consulta toma alrededor de 0.05-0.1s y una prueba MV muestra sub 0.01s veces. – Paso

+0

No obtengo el mismo plan de consulta que imprimió anteriormente para la misma consulta. De todos modos, el cambio en ORDEN no me ayuda realmente, seguro que elimina el archivo pero obtengo los resultados en el orden incorrecto. Además, simplemente cambiando el ORDER en la consulta original a "B.b, B.c" elimina el filesort, lo que indica (bueno, a mí;)) que es posible hacer esto sin una tabla/filesort temporal. (Lo curioso, en realidad tomé prestado el SP para insertarlo en tu blog) – Paso

+0

@Paso: Lo siento, no entendí bien tu tarea. Cree un índice en 'b.c' solamente y cambie la condición' ORDER BY'. Lo actualizaré en la publicación ahora. – Quassnoi

0

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;

Si alias de las columnas, hace que la ayuda ? Ejemplo:

SELECT 
T1.a AS colA, 
T2.b AS colB, 
T2.c AS colC 
FROM A AS T1 
JOIN B AS T2 
ON (T1.b = T2.b) 
WHERE 
T1.a = 44 
ORDER BY colC; 

Los únicos cambios que hice fueron:

  • pongo la condiciones de unión entre paréntesis
  • El unen a las condiciones y donde las condiciones se basan en las columnas de tabla
  • El ORDER BY condición se basa en la columna de la tabla resultante
  • Alise las columnas de la tabla de resultados y las tablas consultadas para (con suerte) hacerlo más claro cuando estaba usando una u otra (y más claro para el servidor. Olvidas referirte a tus columnas en dos lugares en tu consulta original).

Sé que sus datos reales son más complejos, pero supongo que proporcionó una versión simple de la consulta porque el problema está en ese nivel simple.

+0

Me temo que no, su consulta da exactamente el mismo resultado de EXPLICACIÓN. – Paso

+0

¿De verdad quieres unirte a las dos mesas? Lo que quiero decir es, ¿se vinculan las dos tablas donde cada fila es un resultado completo basado en la consulta, o es más como que cada fila tiene los datos necesarios de ambas tablas? Pregunto porque si las dos tablas no están realmente unidas de tal manera que se requiera una unión, podría considerar una UNIÓN en su lugar. Con un UNION, las consultas son completamente independientes y, por lo tanto, no es necesario realizar sub-consultas o tablas temporales o cualquier otra cosa que imponga impuestos. – Anthony

+0

Realmente no entiendo. Las tablas están UNIDAS sobre A.b = B.b y necesito los datos de B para cada A que coincida con una condición, ¿cómo ayudaría UNIÓN aquí? Para completar; no, no necesito todos los datos, solo los datos de B. Vea el ejemplo de etiqueta en la parte superior de la pregunta, eso debería explicar todo lo más precisamente que pueda. – Paso

Cuestiones relacionadas