Postgres múltiples se une a la consulta lenta, cómo almacenar el registro infantil predeterminado

dba.stackexchange https://dba.stackexchange.com/questions/15082

Pregunta

Tengo una pequeña base de datos en Postgres, alrededor de 10,000 registros que almacenan clientes de la compañía.
Tengo una consulta de rendimiento "lenta" (aproximadamente medio segundo) que se realiza con mucha frecuencia, y mi jefe quiere que lo mejore.

En primer lugar, mi código:

select customer_alias.id, customer_alias.name, site.address, phone.phonenumber
from customer_alias
join customer on customer_alias.customer_id = customer.id
left join site on customer.default_site_id = site.id
left join contact_phonenumbers as phone on site.default_phonenumber = phone.id

(Editado left join customer a join customer)

Lo que salta para mí es que estoy realizando una unión para customer Aunque no estoy seleccionando nada de ese registro. Actualmente tengo que unirme a él para obtener el default_site_id, una clave extranjera para el site mesa.

Cada cliente puede tener múltiples sitios, pero solo uno debe mostrarse en esta lista (se debe abrir un cliente para ver todos los sitios). Entonces, mi pregunta es, si no puedo optimizar la consulta, ¿hay una forma diferente de almacenar un sitio predeterminado para un cliente en particular? Lo mismo ocurre con el número de fono predeterminado

Un cliente puede tener muchos sitios, pero un sitio tiene solo un cliente (muchos a uno).


EXPLAIN devoluciones:

Hash Join  (cost=522.72..943.76 rows=5018 width=53)
  Hash Cond: (customer.id = customer_alias.customer_id)
  ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32)
        Hash Cond: (site.id = customer.default_site_id)
        ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32)
              Hash Cond: (phone.id = site.default_phonenumber)
              ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17)
              ->  Hash  (cost=122.18..122.18 rows=5018 width=23)
                    ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23)
        ->  Hash  (cost=124.18..124.18 rows=5018 width=8)
              ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8)
  ->  Hash  (cost=88.18..88.18 rows=5018 width=29)
        ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29)

EXPLAIN ANALYZE devoluciones:

Hash Join  (cost=522.72..943.76 rows=5018 width=53) (actual time=12.457..26.655 rows=5018 loops=1)
  Hash Cond: (customer.id = customer_alias.customer_id)
  ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32) (actual time=8.589..18.796 rows=5018 loops=1)
        Hash Cond: (site.id = customer.default_site_id)
        ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32) (actual time=4.499..11.067 rows=5018 loops=1)
              Hash Cond: (phone.id = site.default_phonenumber)
              ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17) (actual time=0.007..1.581 rows=6970 loops=1)
              ->  Hash  (cost=122.18..122.18 rows=5018 width=23) (actual time=4.465..4.465 rows=5018 loops=1)
                    Buckets: 1024  Batches: 1  Memory Usage: 277kB
                    ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23) (actual time=0.007..2.383 rows=5018 loops=1)
        ->  Hash  (cost=124.18..124.18 rows=5018 width=8) (actual time=4.072..4.072 rows=5018 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 197kB
              ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8) (actual time=0.009..2.270 rows=5018 loops=1)
  ->  Hash  (cost=88.18..88.18 rows=5018 width=29) (actual time=3.855..3.855 rows=5018 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 309kB
        ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29) (actual time=0.008..1.664 rows=5018 loops=1)
Total runtime: 27.290 ms"

Estructuras de mesa

/* ---------------------------------------------------------------------------
 * Table: contacts.customer
 * -------------------------------------------------------------------------*/
CREATE TABLE contacts.customer (
    id                  SERIAL NOT NULL,
    name                integer,   -- Foreign key to contacts.customer_alias
    default_site_id     integer,   -- Foreign key to contacts.site
    -- 12 other fields unrelated to query
    CONSTRAINT customer_pkey PRIMARY KEY (id )
    CONSTRAINT default_site_id FOREIGN KEY (default_site_id)
    REFERENCES contacts.site (id) MATCH SIMPLE
    ON UPDATE NO ACTION ON DELETE NO ACTION;
);

-- circular foreign key to customer_alias added later

-- Indexed: id, name, default_site_id (btree)


/*----------------------------------------------------------------------------
 * Table: contacts.customer_alias
 *--------------------------------------------------------------------------*/
CREATE TABLE contacts.customer_alias (
    id              SERIAL NOT NULL,
    customer_id     integer,
    name            text,
    -- 5 other fields (not used in query)
    CONSTRAINT customer_alias_pkey PRIMARY KEY (id ),
    CONSTRAINT customer_alias_customer_id_fkey FOREIGN KEY (customer_id)
         REFERENCES contacts.customer (id) MATCH SIMPLE
         ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED
)
-- indexed: id, customer_id (btree)

-- Customer_alias foreign key
ALTER TABLE contacts.customer
    ADD CONSTRAINT customer_name_fkey FOREIGN KEY (name)
         REFERENCES contacts.customer_alias (id) MATCH SIMPLE
         ON UPDATE NO ACTION ON DELETE NO ACTION;


/* ---------------------------------------------------------------------------
 * Table: contacts.site
 * -------------------------------------------------------------------------*/
CREATE TABLE contacts.site (
    id                  SERIAL NOT NULL,
    customer_id         integer,
    address             text,
    default_contact_id  integer,
    default_phonenumber integer,
    -- 9 other unrelated fields
    CONSTRAINT site_pkey PRIMARY KEY (id ),
    CONSTRAINT site_customer_id_fkey FOREIGN KEY (customer_id)
         REFERENCES contacts.customer (id) MATCH SIMPLE
         ON UPDATE NO ACTION ON DELETE NO ACTION
)
indexed: id, customer_id, default_contact_id, default_phonenumber ( btree)


/* ---------------------------------------------------------------------------
 * Table: contacts.contact_phonenumbers
 * -------------------------------------------------------------------------*/
CREATE TABLE contacts.contact_phonenumbers (
    id              SERIAL NOT NULL,
    site_id         integer,
    phonenumber     text,
    -- 4 other unrelated fields
    CONSTRAINT contact_phonenumbers_pkey PRIMARY KEY (id )
)
-- indexed: id, site_id (btree)

Si ejecuto el lado del cliente de consulta, a través de ODBC, se necesitan entre 450 y 500 milisegundos. Si ejecuto la consulta en Pgadmin III, establece que la consulta toma aproximadamente 250 milisegundos, aunque a veces lleva 60-100 ms (que es lo que estoy apuntando).
Actualmente no tengo acceso SSH al servidor, por lo que no puedo ejecutarlo directamente.

Solo veo alrededor de 100 de estas filas en cualquier momento en la pantalla, ¿es posible recuperar solo las filas relevantes? Intenté limitar el resultado por ejemplo LIMIT 100, OFFSET 2345, pero eso realiza una nueva búsqueda cada vez.

¡Muchas gracias por la ayuda hasta ahora!

¿Fue útil?

Solución

Usted escribe:

Cada cliente puede tener múltiples sitios, pero solo uno debe mostrarse en esta lista.

Sin embargo, su consulta recupera todas las filas. Ese sería un punto para optimizar. Pero tampoco defines que site es ser elegido.

De cualquier manera, no importa mucho aquí. Su EXPLAIN muestra solo 5026 filas para el site escanear (5018 para el customer escanear). Por lo tanto, casi ningún cliente tiene más de un sitio. Acaso tú ANALYZE tus tablas antes de correr EXPLAIN?

De los números que veo en tu EXPLAIN, Los índices no te darán nada para esta consulta. Los escaneos de tabla secuenciales serán la forma más rápida posible. Sin embargo, medio segundo es bastante lento para 5000 filas. Quizás su base de datos necesite algo ajuste de rendimiento general?

¿Quizás la consulta en sí es más rápida, pero "medio segundo" incluye la transferencia de red? Explicar analizar nos diría más.

Si esta consulta es su cuello de botella, le sugiero que implementa un vista materializada.


Después de proporcionar más información, encuentro que mi diagnóstico se mantiene prácticamente.

La consulta en sí necesita 27 ms. No hay mucho problema allí. "Medio segundo" era el tipo de malentendido que sospechaba. La parte lenta es la transferencia de red (más codificación / decodificación de SSH, posiblemente renderizado). Solo debes recuperar 100 filas, eso resolver la mayor parte, incluso si significa ejecutar toda la consulta cada vez.

Si sigue la ruta con una vista materializada como propuse que podría agregar un número de serie sin huecos a la tabla más índice en él - agregando una columna row_number() OVER (<your sort citeria here>) AS mv_id.

Entonces puedes consultar:

SELECT *
FROM   materialized_view
WHERE  mv_id >= 2700
AND    mv_id <  2800;

Esto funcionará muy rápido. LIMIT / OFFSET No se puede competir, eso necesita calcular toda la tabla antes de que pueda ordenar y elegir 100 filas.


Tiempo de pgadmin

Cuando ejecuta una consulta desde la herramienta de consulta, el panel de mensajes muestra algo como:

Total query runtime: 62 ms.

Y la línea de estado muestra el mismo tiempo. Cito pgadmin ayuda sobre eso:

La línea de estado mostrará cuánto tiempo tardó en completarse la última consulta. Si se devolvió un conjunto de datos, no solo se muestra el tiempo transcurrido para la ejecución del servidor, sino también el tiempo para recuperar los datos del servidor a la página de salida de datos.

Si desea ver la hora en el servidor, debe usar SQL EXPLAIN ANALYZE o el incorporado Shift + F7atajo de teclado o Query -> Explain analyze. Luego, en la parte inferior de la salida de explicación, obtienes algo como esto:

Total runtime: 0.269 ms
Licenciado bajo: CC-BY-SA con atribución
No afiliado a dba.stackexchange
scroll top