Tengo una tabla con 7.2 millones de tuplas que se ve así:
table public.methods
column | type | attributes
--------+-----------------------+----------------------------------------------------
id | integer | not null DEFAULT nextval('methodkey'::regclass)
hash | character varying(32) | not null
string | character varying | not null
method | character varying | not null
file | character varying | not null
type | character varying | not null
Indexes:
"methods_pkey" PRIMARY KEY, btree (id)
"methodhash" btree (hash)
Ahora quiero seleccionar algunos valores, pero la consulta es increíblemente lenta:
db=# explain
select hash, string, count(method)
from methods
where hash not in
(select hash from nostring)
group by hash, string
order by count(method) desc;
QUERY PLAN
----------------------------------------------------------------------------------------
Sort (cost=160245190041.10..160245190962.07 rows=368391 width=182)
Sort Key: (count(methods.method))
-> GroupAggregate (cost=160245017241.77..160245057764.73 rows=368391 width=182)
-> Sort (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
Sort Key: methods.hash, methods.string
-> Seq Scan on methods (cost=0.00..160243305942.27 rows=3683905 width=182)
Filter: (NOT (SubPlan 1))
SubPlan 1
-> Materialize (cost=0.00..41071.54 rows=970636 width=33)
-> Seq Scan on nostring (cost=0.00..28634.36 rows=970636 width=33)
La hash
columna es el hash de md5 string
y tiene un índice. Entonces, creo que mi problema es que toda la tabla está ordenada por id y no por hash, por lo que lleva un tiempo ordenarla primero y luego agruparla.
La tabla nostring
contiene solo una lista de hashes que no quiero tener. Pero necesito que ambas tablas tengan todos los valores. Por lo tanto, no es una opción eliminarlos.
información adicional: ninguna de las columnas puede ser nula (fijada en la definición de la tabla) y estoy usando postgresql 9.2.
NULL
valores en la columnamethod
? ¿Hay duplicados enstring
?Respuestas:
La respuesta de
LEFT JOIN
in @ dezso debería ser buena. Sin embargo, un índice difícilmente será útil (per se), porque la consulta tiene que leer toda la tabla de todos modos; la excepción son los escaneos de solo índice en Postgres 9.2+ y condiciones favorables, ver más abajo.Ejecutar
EXPLAIN ANALYZE
en la consulta. Varias veces para excluir los efectos de cobro y el ruido. Compara los mejores resultados.Cree un índice de varias columnas que coincida con su consulta:
¿Espere? ¿Después de que dije que un índice no ayudaría? Bueno, lo necesitamos para
CLUSTER
la mesa:Vuelva a ejecutar
EXPLAIN ANALYZE
. ¿Más rápido? Debería ser.CLUSTER
es una operación única para reescribir toda la tabla en el orden del índice utilizado. También es efectivamente aVACUUM FULL
. Si quieres estar seguro, ejecutarías una prueba previaVACUUM FULL
solo para ver qué se puede atribuir a eso.Si su tabla ve muchas operaciones de escritura, el efecto se degradará con el tiempo. Programe
CLUSTER
fuera de horario para restaurar el efecto. El ajuste fino depende de su caso de uso exacto. El manual sobreCLUSTER
.CLUSTER
Es una herramienta bastante tosca, necesita un candado exclusivo sobre la mesa. Si no puede permitirse eso, considerepg_repack
qué puede hacer lo mismo sin un bloqueo exclusivo. Más en esta respuesta posterior:Si el porcentaje de
NULL
valores en la columnamethod
es alto (más de ~ 20 por ciento, dependiendo del tamaño real de las filas), un índice parcial debería ayudar:(Su actualización posterior muestra que sus columnas son
NOT NULL
, por lo que no es aplicable).Si está ejecutando PostgreSQL 9.2 o posterior (como comentó @deszo ), los índices presentados pueden ser útiles sin
CLUSTER
que el planificador pueda utilizar escaneos de solo índice . Solo aplicable en condiciones favorables: no hay operaciones de escritura que afecten el mapa de visibilidad ya que la últimaVACUUM
y todas las columnas de la consulta deben estar cubiertas por el índice. Básicamente, las tablas de solo lectura pueden usar esto en cualquier momento, mientras que las tablas muy escritas son limitadas. Más detalles en el Wiki de Postgres.El índice parcial mencionado anteriormente podría ser aún más útil en ese caso.
Si , por otro lado, no hay
NULL
valores en la columnamethod
, debe1.) definirlo
NOT NULL
y2.) usar en
count(*)
lugar decount(method)
, eso es un poco más rápido y hace lo mismo en ausencia deNULL
valores.Si tiene que llamar a esta consulta con frecuencia y la tabla es de solo lectura, cree un
MATERIALIZED VIEW
.Punto fino exótico: su tabla lleva un nombre
nostring
, pero parece contener hashes. Al excluir los hashes en lugar de las cadenas, existe la posibilidad de que excluya más cadenas de las previstas. Extremadamente improbable, pero posible.fuente
Bienvenido a DBA.SE!
Puede intentar reformular su consulta de esta manera:
u otra posibilidad:
NOT IN
es un sumidero típico para el rendimiento ya que es difícil usar un índice con él.Esto puede mejorarse aún más con los índices. Un índice sobre
nostring.hash
parece útil. Pero primero: ¿qué obtienes ahora? (Sería mejor ver la producciónEXPLAIN ANALYZE
ya que los costos en sí mismos no indican el tiempo que tomaron las operaciones).fuente
EXPLAIN ANALYZE
.Como hash es un md5, probablemente intentes convertirlo en un número: puedes almacenarlo como un número o simplemente crear un índice funcional que calcule ese número en una función inmutable.
Otras personas ya crearon una función pl / pgsql que convierte (parte de) un valor md5 de texto a cadena. Consulte /programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql para ver un ejemplo.
Creo que realmente está pasando mucho tiempo en la comparación de cadenas mientras escanea el índice. Si logra almacenar ese valor como un número, entonces debería ser realmente más rápido.
fuente
Me encontré mucho con este problema y descubrí un simple truco de 2 partes.
Cree un índice de subcadena en el valor hash: (7 suele ser una buena longitud)
create index methods_idx_hash_substring ON methods(substring(hash,1,7))
Haga que sus búsquedas / combinaciones incluyan una coincidencia de subcadena, por lo que se sugiere que el planificador de consultas use el índice:
antiguo:
WHERE hash = :kwarg
nuevo:
WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))
También debe tener un índice en bruto
hash
también.el resultado (generalmente) es que el planificador consultará primero el índice de subcadena y eliminará la mayoría de las filas. luego hace coincidir el hash completo de 32 caracteres con el índice (o tabla) correspondiente. Este enfoque ha reducido las consultas de 800ms a 4 para mí.
fuente