2013-06-28 10 views
10

Sto cercando di capire il modo migliore, (probabilmente non importa in questo caso) per trovare le righe di una tabella, in base all'esistenza di una bandiera e un ID relazionale di fila in un altro tavolo.SQLite3 query optimization join vs subselect

qui sono gli schemi:

CREATE TABLE files (
id INTEGER PRIMARY KEY, 
dirty INTEGER NOT NULL); 

    CREATE TABLE resume_points (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 
scan_file_id INTEGER NOT NULL); 

sto utilizzando SQLite3

ci file tabella sarà molto grande, le righe 10K-5M genere. i resume_points saranno piccoli < 10K con solo 1-2 scan_file_id distinti l'

quindi il mio primo pensiero è stato:

select distinct files.* from resume_points inner join files 
on resume_points.scan_file_id=files.id where files.dirty = 1; 

un collega ha suggerito girando il join in giro:

select distinct files.* from files inner join resume_points 
on files.id=resume_points.scan_file_id where files.dirty = 1; 

poi Ho pensato che poiché sappiamo che il numero di distinti scan_file_id sarà così piccolo, forse una sottoselezione sarebbe ottimale (in questo raro caso):

select * from files where id in (select distinct scan_file_id from resume_points); 

le uscite explain avevano le seguenti righe: 42, 42 e 48 rispettivamente.

+1

Questo dipende dei dati e l'hardware. Devi misurare questo te stesso. –

+1

Hai perso e files.dirty = 1 sull'ultima query – eglasius

risposta

11

TL; DR: Il miglior interrogazione e l'indice è:

create index uniqueFiles on resume_points (scan_file_id); 
select * from (select distinct scan_file_id from resume_points) d join files on d.scan_file_id = files.id and files.dirty = 1; 

Dato che tipicamente lavoro con SQL Server, all'inizio ho pensato che sicuramente Query Optimizer avrebbe trovato il piano di esecuzione ottimale per una semplice ricerca del tipo indipendentemente dal modo in cui si scrivono queste istruzioni SQL equivalenti. Così ho scaricato SQLite e ho iniziato a giocare. Con mia grande sorpresa, c'era un'enorme differenza nelle prestazioni.

Ecco il codice di impostazione:

CREATE TABLE files (
id INTEGER PRIMARY KEY autoincrement, 
dirty INTEGER NOT NULL); 

CREATE TABLE resume_points (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 
scan_file_id INTEGER NOT NULL); 

insert into files (dirty) values (0); 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 
insert into files (dirty) select (case when random() < 0 then 1 else 0 end) from files; 

insert into resume_points (scan_file_id) select (select abs(random() % 8000000)) from files limit 5000; 

insert into resume_points (scan_file_id) select (select abs(random() % 8000000)) from files limit 5000; 

ho considerato due indici:

create index dirtyFiles on files (dirty, id); 
create index uniqueFiles on resume_points (scan_file_id); 
create index fileLookup on files (id); 

sotto ci sono le domande che ho provato ed i tempi di esecuzione sul mio portatile i5. La dimensione del file del database è solo di circa 200 MB poiché non ha altri dati.

select distinct files.* from resume_points inner join files on resume_points.scan_file_id=files.id where files.dirty = 1; 
4.3 - 4.5ms with and without index 

select distinct files.* from files inner join resume_points on files.id=resume_points.scan_file_id where files.dirty = 1; 
4.4 - 4.7ms with and without index 

select * from (select distinct scan_file_id from resume_points) d join files on d.scan_file_id = files.id and files.dirty = 1; 
2.0 - 2.5ms with uniqueFiles 
2.6-2.9ms without uniqueFiles 

select * from files where id in (select distinct scan_file_id from resume_points) and dirty = 1; 
2.1 - 2.5ms with uniqueFiles 
2.6-3ms without uniqueFiles 

SELECT f.* FROM resume_points rp INNER JOIN files f on rp.scan_file_id = f.id 
WHERE f.dirty = 1 GROUP BY f.id 
4500 - 6190 ms with uniqueFiles 
8.8-9.5 ms without uniqueFiles 
    14000 ms with uniqueFiles and fileLookup 

select * from files where exists (
select * from resume_points where files.id = resume_points.scan_file_id) and dirty = 1; 
8400 ms with uniqueFiles 
7400 ms without uniqueFiles 

Sembra che il query optimizer di SQLite non sia affatto avanzato. Le migliori query prima riducono resume_points a un piccolo numero di righe (due nel caso di test. L'OP ha detto che sarebbe 1-2), quindi cerca il file per vedere se è sporco o no. L'indice dirtyFiles non ha fatto molta differenza per nessuno dei file. Penso che possa essere dovuto al modo in cui i dati sono disposti nelle tabelle di test. Potrebbe fare la differenza nelle tabelle di produzione. Tuttavia, la differenza non è troppo grande in quanto ci saranno meno di una manciata di ricerche. uniqueFiles fa la differenza poiché può ridurre 10000 righe di resume_points a 2 righe senza eseguire la scansione nella maggior parte di esse. fileLookup ha reso alcune query leggermente più veloci, ma non abbastanza per modificare in modo significativo i risultati. In particolare ha reso il gruppo molto lento. In conclusione, riduci il risultato in anticipo per fare le maggiori differenze.

+1

Hai eseguito il comando Analizza dopo aver creato gli indici? – Giorgi

1

Dal files.id è la chiave primaria, provare GROUP ing BY questo campo piuttosto che il controllo DISTINCT files.*

SELECT f.* 
FROM resume_points rp 
INNER JOIN files f on rp.scan_file_id = f.id 
WHERE f.dirty = 1 
GROUP BY f.id 

Un'altra opzione da considerare per le prestazioni è l'aggiunta di un indice per resume_points.scan_file_id.

CREATE INDEX index_resume_points_scan_file_id ON resume_points (scan_file_id) 
1

Si potrebbe provare exists, che non produrrà alcun duplicato files:

select * from files 
where exists (
    select * from resume_points 
    where files.id = resume_points.scan_file_id 
) 
and dirty = 1; 

Naturalmente potrebbe aiuto per avere gli indici corretti:

files.dirty 
resume_points.scan_file_id 

se un indice di è utile dipenderà dai tuoi dati.

0

Se la tabella "resume_points" avrà solo uno o due numeri identificativi di file distinti, sembra che occorrano solo una o due righe e sembra che sia necessario scan_file_id come chiave primaria. Quella tabella ha solo due colonne e il numero di identificazione non ha senso.

E se è il caso, non è necessario uno dei numeri ID.

pragma foreign_keys = on; 
CREATE TABLE resume_points (
    scan_file_id integer primary key 
); 

CREATE TABLE files (
    scan_file_id integer not null references resume_points (scan_file_id), 
    dirty INTEGER NOT NULL, 
    primary key (scan_file_id, dirty) 
); 

E ora non è necessario il join. Basta interrogare la tabella "file".

1

Penso che jtseng abbia dato la soluzione.

select * from (select distinct scan_file_id from resume_points) d 
join files on d.scan_file_id = files.id and files.dirty = 1 

In sostanza è la stessa cosa che hai postato come ultima opzione:

select * from files where id in (select distinct scan_file_id from resume_points) and dirty = 1; 

E 'allucinante si deve evitare una scansione completa della tabella/join.

Così in un primo momento è necessario il vostro ID di 1-2 distinte:

select distinct scan_file_id from resume_points 

dopo che solo i tuoi 1-2 file devono essere uniti d'altro tavolo invece di tutti 10K, che dà l'ottimizzazione delle prestazioni.

se avete bisogno di questa affermazione più volte, vorrei metterlo in una vista. la vista non cambierà le prestazioni ma sembra più pulita/più facile da leggere.

di controllare anche la documentazione di ottimizzazione delle query: http://www.sqlite.org/optoverview.html