Las consultas de texto completo en esta base de datos (almacenamiento de tickets RT ( Request Tracker )) parecen tardar mucho tiempo en ejecutarse. La tabla de archivos adjuntos (que contiene los datos de texto completo) es de aproximadamente 15 GB.
El esquema de la base de datos es el siguiente, es de aproximadamente 2 millones de filas:
rt4 = # \ d + archivos adjuntos Tabla "archivos adjuntos públicos" Columna | Tipo | Modificadores | Almacenamiento | Descripción ----------------- + ----------------------------- + - -------------------------------------------------- ------ + ---------- + ------------- id | entero | nextval no nulo predeterminado ('attachments_id_seq' :: regclass) | llano | transaccion | entero | no nulo | llano | padre | entero | no nulo predeterminado 0 | llano | messageid | carácter variable (160) | El | extendido | sujeto | carácter variable (255) | El | extendido | nombre de archivo | carácter variable (255) | El | extendido | contenttype | carácter variable (80) | El | extendido | contentencoding | carácter variable (80) | El | extendido | contenido | texto | El | extendido | encabezados | texto | El | extendido | creador | entero | no nulo predeterminado 0 | llano | creado | marca de tiempo sin zona horaria | El | llano | contentindex | tsvector | El | extendido | Índices: "attachments_pkey" CLAVE PRIMARIA, btree (id) "adjuntos1" btree (padre) btree "adjuntos2" (transacciónid) btree "adjuntos3" (padre, transacciónid) Ginebra "contentindex_idx" (contentindex) Tiene OID: no
Puedo consultar la base de datos por sí sola muy rápidamente (<1s) con una consulta como:
select objectid
from attachments
join transactions on attachments.transactionid = transactions.id
where contentindex @@ to_tsquery('frobnicate');
Sin embargo, cuando RT ejecuta una consulta que se supone que realiza una búsqueda de índice de texto completo en la misma tabla, generalmente tarda cientos de segundos en completarse. La salida de análisis de consulta es la siguiente:
Consulta
SELECT COUNT(DISTINCT main.id)
FROM Tickets main
JOIN Transactions Transactions_1 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
AND ( Transactions_1.ObjectId = main.id )
JOIN Attachments Attachments_2 ON ( Attachments_2.TransactionId = Transactions_1.id )
WHERE (main.Status != 'deleted')
AND ( ( ( Attachments_2.ContentIndex @@ plainto_tsquery('frobnicate') ) ) )
AND (main.Type = 'ticket')
AND (main.EffectiveId = main.id);
EXPLAIN ANALYZE
salida
PLAN DE CONSULTA -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------- Agregado (costo = 51210.60..51210.61 filas = 1 ancho = 4) (tiempo real = 477778.806..477778.806 filas = 1 bucles = 1) -> Bucle anidado (costo = 0.00..51210.57 filas = 15 ancho = 4) (tiempo real = 17943.986..477775.174 filas = 4197 bucles = 1) -> Bucle anidado (costo = 0.00..40643.08 filas = 6507 ancho = 8) (tiempo real = 8.526..20610.380 filas = 1714818 bucles = 1) -> Seq Scan en tickets principales (costo = 0.00..9818.37 filas = 598 ancho = 8) (tiempo real = 0.008..256.042 filas = 96990 bucles = 1) Filtro: (((estado) :: texto 'eliminado' :: texto) Y (id = efectivo) Y ((tipo) :: texto = 'ticket' :: texto)) -> Escaneo de índice usando transacciones1 en transacciones transacciones_1 (costo = 0.00..51.36 filas = 15 ancho = 8) (tiempo real = 0.102..0.202 filas = 18 bucles = 96990) Índice Cond: (((objecttype) :: text = 'RT :: Ticket' :: text) AND (objectid = main.id)) -> Escaneo de índice usando archivos adjuntos2 en archivos adjuntos archivos adjuntos_2 (costo = 0.00..1.61 filas = 1 ancho = 4) (tiempo real = 0.266..0.266 filas = 0 bucles = 1714818) Índice Cond: (transaccion = transacciones_1.id) Filtro: (contentindex @@ plainto_tsquery ('frobnicate' :: text)) Tiempo de ejecución total: 477778.883 ms
Por lo que puedo decir, el problema parece ser que no está usando el índice creado en el contentindex
campo ( contentindex_idx
), sino que está haciendo un filtro en una gran cantidad de filas coincidentes en la tabla de archivos adjuntos. Los recuentos de filas en la salida de explicación también parecen ser muy inexactos, incluso después de un reciente ANALYZE
: filas estimadas = 6507 filas reales = 1714818.
No estoy realmente seguro de a dónde ir después con esto.
Respuestas:
Esto se puede mejorar de mil y una maneras, entonces debería ser cuestión de milisegundos .
Mejores consultas
Esta es solo su consulta reformateada con alias y algo de ruido eliminado para despejar la niebla:
La mayor parte del problema con su consulta radica en las dos primeras tablas
tickets
ytransactions
, que faltan en la pregunta. Estoy llenando de conjeturas educadas.t.status
,t.objecttype
ytr.objecttype
probablemente no debería serlotext
,enum
o posiblemente algún valor muy pequeño que haga referencia a una tabla de búsqueda.EXISTS
semi-unirseSuponiendo que
tickets.id
es la clave principal, este formulario reescrito debería ser mucho más barato:En lugar de multiplicar filas con dos uniones 1: n, solo para colapsar múltiples coincidencias al final
count(DISTINCT id)
, use unaEXISTS
semiunión, que puede dejar de mirar más allá tan pronto como se encuentre la primera coincidencia y al mismo tiempo obsoleto elDISTINCT
paso final . Por documentación:La efectividad depende de cuántas transacciones por boleto y archivos adjuntos por transacción haya.
Determinar el orden de las uniones con
join_collapse_limit
Si sabe que su término de búsqueda
attachments.contentindex
es muy selectivo , más selectivo que otras condiciones en la consulta (que probablemente sea el caso de 'frobnicate', pero no para 'problema'), puede forzar la secuencia de uniones. El planificador de consultas apenas puede juzgar la selectividad de palabras particulares, excepto las más comunes. Por documentación:Use
SET LOCAL
con el propósito de configurarlo solo para la transacción actual.El orden de las
WHERE
condiciones es siempre irrelevante. Solo el orden de las combinaciones es relevante aquí.O use un CTE como explica @jjanes en la "Opción 2". para un efecto similar
Índices
Índices de árbol B
Tome todas las condiciones
tickets
que se usan de manera idéntica con la mayoría de las consultas y cree un índice parcial sobretickets
:Si una de las condiciones es variable, suéltela
WHERE
y añada la columna como columna de índice.Otro sobre
transactions
:La tercera columna es solo para habilitar escaneos de solo índice.
Además, dado que tiene este índice compuesto con dos columnas enteras en
attachments
:Este índice adicional es un desperdicio completo , elimínelo:
Detalles:
Índice de GIN
Agregue
transactionid
a su índice GIN para que sea mucho más efectivo. Esta puede ser otra bala de plata , porque potencialmente permite escaneos de solo índice, eliminando por completo las visitas a la gran mesa.Necesita clases de operador adicionales proporcionadas por el módulo adicional
btree_gin
. Instrucciones detalladas:4 bytes de una
integer
columna no hacen que el índice sea mucho más grande. Además, afortunadamente para usted, los índices GIN son diferentes de los índices del árbol B en un aspecto crucial. Por documentación:El énfasis audaz es mío. Por lo que sólo necesita el uno índice GIN (grande y algo costoso).
Definición de tabla
Mueve el
integer not null columns
al frente. Esto tiene un par de efectos positivos menores en el almacenamiento y el rendimiento. Ahorra de 4 a 8 bytes por fila en este caso.fuente
Opción 1
El planificador no tiene una idea de la verdadera naturaleza de la relación entre EffectiveId e id, por lo que probablemente piense en la cláusula:
va a ser mucho más selectivo de lo que realmente es Si esto es lo que creo que es, EffectiveID casi siempre es igual a main.id, pero el planificador no lo sabe.
Una forma posiblemente mejor de almacenar este tipo de relación suele ser definir el valor NULL de EffectiveID para que signifique "efectivamente igual que id", y almacenar algo en él solo si hay una diferencia.
Suponiendo que no desea reorganizar su esquema, puede intentar solucionarlo reescribiendo esa cláusula como algo así como:
El planificador podría asumir que
between
es menos selectivo que una igualdad, y eso podría ser suficiente para sacarlo de su trampa actual.opcion 2
Otro enfoque es usar un CTE:
Esto obliga al planificador a usar ContentIndex como fuente de selectividad. Una vez que se ve obligado a hacerlo, las correlaciones engañosas de la columna en la tabla Tickets ya no serán tan atractivas. Por supuesto, si alguien busca 'problema' en lugar de 'frobnicate', eso podría ser contraproducente.
Opción 3
Para investigar más las estimaciones de filas incorrectas, debe ejecutar la consulta a continuación en todas las permutaciones 2 ^ 3 = 8 de las diferentes cláusulas AND que se comentan. Esto ayudará a determinar de dónde proviene la mala estimación.
fuente