Benchmark
Testen der interessantesten Kandidaten mit Postgres 9.4 und 9.5 mit einer halbwegs realistischen Tabelle von 200.000 Zeilen in purchases
und 10.000 unterschiedlichencustomer_id
( durchschnittlich 20 Zeilen pro Kunde ).
Für Postgres 9.5 habe ich einen zweiten Test mit effektiv 86446 verschiedenen Kunden durchgeführt. Siehe unten ( durchschnittlich 2,3 Zeilen pro Kunde ).
Installieren
Haupttisch
CREATE TABLE purchases (
id serial
, customer_id int -- REFERENCES customer
, total int -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);
Ich verwende eine serial
(PK-Einschränkung unten hinzugefügt) und eine Ganzzahl, customer_id
da dies ein typischeres Setup ist. Wird auch hinzugefügt some_column
, um normalerweise mehr Spalten auszugleichen.
Dummy-Daten, PK, Index - eine typische Tabelle enthält auch einige tote Tupel:
INSERT INTO purchases (customer_id, total, some_column) -- insert 200k rows
SELECT (random() * 10000)::int AS customer_id -- 10k customers
, (random() * random() * 100000)::int AS total
, 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM generate_series(1,200000) g;
ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);
DELETE FROM purchases WHERE random() > 0.9; -- some dead rows
INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int AS customer_id -- 10k customers
, (random() * random() * 100000)::int AS total
, 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM generate_series(1,20000) g; -- add 20k to make it ~ 200k
CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);
VACUUM ANALYZE purchases;
customer
Tabelle - für übergeordnete Abfrage
CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM purchases
GROUP BY 1
ORDER BY 1;
ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);
VACUUM ANALYZE customer;
In meinem zweiten Test für 9.5 habe ich das gleiche Setup verwendet, aber mit random() * 100000
zu generieren customer_id
, um nur wenige Zeilen pro zu erhalten customer_id
.
Objektgrößen für Tabelle purchases
Mit dieser Abfrage generiert .
what | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
core_relation_size | 20496384 | 20 MB | 102
visibility_map | 0 | 0 bytes | 0
free_space_map | 24576 | 24 kB | 0
table_size_incl_toast | 20529152 | 20 MB | 102
indexes_size | 10977280 | 10 MB | 54
total_size_incl_toast_and_indexes | 31506432 | 30 MB | 157
live_rows_in_text_representation | 13729802 | 13 MB | 68
------------------------------ | | |
row_count | 200045 | |
live_tuples | 200045 | |
dead_tuples | 19955 | |
Abfragen
WITH cte AS (
SELECT id, customer_id, total
, row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
FROM purchases
)
SELECT id, customer_id, total
FROM cte
WHERE rn = 1;
2. row_number()
in Unterabfrage (meine Optimierung)
SELECT id, customer_id, total
FROM (
SELECT id, customer_id, total
, row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
FROM purchases
) sub
WHERE rn = 1;
SELECT DISTINCT ON (customer_id)
id, customer_id, total
FROM purchases
ORDER BY customer_id, total DESC, id;
4. rCTE mit LATERAL
Unterabfrage ( siehe hier )
WITH RECURSIVE cte AS (
( -- parentheses required
SELECT id, customer_id, total
FROM purchases
ORDER BY customer_id, total DESC
LIMIT 1
)
UNION ALL
SELECT u.*
FROM cte c
, LATERAL (
SELECT id, customer_id, total
FROM purchases
WHERE customer_id > c.customer_id -- lateral reference
ORDER BY customer_id, total DESC
LIMIT 1
) u
)
SELECT id, customer_id, total
FROM cte
ORDER BY customer_id;
5. customer
Tabelle mit LATERAL
( siehe hier )
SELECT l.*
FROM customer c
, LATERAL (
SELECT id, customer_id, total
FROM purchases
WHERE customer_id = c.customer_id -- lateral reference
ORDER BY total DESC
LIMIT 1
) l;
SELECT (array_agg(id ORDER BY total DESC))[1] AS id
, customer_id
, max(total) AS total
FROM purchases
GROUP BY customer_id;
Ergebnisse
Ausführungszeit für die oben genannten Abfragen mit EXPLAIN ANALYZE
(und allen Optionen deaktiviert ), am besten aus 5 Läufen .
Alle Abfragen verwendeten eine Nur - Indexsuche auf purchases2_3c_idx
(unter anderen Stufen). Einige von ihnen nur für die kleinere Größe des Index, andere effektiver.
A. Postgres 9.4 mit 200k Reihen und ~ 20 pro customer_id
1. 273.274 ms
2. 194.572 ms
3. 111.067 ms
4. 92.922 ms
5. 37.679 ms -- winner
6. 189.495 ms
B. Das gleiche gilt für Postgres 9.5
1. 288.006 ms
2. 223.032 ms
3. 107.074 ms
4. 78.032 ms
5. 33.944 ms -- winner
6. 211.540 ms
C. Wie B., jedoch mit ~ 2,3 Zeilen pro customer_id
1. 381.573 ms
2. 311.976 ms
3. 124.074 ms -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms
Verwandte Benchmarks
Hier ist ein neuer Test von "ogr" mit 10 Millionen Zeilen und 60.000 einzigartigen "Kunden" auf Postgres 11.5 (Stand: September 2019). Die Ergebnisse stimmen immer noch mit dem überein, was wir bisher gesehen haben:
Ursprünglicher (veralteter) Benchmark von 2011
Ich habe drei Tests mit PostgreSQL 9.1 in einer realen Tabelle mit 65579 Zeilen und einspaltigen btree-Indizes für jede der drei beteiligten Spalten durchgeführt und die beste Ausführungszeit von 5 Läufen genommen.
Vergleich der ersten Abfrage ( A
) von @OMGPonies mit der obigen DISTINCT ON
Lösung ( B
):
Wählen Sie die gesamte Tabelle aus. In diesem Fall werden 5958 Zeilen angezeigt.
A: 567.218 ms
B: 386.673 ms
Verwenden Sie die Bedingung, WHERE customer BETWEEN x AND y
die zu 1000 Zeilen führt.
A: 249.136 ms
B: 55.111 ms
Wählen Sie einen einzelnen Kunden mit WHERE customer = x
.
A: 0.143 ms
B: 0.072 ms
Der gleiche Test wurde mit dem in der anderen Antwort beschriebenen Index wiederholt
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
1A: 277.953 ms
1B: 193.547 ms
2A: 249.796 ms -- special index not used
2B: 28.679 ms
3A: 0.120 ms
3B: 0.048 ms
MAX(total)
?