Caso di prova
PostgreSQL 9.1. Testare il database con risorse limitate, ma abbastanza per questo piccolo caso. Le impostazioni locali per le regole di confronto saranno rilevanti:
SHOW LC_COLLATE;
de_AT.UTF-8
Passaggio 1) Ricostruisci l'ambiente di test grezzo
-- 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);
Passaggio 2) ANALIZZA
ANALYZE x.product;
ANALYZE x.django_site;
Passaggio 3) Riordina PER casuale()
-- DROP TABLE x.p;
CREATE TABLE x.p AS
SELECT *
FROM x.product
ORDER BY random();
ANALYZE x.p;
Risultati
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) Pre ANALIZZA (-> scansione dell'indice bitmap)
2) Post ANALIZZA (-> scansione sequenziale)
3) Riordina per random(), ANALIZZA
ORDER BY d.domain, p.ordering, p.name
1) Tempo di esecuzione totale:1253,543 ms
2) Tempo di esecuzione totale:1250,351 ms
3) Tempo di esecuzione totale:1283,111 ms
ORDER BY p.ordering, p.name
1) Durata totale:177.266 ms
2) Durata totale:174.556 ms
3) Durata totale:177.797 ms
ORDER BY d.id, p.ordering, p.name
1) Durata totale:176.628 ms
2) Durata totale:176.811 ms
3) Durata totale:178.150 ms
Il pianificatore ovviamente tiene conto di questo d.id
è funzionalmente dipendente.
ORDER BY d.int_col, p.ordering, p.name -- integer column in other table
1) Durata totale:242.218 ms -- !!
2) Durata totale:245.234 ms
3) Durata totale:254.581 ms
Al pianificatore ovviamente manca quel d.int_col
(NON NULL) è altrettanto dipendente dal punto di vista funzionale. Ma l'ordinamento in base a una colonna intera è economico.
ORDER BY p.name -- varchar(255) in same table
1) Durata totale:2259,171 ms -- !!
2) Durata totale:2257,650 ms
3) Durata totale:2258,282 ms
Ordinamento per varchar
(lungo). o text
la colonna è costosa...
ORDER BY p.name COLLATE "C"
1) Durata totale:327.516 ms -- !!
2) Durata totale:325.103 ms
3) Durata totale:327.206 ms
... ma non così costoso se fatto senza locale.
Con le impostazioni locali fuori mano, ordinando per varchar
la colonna non è del tutto ma quasi altrettanto veloce. Locale "C"
è effettivamente "nessuna localizzazione, solo ordine per valore byte". Cito il manuale:
Se vuoi che il sistema si comporti come se non avesse il supporto per la localizzazione, usa il nome della locale speciale C, o equivalentemente POSIX.
Mettendo tutto insieme, @dvd ha scelto:
ORDER BY d.domain COLLATE "C", p.ordering, p.name
...3) Durata totale:275,854 ms
Dovrebbe andare.