2012-03-01 14 views
9

Tengo una consulta que me está dando problemas y no puedo entender por qué el optimizador de consultas de MySQL se está comportando como está. Aquí está la información de fondo:¿Por qué MySQL no puede optimizar esta consulta?

Tengo 3 tablas. Dos son relativamente pequeños y uno es grande.

Tabla 1 (muy pequeña, 727 filas):

CREATE TABLE ipa (
ipa_id int (11) AUTO_INCREMENT NOT NULL,
ipa_code int (11) NULL DEFAULT,
ipa_name varchar (100) NULL DEFAULT,
payorcode varchar (2) NULL DEFAULT,
compid int (11) DEFAULT '2'
PRIMARY KEY (ipa_id),
CLAVE ipa_code (ipa_code)) ENGINE = MyISAM

la Tabla 2 (más bien pequeñas, 59455 filas):

CREATE TABLE assign_ipa (
assignid int (11) NOT NULL AUTO_INCREMENT,
ipa_id int (11) NOT NULL,
userid int (11) NOT NUL L,
username varchar (20) NULL DEFAULT,
compid int (11) NULL DEFAULT,
PayorCode char (10) NULL DEFAULT
PRIMARY KEY (assignid),
UNIQUE KEY assignid (assignid, ipa_id),
CLAVE ipa_id (ipa_id)
) ENGINE = MyISAM

Tabla 3 (grande, 24,711,730 filas):

crear la tabla master_final (
IPA int (11) NULL DEFAULT,
MbrCt smallint (6) DEFAULT '0',
PayorCode varchar (4) DEFAULT 'WC',
CLAVE idx_IPA (IPA)
) ENGINE = MyISAM DEFAULT

Ahora para la consulta. Estoy haciendo una combinación de tres vías utilizando las dos primeras tablas más pequeñas para esencialmente subconjunto de la gran tabla en uno de sus valores indexados. Básicamente, obtengo una lista de ID para un usuario, SJOnes y consulto el archivo grande para esos ID.

mysql> explicar
master_final SELECT.PayorCode, sum (master_final.Mbrct) AS MbrCt
DE master_final
INNER JOIN ipa EN ipa.ipa_code = master_final.IPA
combinación interna assign_ipa EN ipa.ipa_id = assign_ipa.ipa_id
WHERE assign_ipa.username = 'SJones'
GROUP BY master_final.PayorCode, master_final.ipa \ G;
* ** * ** * ** * ** * 1. fila * ** * ** * ** * * * *
id: 1
SELECT_TYPE: SIMPLE
tabla: master_final
Tipo: Todos
possible_keys: idx_IPA
importante: NULL
key_len: NULL
ref: NULL
filas:
adicional: El uso temporal; Usando filesort
* ** * ** * ** * ** * 2. fila * ** * ** * ** * ** *
id: 1
SELECT_TYPE: SIMPLE
tabla: IPA
Tipo: ref
possible_keys: PRIMARIA, ipa_code
clave: ipa_code
key_len: 5
ref: wc_test.master_final.IPA
filas: 1
adicional: Usando donde
* ** * ** * ** * ** * 3.fila * ** * ** * ** * ** *
id: 1
SELECT_TYPE: SIMPLE
tabla: assign_ipa
Tipo: ref
possible_keys: ipa_id
clave: ipa_id
key_len: 4
ref: wc_test.ipa .ipa_id
filas: 37
adicionales: (! como 30 minutos), utilizando, cuando
3 rows in set (0.00 sec)

Esta consulta se lleva para siempre. El enunciado de explicación me dice por qué, está haciendo un escaneo de tabla completo en la gran mesa, a pesar de que hay un índice perfectamente bueno. No lo está usando. No entiendo esto. Puedo ver la consulta y ver que solo necesita consultar un par de ID de la gran tabla. Si puedo hacerlo, ¿por qué el optimizador de MySQL no puede hacerlo?

Para ilustrar, aquí están las IDs asociados con '' SJones:

mysql> select nombre de usuario, ipa_id de assign_ipa donde nombre de usuario = SJones ';
+ ---------- + -------- +
| nombre de usuario | ipa_id |
+ ---------- + -------- +
| SJones | 688 |
| SJones | 689 |
+ ---------- + -------- +
2 filas en el conjunto (0,02 seg)

Ahora, puedo reescribir la consulta sustituyendo los valores ipa_id para el nombre de usuario en la cláusula where. Para mí esto es equivalente a la consulta original. MySQL lo ve de manera diferente. Si hago esto, el optimizador hace uso del índice en la gran mesa.

mysql> SELECT explicar
master_final.PayorCode, suma (master_final.Mbrct) AS MbrCt
DE master_final
INNER JOIN IPA EN ipa.ipa_code = master_final.IPA
INNER JOIN assign_ipa EN ipa.ipa_id = assign_ipa.ipa_id
* WHERE assign_ipa.ipa_id in ('688', '689') *
GROUP BY master_final.PayorCode, master_final.ipa \ G;
* ** * ** * ** * ** * 1.fila * ** * ** * ** * ** *
id: 1
SELECT_TYPE: SIMPLE
tabla: ipa
tipo: gama
possible_keys: PRIMARIO, ipa_code
clave: PRIMARIA
key_len: 4
ref: NULL
filas: 2
Extra: Uso de; Usando temporal; Usando filesort
* ** * ** * ** * ** * 2. fila * ** * ** * ** * ** *
id: 1
SELECT_TYPE: SIMPLE
tabla: assign_ipa
Tipo: ref
possible_keys: ipa_id
clave: ipa_id
key_len: 4
ref: wc_test.ipa.ipa_id
filas: 37
adicional: Usando donde
* ** * ** * ** * ** * 3. fila * ** * ** * ** * ** *
id: 1
SELECT_TYPE: SIMPLE
tabla: master_final
Tipo: ref
possible_keys: idx_IPA
clave: idx_IPA
key_len: 5
ref: wc_test.ipa.ipa_code
filas:
adicional: Usando donde
3 rows in set (0.00 sec)

Lo único que he cambiado es que una cláusula donde ni siquiera llega directamente a la gran mesa. Y aún así, el optimizador usa el índice 'idx_IPA' en la gran tabla y ya no se usa la exploración de tabla completa. La consulta cuando se vuelve a escribir de esta manera es muy rápida.

OK, eso es mucha historia. Ahora mi pregunta. ¿Por qué debería importar la cláusula where al optimizador? Cualquiera de las cláusulas devolverá el mismo conjunto de resultados de la tabla más pequeña, y sin embargo obtengo resultados dramáticamente diferentes dependiendo de cuál use. Obviamente, quiero usar la cláusula where que contiene el nombre de usuario en lugar de intentar pasar todos los ID asociados a la consulta. Como está escrito, ¿no es posible?

  1. ¿Alguien puede explicar por qué sucede esto?
  2. ¿Cómo podría reescribir mi consulta para evitar el escaneo completo de la tabla?

Gracias por seguir conmigo. Sé que es una pregunta muy larga.

+0

Leí un artículo de uno de los desarrolladores de MySQL (hace algún tiempo) que el optimizador todavía estaba en proceso, y luego fueron absorbidos por Oracle. ¿Has intentado utilizar "pistas" o tal vez mover el "assign_ipa.username = 'SJones'" a JOIN? –

Respuesta

4

No estoy seguro si estoy en lo cierto, pero creo que lo siguiente está sucediendo aquí. Este:

WHERE assign_ipa.username = 'SJones' 

puede crear una tabla temporal, ya que requiere un escaneo completo de tabla. Las tablas temporales no tienen índices, y tienden a desacelerar mucho las cosas.

El segundo caso

INNER JOIN ipa ON ipa.ipa_code = master_final.IPA 
INNER JOIN assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id 
WHERE assign_ipa.ipa_id in ('688','689') 

por otro lado permite la unión de índices, que es rápida. Además, se puede transformar a

SELECT .... FROM master_final WHERE IDA IN (688, 689) ... 

y creo que MySQL está haciendo eso, también.

Crear un índice en assign_ipa.nombre de usuario puede ayudar.

Editar

I replanteado el problema y ahora tenemos una explicación diferente.

La razón del curso es el índice que falta. Esto significa que MySQL no tiene idea de cuán grande sería el resultado de consultar assign_ipa (MySQL no almacena conteos), por lo que comienza con las uniones en primer lugar, donde puede retransmitir las claves.

Eso es lo que nos dicen las filas 2 y 3 del registro de explicación.

Y después de eso, se trata de filtrar el resultado por assign_ipa.username, que no tiene clave, como se indica en la fila 1.

Tan pronto como hay un índice, se filtra assign_ipa primero, y después se une , usando los índices correspondientes.

+0

Agregué un índice en assign_ipa.username y, efectivamente, eso solucionó el problema. ¡Brillante! Nunca hubiera pensado en eso. ¡Gracias por tu ayuda! – craigm

+0

+1 pero lo siento, no tengo ni idea: ¿por qué crearía una tabla temporal? ¿Para almacenar los ID de assign_ipa descubiertos durante el escaneo? ¿Realmente se hace en una tabla temporal para un pequeño número de coincidencias, y es algo de lo que debería preocuparme en otros DB (Oracle, SQL)? ¡Gracias! – Rup

+0

No sé Oracle y MS SQL para bien, pero creo que son mucho mejor en el uso de tamaños de mesa para estimar los costos de la consulta. –

2

Esto probablemente no es una respuesta directa a su pregunta, pero aquí hay algunas cosas que usted puede hacer:

  1. Run ANALYZE_TABLE ... se actualizará la tabla de estadísticas que tiene un gran impacto en lo optimizador decidirá hacer.

  2. Si aún crees que las uniones no están en orden para que las desees (lo que sucede en tu caso, y por lo tanto el optimizador no usa índices como esperas), puedes usar STRAIGHT_JOIN ... de here: "STRAIGHT_JOIN obliga al optimizador para unir las tablas en el orden en el que aparecen en la cláusula FROM Usted puede usar esto para acelerar una consulta si el optimizador se une a las tablas en orden no óptimo."

  3. para mí Poner "donde parte" directamente en join a veces hace la diferencia y acelera las cosas. Por ejemplo, puede escribir:

...t1 INNER JOIN t2 ON t1.k1 = t2.k2 AND t2.k2=something...

en lugar de

...t1 INNER JOIN t2 ON t1.k1 = t2.k2 .... WHERE t2.k2=something...

Así que esto definitivamente no es una explicación de por qué tiene ese comportamiento, pero sólo algunas pistas. El Optimizador de consultas es una bestia extraña, pero afortunadamente existe el comando EXPLAIN que puede ayudarlo a engañarlo para que se comporte de la manera que desee.

Cuestiones relacionadas