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.