2015-08-27 10 views
6

Questa domanda è relativa a this. Ho una tabella che contiene i valori di alimentazione per i dispositivi e ho bisogno di calcolare il consumo di energia per un determinato intervallo di tempo e restituire 10 dispositivi più consumanti. Ho generato 192 dispositivi e 7742208 record di misurazione (40324 per ciascuno). Questo è all'incirca quanto i dispositivi di registrazione produrranno in un mese.Come ottimizzare la query SQL con le funzioni della finestra

Per questa quantità di dati la mia query corrente richiede più di 40 secondi per l'esecuzione, il che è eccessivo perché l'intervallo di tempo e la quantità di dispositivi e misurazioni potrebbero essere molto più elevati. Dovrei provare a risolvere questo problema con un approccio diverso da lag() OVER PARTITION e quali altre ottimizzazioni possono essere fatte? Apprezzerei molto i suggerimenti con esempi di codice.

PostgreSQL versione 9.4

query con valori di esempio:

SELECT 
    t.device_id, 
    sum(len_y*(extract(epoch from len_x))) AS total_consumption 
FROM (
    SELECT 
     m.id, 
     m.device_id, 
     m.power_total, 
     m.created_at, 
     m.power_total+lag(m.power_total) OVER (
     PARTITION BY device_id 
     ORDER BY m.created_at 
    ) AS len_y, 
     m.created_at-lag(m.created_at) OVER (
     PARTITION BY device_id 
     ORDER BY m.created_at 
    ) AS len_x 
    FROM 
     measurements AS m 
    WHERE m.created_at BETWEEN '2015-07-30 13:05:24.403552+00'::timestamp 
    AND '2015-08-27 12:34:59.826837+00'::timestamp 
) AS t 
GROUP BY t.device_id 
ORDER BY total_consumption 
DESC LIMIT 10; 

informazioni Classifica:

Column |   Type   |       Modifiers 
--------------+--------------------------+---------------------------------------------------------- 
id   | integer     | not null default nextval('measurements_id_seq'::regclass) 
created_at | timestamp with time zone | default timezone('utc'::text, now()) 
power_total | real      | 
device_id | integer     | not null 
Indexes: 
    "measurements_pkey" PRIMARY KEY, btree (id) 
    "measurements_device_id_idx" btree (device_id) 
    "measurements_created_at_idx" btree (created_at) 
Foreign-key constraints: 
    "measurements_device_id_fkey" FOREIGN KEY (device_id) REFERENCES devices(id) 

piano di query:

Limit (cost=1317403.25..1317403.27 rows=10 width=24) (actual time=41077.091..41077.094 rows=10 loops=1) 
-> Sort (cost=1317403.25..1317403.73 rows=192 width=24) (actual time=41077.089..41077.092 rows=10 loops=1) 
Sort Key: (sum((((m.power_total + lag(m.power_total) OVER (?))) * date_part('epoch'::text, ((m.created_at - lag(m.created_at) OVER (?))))))) 
Sort Method: top-N heapsort Memory: 25kB 
-> GroupAggregate (cost=1041700.67..1317399.10 rows=192 width=24) (actual time=25361.013..41076.562 rows=192 loops=1) 
Group Key: m.device_id 
-> WindowAgg (cost=1041700.67..1201314.44 rows=5804137 width=20) (actual time=25291.797..37839.727 rows=7742208 loops=1) 
-> Sort (cost=1041700.67..1056211.02 rows=5804137 width=20) (actual time=25291.746..30699.993 rows=7742208 loops=1) 
Sort Key: m.device_id, m.created_at 
Sort Method: external merge Disk: 257344kB 
-> Seq Scan on measurements m (cost=0.00..151582.05 rows=5804137 width=20) (actual time=0.333..5112.851 rows=7742208 loops=1) 
Filter: ((created_at >= '2015-07-30 13:05:24.403552'::timestamp without time zone) AND (created_at <= '2015-08-27 12:34:59.826837'::timestamp without time zone)) 

Planning time: 0.351 ms 
Execution time: 41114.883 ms 

query per generare tabella di prova e dati:

CREATE TABLE measurements (
    id   serial primary key, 
    device_id integer, 
    power_total real, 
    created_at timestamp 
); 

INSERT INTO measurements(
    device_id, 
    created_at, 
    power_total 
) 
SELECT 
    device_id, 
    now() + (i * interval '1 minute'), 
    random()*(50-1)+1 
FROM (
    SELECT 
    DISTINCT(device_id), 
    generate_series(0,10) AS i 
FROM (
    SELECT 
    generate_series(1,5) AS device_id 
) AS dev_ids 
) AS gen_table; 
+0

Che ne dici di un indice composito su (id_dispositivo, creato_at)? BTW, IMHO dovresti dividere 'm.power_total + lag (m.power_total)' di due prima dell'uso. (O prendi semplicemente la media) – joop

+1

+1 La migliore domanda che ho visto da molto tempo. Campione molto ben scritto e corretto. Creo il campione db in un secondo. Ora quali valori devo inserire nella serie per generare un db simile alla tua dimensione attuale? –

+2

La condizione 'where' non rimuove alcuna riga. È destinato? L'ordinamento viene fatto anche su disco: 'external merge Disk: 257344kB' che richiede abbastanza tempo (il piano di esecuzione ha perso l'indentazione, quindi è un po 'difficile da leggere).Se aumenti il ​​'work_mem' per la sessione fino a quando l'ordinamento è fatto in memoria, dovresti vedere prestazioni migliori. –

risposta

1

vorrei provare a spostare una parte dei calcoli nelle PHA e di inserimento di riga.

aggiungere una nuova colonna:

alter table measurements add consumption real; 

aggiornare la colonna:

with m1 as (
    select 
     id, power_total, created_at, 
     lag(power_total) over (partition by device_id order by created_at) prev_power_total, 
     lag(created_at) over (partition by device_id order by created_at) prev_created_at 
    from measurements 
    ) 
update measurements m2 
set consumption = 
    (m1.power_total+ m1.prev_power_total)* 
    extract(epoch from m1.created_at- m1.prev_created_at) 
from m1 
where m2.id = m1.id; 

creare un trigger:

create or replace function before_insert_on_measurements() 
returns trigger language plpgsql 
as $$ 
declare 
    rec record; 
begin 
    select power_total, created_at into rec 
    from measurements 
    where device_id = new.device_id 
    order by created_at desc 
    limit 1; 
    new.consumption:= 
     (new.power_total+ rec.power_total)* 
     extract(epoch from new.created_at- rec.created_at); 
    return new; 
end $$; 

create trigger before_insert_on_measurements 
before insert on measurements 
for each row execute procedure before_insert_on_measurements(); 

La query:

select device_id, sum(consumption) total_consumption 
from measurements 
-- where conditions 
group by 1 
order by 1 
+0

Grazie! Utilizzando questo approccio sono stato in grado di ottenere il tempo di esecuzione di 9 secondi. A proposito, dovrebbe essere ordinato dalla 2a colonna. =) –

0

Penso che il tuo problema sia diverso.

ho creare i dati di esempio con 8 M righe (200 dispositivi, 40000 misure)

e di risposta è molto veloce (2 secondi)

Postgres 9.3 - iCore 5/3.2 MHz/8GB/SATA HDD/Windows 7
non ho creato indice ancora (si dimentica che una parte nello script di setup)

enter image description here

+0

Ti sei assicurato che la condizione 'where' non escluda nessuno degli 8 milioni di righe? Perché questo è ciò che accade nella query originale. Se eseguo il campione con 8 milioni di righe, ci vogliono circa 12 secondi (che è ancora più veloce del tempo originale) –

+0

@a_horse_with_no_name Mi sono limitato a copiare selezionare dalla domanda OP. Verificherà di nuovo. –

+0

@a_horse_with_no_name Perché dici Non filtra nessuna riga? Selezione interna con 'WHERE m.created_at BETWEEN '2015-07-30 13: 05: 24.403552 + 00' :: timestamp E '2015-08-27 12: 34: 59.826837 + 00' :: timestamp' porta 5800 righe, senza che 'where' la selezione porti i record 8M e duri 300 secondi. –

Problemi correlati