Tengo una tabla con 250K filas en mi base de datos de prueba. (Hay unos pocos cientos de millones en producción, podemos observar el mismo problema allí). La tabla tiene un identificador de cadena nvarchar2 (50), no nulo, con un índice único (no es el PK).
Los identificadores están formados por una primera parte que tiene 8 valores diferentes en mi base de datos de prueba (y alrededor de mil en producción), luego un signo @, y finalmente un número, de 1 a 6 dígitos de largo. Por ejemplo, podría haber 50 mil filas que comienzan con 'ABCD_BGX1741F_2006_13_20110808.xml @', y le siguen 50 mil números diferentes.
Cuando busco una sola fila en función de su identificador, la cardinalidad se estima en 1, el costo es muy bajo y funciona bien. Cuando busco más de una fila con varios identificadores en una expresión IN o una expresión OR, las estimaciones para el índice son completamente incorrectas, por lo que se utiliza una exploración de tabla completa. Si fuerzo el índice con una pista, es muy rápido, el escaneo completo de la tabla en realidad se ejecuta un orden de magnitud más lento (y mucho más lento en la producción). Por lo tanto, es un problema de optimizador.
Como prueba, dupliqué la tabla (en el mismo esquema + espacio de tabla) con exactamente el mismo DDL y exactamente el mismo contenido. Recreé el índice único en la primera tabla para una buena medida, y creé exactamente el mismo índice en la tabla de clonación. Hice un DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);
. Incluso puede ver que los nombres de índice son consecutivos. Entonces, la única diferencia entre las dos tablas es que la primera se cargó en orden aleatorio durante un largo período de tiempo, con bloques dispersos en el disco (en un espacio de tabla junto con varias otras tablas grandes), la segunda se cargó como un lote INSERTAR-SELECCIONAR. Aparte de eso, no puedo imaginar ninguna diferencia. (La tabla original se ha reducido desde la última gran eliminación, y no ha habido una sola eliminación después de eso).
Aquí hay planes de consulta para los enfermos y la tabla de clonación (las cadenas debajo del pincel negro son las mismas en toda la imagen, y también debajo del pincel gris):
(En este ejemplo, hay 1867 filas que comienzan con el identificador que está cepillado en negro. Una consulta de 2 filas produce una cardinalidad de 1867 * 2, una consulta de 3 filas produce una cardinalidad de 1867 * 3, etc. No se puede Por casualidad, a Oracle parece no importarle el final de los identificadores).
¿Qué podría causar este comportamiento? Obviamente, sería bastante costoso recrear la mesa en producción.
USER_TABLES: http://i.stack.imgur.com/nDWze.jpg USER_INDEXES: http://i.stack.imgur.com/DG9um.jpg Solo cambié el esquema y el nombre del espacio de tabla. Puede ver que los nombres de tabla e índice son los mismos que en la captura de pantalla del plan de consulta.
fuente
in
), ¿no? Creo que la CBO hace la suposición de cardinalidad 1, pero solo en el caso más simple. Supongo que podríaUNION ALL
solucionar todo el problema utilizando un gran, pero puede haber otras razones para no hacerlo y JL menciona otras posibles soluciones en la publicación del blog vinculado.method_opt=>'for all indexed columns'
?¡Encontré la solución! Es tan hermoso y realmente aprendí MUCHO sobre Oracle.
En una palabra: histogramas.
Comencé a leer mucho sobre cómo funciona la CBO de Oracle y me topé con histogramas. No lo entendí completamente, así que eché un vistazo a la tabla USER_HISTOGRAMS y listo. Había varias filas para la mesa enferma, y prácticamente nada para la mesa clonada. Para la tabla enferma, había una fila para cada una de las 8 partes de inicio de identificador diferentes. Y esta es la clave: se cortaron con 32 caracteres, antes del signo @. Como dije, la primera parte de las teclas es muy repetitiva, se vuelven diferentes después del signo @.
Parece que los histogramas pueden ser más poderosos que el simple hecho de que un índice único siempre tiene una cardinalidad de 0 o 1 para un valor dado. Cuando estaba buscando más de 2 filas, Oracle miró el histograma, pensó que podría haber decenas de miles de valores para esa parte de inicio del identificador, y desvió la CBO del rumbo.
¡Eliminé los histogramas para esa columna en la tabla anterior y el problema desapareció!
Más información: https://blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating
fuente
Le envié un correo electrónico a Jonathan Lewis sobre esto y obtuve una respuesta muy útil:
Le recomiendo leer las publicaciones de blog que vincula, que describen en detalle la limitación de los histogramas en los que se está ejecutando, por ejemplo:
fuente