Pregunta

He creado un índice de texto Oracle como el siguiente:

create index my_idx on my_table (text) indextype is ctxsys.context; 

Y luego puedo hacer lo siguiente:

select * from my_table where contains(text, '%blah%') > 0;

Pero digamos que tenemos una columna en esta tabla, digamos group_id, y quería hacer la siguiente consulta en su lugar:

select * from my_table where contains(text, '%blah%') > 0 and group_id = 43;

Con el índice anterior, Oracle tendrá que buscar todos los elementos que contengan 'blah', y luego verifique todos sus group_ids.

Idealmente, preferiría buscar solo los elementos con group_id = 43, así que me gustaría un índice como este:

create index my_idx on my_table (group_id, text) indextype is ctxsys.context; 

Como un índice normal, por lo que se puede hacer una búsqueda de texto separada para cada group_id.

¿Hay alguna manera de hacer algo como esto en Oracle (estoy usando 10G si eso es importante)?

Editar (aclaración)

Considere una tabla con un millón de filas y las siguientes dos columnas, entre otras, A y B, ambos numéricos. Digamos que hay 500 valores diferentes de A y 2000 valores diferentes de B, y cada fila es única.

Ahora consideremos select ... where A = x and B = y

Un índice de A y B por separado por lo que puedo decir hacer una búsqueda de índice en B, que devolverá 500 filas diferentes y luego hará una unión/escaneo en estas filas. En cualquier caso, se deben observar al menos 500 filas (aparte de que la base de datos tiene suerte y encuentra la fila requerida temprano.

Mientras que un índice en (A,B) es mucho más efectivo, encuentra la búsqueda de una fila en una fila.

Poner índices separados en group_id Y el texto que siento solo deja el generador de consultas con dos opciones.

(1) Use el group_id indexar y escanear todas las filas resultantes para el texto.
(2) Use el índice de texto y escanee todas las filas resultantes para el group_id.
(3) Use ambos índices y haga una unión.

Mientras que yo quiero:

(4) Use el (group_id, "text") índice para encontrar el índice de texto bajo el particular group_id y escanee ese índice de texto para la fila/filas particulares que necesito. No se requiere escaneo y verificación o unión, al igual que cuando se usa un índice en (A,B).

¿Fue útil?

Solución

Texto de Oracle

1 - Puede mejorar el rendimiento creando el índice de contexto con FILTRADO POR:

create index my_idx on my_table(text) indextype is ctxsys.context filter by group_id;

En mis pruebas el filter by Definitivamente mejoró el rendimiento, pero aún era un poco más rápido usar un índice BTree en Group_ID.

2-Los índices CTXCAT usan "sub-índice", y parecen funcionar de manera similar a un índice de múltiples columnas. Esta parece ser la opción (4) estás buscando:

begin
  ctx_ddl.create_index_set('my_table_index_set');
  ctx_ddl.add_index('my_table_index_set', 'group_id');
end;
/

create index my_idx2 on my_table(text) indextype is ctxsys.ctxcat
    parameters('index set my_table_index_set');

select * from my_table where catsearch(text, 'blah', 'group_id = 43') > 0

Este es probablemente el enfoque más rápido. El uso de la consulta anterior contra 120 MB de texto aleatorio similar a su escenario A y B requirió solo 18 obtengas consistentes. Pero en el lado negativo, crear el índice CTXCAT tomó casi 11 minutos y usó 1.8 GB de espacio.

(Nota: El texto de Oracle parece funcionar correctamente aquí, pero no estoy familiarizado con el texto y no puedo Gaurentee, este no es un uso inapropiado de estos índices como dijo @NulluserException).

Se unen a los índices de columna multicolumno frente a un índice

Para la situación que describe en su edición, normalmente No habría una diferencia significativa entre el uso de un índice en (a, b) y unir índices separados en A y B. construí algunas pruebas con datos similares a lo que describió y una unión de índice requirió solo 7 gets consistentes versus 2 gets consistentes para el índice de múltiples columnas.

La razón de esto es porque Oracle recupera datos en bloques. Un bloque suele ser de 8k, y un bloque de índice ya está ordenado, por lo que probablemente pueda ajustar los valores de 500 a 2000 en unos pocos bloques. Si le preocupa el rendimiento, generalmente el IO para leer y escribir bloques es lo único que importa. Si Oracle tiene que unir o no unos pocos miles de filas es una cantidad intrascendente de tiempo de CPU.

Sin embargo, esto no se aplica a los índices de texto Oracle. Puede unir un índice de contexto con un índice BTree (un mapa de bits y "?), Pero el rendimiento es pobre.

Otros consejos

Pondría un índice group_id Y vea si eso es lo suficientemente bueno. No dices de cuántas filas estamos hablando o de qué rendimiento necesitas.

Recuerde, el orden en que se manejan los predicados no es necesariamente el orden en el que los escribió en la consulta. No intente burlar al optimizador a menos que tenga una razón real.

Version corta: No hay necesidad de hacer eso. El Optimizador de la consulta es lo suficientemente inteligente como para decidir cuál es la mejor manera de seleccionar sus datos. Simplemente cree un índice btree en group_id, es decir:

CREATE INDEX my_group_idx ON my_table (group_id); 

Versión larga: Creé un script (testperf.sql) que inserta 136 filas de datos ficticios.

DESC my_table;

Name     Null     Type      
-------- -------- --------- 
ID       NOT NULL NUMBER(4) 
GROUP_ID          NUMBER(4) 
TEXT              CLOB      

Hay un índice btree en group_id. Para asegurarse de que el índice se use realmente, ejecute esto como usuario de DBA:

EXEC DBMS_STATS.GATHER_TABLE_STATS('<YOUR USER HERE>', 'MY_TABLE', cascade=>TRUE);

Aquí está cuántas filas cada uno group_id tiene y el porcentaje correspondiente:

GROUP_ID               COUNT                  PCT                    
---------------------- ---------------------- ---------------------- 
1                      1                      1                      
2                      2                      1                      
3                      4                      3                      
4                      8                      6                      
5                      16                     12                     
6                      32                     24                     
7                      64                     47                     
8                      9                      7         

Tenga en cuenta que el optimizador de consultas usará un índice solo si cree que es una buena idea, es decir, está recuperando hasta un cierto porcentaje de filas. Entonces, si le pide un plan de consulta:

SELECT * FROM my_table WHERE group_id = 1;
SELECT * FROM my_table WHERE group_id = 7;

Verá que para la primera consulta, usará el índice, mientras que para la segunda consulta, realizará un escaneo de tabla completo, ya que hay demasiadas filas para que el índice sea efectivo cuando group_id = 7.

Ahora, considere una condición diferente - WHERE group_id = Y AND text LIKE '%blah%' (ya que no estoy muy familiarizado con ctxsys.context).

SELECT * FROM my_table WHERE group_id = 1 AND text LIKE '%ipsum%';

Mirando el plan de consulta, verá que voluntad Use el índice en group_id. Tenga en cuenta que el orden de sus condiciones no es importante:

SELECT * FROM my_table WHERE text LIKE '%ipsum%' AND group_id = 1;

Genera el mismo plan de consulta. Y si intentas ejecutar la misma consulta en group_id = 7, verá que se remonta al escaneo completo de la mesa:

SELECT * FROM my_table WHERE group_id = 7 AND text LIKE '%ipsum%';

Tenga en cuenta que Oracle recopila automáticamente las estadísticas todos los días (está programado para funcionar todas las noches y los fines de semana), para mejorar continuamente la efectividad del optimizador de la consulta. En resumen, Oracle hace todo lo posible para optimizar el optimizador, para que no tenga que hacerlo.

No tengo una instancia de Oracle en la mano para probar, y no he utilizado la indexación de texto completo en Oracle, pero generalmente he tenido un buen rendimiento con vistas en línea, que podría ser una alternativa al tipo de índice que tenía en mente. Es la siguiente sintaxis legítima cuando contiene() ¿esta involucrado?

Esta vista en línea le brinda los valores PK de las filas en el Grupo 43:

             (
             select T.pkcol
             from T
             where group = 43
             )

Si el grupo tiene un índice normal y no tiene baja cardinalidad, obtener este conjunto debería ser rápido. Entonces te unirías a ese conjunto con T de nuevo:

           select * from T
           inner join
            (
             select T.pkcol
             from T
             where group = 43
             ) as MyGroup

           on T.pkcol = MyGroup.pkcol
           where contains(text, '%blah%') > 0

Esperemos que el optimizador pueda usar el índice PK para optimizar la unión y luego aplicar el contiene predicar solo a las filas del grupo 43.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top