PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Ordenação de consulta lenta por uma coluna em uma tabela unida

Caso de teste


PostgreSQL 9.1. Banco de dados de teste com recursos limitados, mas suficiente para este pequeno caso. A localidade para agrupamento será relevante:
SHOW LC_COLLATE;

 de_AT.UTF-8

Etapa 1) Reconstruir o ambiente de teste bruto
-- DROP TABLE x;
CREATE SCHEMA x;  -- test schema

-- DROP TABLE x.django_site;
CREATE TABLE x.django_site (
id serial primary key
,domain character varying(100) not null
,int_col int not null
);
INSERT INTO x.django_site values (1,'www.testsite.com/foodir/', 3);

-- DROP TABLE x.product;
CREATE TABLE x.product (
 id serial primary key
,site_id integer not null
,name character varying(255) not null
,slug character varying(255) not null
,sku character varying(255) 
,ordering integer not null
,active boolean not null
);

INSERT INTO x.product (site_id, name, slug, sku, ordering, active)
SELECT 1
    ,repeat(chr((random() * 255)::int + 32), (random()*255)::int)
    ,repeat(chr((random() * 255)::int + 32), (random()*255)::int)
    ,repeat(chr((random() * 255)::int + 32), (random()*255)::int)
    ,i -- ordering in sequence
    ,NOT (random()* 0.5174346569119122)::int::bool
FROM generate_series(1, 17540) AS x(i);
-- SELECT ((591::float8 / 17540)* 0.5) / (1 - (591::float8 / 17540))
-- = 0.5174346569119122

CREATE INDEX product_site_id on x.product(site_id);

Etapa 2) ANALISAR
    ANALYZE x.product;
    ANALYZE x.django_site;

Etapa 3) Reordenar POR random()
-- DROP TABLE x.p;
CREATE TABLE x.p AS
SELECT *
FROM   x.product
ORDER  BY random();

ANALYZE x.p;

Resultados

EXPLAIN ANALYZE
    SELECT p.*
    FROM   x.p
    JOIN   x.django_site d ON (p.site_id = d.id)
    WHERE  p.active
    AND    p.site_id = 1
--    ORDER  BY d.domain, p.ordering, p.name
--    ORDER  BY p.ordering, p.name
--    ORDER  BY d.id, p.ordering, p.name
--    ORDER  BY d.int_col, p.ordering, p.name
--    ORDER  BY p.name COLLATE "C"
--    ORDER  BY d.domain COLLATE "C", p.ordering, p.name -- dvd's final solution

1) Pré ANALYZE (-> varredura de índice de bitmap)
2) Pós ANALYZE (-> seq scan)
3) Reordenar por random(), ANALYZE
ORDER  BY d.domain, p.ordering, p.name

1) Tempo de execução total:1253,543 ms
2) Tempo de execução total:1250,351 ms
3) Tempo de execução total:1283,111 ms
ORDER  BY p.ordering, p.name

1) Tempo de execução total:177,266 ms
2) Tempo de execução total:174,556 ms
3) Tempo de execução total:177,797 ms
ORDER  BY d.id, p.ordering, p.name

1) Tempo de execução total:176,628 ms
2) Tempo de execução total:176,811 ms
3) Tempo de execução total:178,150 ms
O planejador obviamente considera isso d.id é funcionalmente dependente.
ORDER  BY d.int_col, p.ordering, p.name -- integer column in other table

1) Tempo de execução total:242,218 ms -- !!
2) Tempo de execução total:245,234 ms
3) Tempo de execução total:254,581 ms
O planejador obviamente sente falta desse d.int_col (NOT NULL) é tão funcionalmente dependente. Mas classificar por uma coluna inteira é barato.
ORDER  BY p.name -- varchar(255) in same table

1) Tempo de execução total:2259,171 ms -- !!
2) Tempo de execução total:2257,650 ms
3) Tempo de execução total:2258,282 ms
Classificando por um (longo) varchar ou texto coluna é cara...
ORDER  BY p.name COLLATE "C"

1) Tempo de execução total:327,516 ms -- !!
2) Tempo de execução total:325,103 ms
3) Tempo de execução total:327,206 ms
... mas não tão caro se feito sem localidade.

Com a localidade fora do caminho, classificando por um varchar coluna não é bem, mas quase tão rápido. Localidade "C" é efetivamente "sem localidade, apenas ordenar por valor de byte". Cito o manual:

Se você quiser que o sistema se comporte como se não tivesse suporte de localidade, use o nome de localidade especial C, ou equivalentemente POSIX.

Juntando tudo, @dvd escolheu:
ORDER  BY d.domain COLLATE "C", p.ordering, p.name

...3) Tempo de execução total:275,854 ms
Isso deve servir.