2013-06-28 10 views
10

Próbuję znaleźć najlepszy sposób (prawdopodobnie nie ma znaczenia w tym przypadku), aby znaleźć wiersze jednej tabeli, na podstawie istnienia flagi i identyfikatora relacyjnego z rzędu w innej tabeli.Optymalizacja zapytań SQLite3 join vs subelement

tutaj są schematy:

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); 

Używam SQLite3

tam pliki stół będzie bardzo duża, 10K-5M wiersze zwykle. się resume_points będzie mały < 10K z zaledwie 1-2 odrębne scan_file_id „s

więc moja pierwsza myśl była:

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

współpracownik zasugerował, obracając się wokół dołączyć:

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

następnie Pomyślałem, ponieważ wiemy, że liczba różnych scan_file_id będzie tak mała, być może podselekcja będzie optymalna (w tym rzadkim przypadku):

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

Wyjścia explain miały następujące wiersze: odpowiednio 42, 42 i 48.

+1

To zależy od danych i sprzętu. Musisz to zmierzyć samodzielnie. –

+1

Tęskniłeś i files.dirty = 1 przy ostatnim zapytaniu – eglasius

Odpowiedz

11

TL; DR: Najlepszy zapytań i indeks jest:

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; 

Ponieważ zazwyczaj pracują z SQL Server, na początku myślałem, że na pewno optymalizator zapytań będzie znalezienie optymalnego planu wykonania dla takiego prostego zapytania niezależnie od tego, w jaki sposób wypiszesz te równoważne instrukcje SQL. Więc ściągnąłem SQLite i zacząłem się bawić. Ku mojemu zdziwieniu, była ogromna różnica w wydajności.

Oto kod instalacyjny:

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; 

Rozważałem dwa indeksy:

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

Poniżej znajdują się pytania Próbowałem i czasy wykonania na moim laptopie i5. Rozmiar pliku bazy danych wynosi tylko około 200 MB, ponieważ nie ma żadnych innych danych.

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 

Wygląda na to, że optymalizator zapytań SQLite nie jest wcale zaawansowany. Najlepsze zapytania najpierw redukują punkty wznowienia do niewielkiej liczby wierszy (dwa w teście.Odp powiedzieli, że będzie 1-2), a następnie sprawdź plik, czy jest brudny czy nie. Indeks dirtyFiles nie zrobił dużej różnicy dla żadnego z plików. Myślę, że może tak być ze względu na sposób ułożenia danych w tabelach testowych. Może to spowodować różnicę w tabelach produkcyjnych. Różnica nie jest jednak zbyt duża, ponieważ będzie mniej niż garstka wyszukiwań. uniqueFiles robi różnicę, ponieważ może zmniejszyć 10000 wierszy punktów CVT do 2 wierszy bez skanowania przez większość z nich. fileLookup przyspieszyło nieco zapytania, ale nie na tyle, aby znacząco zmienić wyniki. Zwłaszcza to spowodowało, że grupa była bardzo powolna. Na zakończenie zmniejsz zestaw wyników wcześniej, aby uzyskać największe różnice.

+1

Czy uruchomiłeś narzędzie Analyze po utworzeniu indeksów? – Giorgi

1

Od files.id jest kluczem podstawowym, spróbuj GROUP ing BY to pole zamiast sprawdzania 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 

Inną opcją do rozważenia wydajność jest dodanie indeksu do resume_points.scan_file_id.

CREATE INDEX index_resume_points_scan_file_id ON resume_points (scan_file_id) 
1

Można spróbować exists, który nie przyniesie żadnych duplikat files:

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

Oczywiście to może help mieć odpowiednie indeksy:

files.dirty 
resume_points.scan_file_id 

czy indeks jest pomocna, zależy od twoich danych.

0

Jeśli tabela "resume_points" będzie miała tylko jeden lub dwa różne numery identyfikatorów plików, wydaje się, że potrzebuje tylko jednego lub dwóch wierszy i wydaje się, że jako klucz podstawowy potrzebny jest plik scan_file_id. Ta tabela ma tylko dwie kolumny, a numer identyfikacyjny jest bez znaczenia.

A jeśli to sprawa, nie potrzebujesz żadnego z numerów identyfikacyjnych.

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) 
); 

A teraz nie ma potrzeby łączenia, albo. Po prostu zapytaj o tabelę "pliki".

1

Myślę, że jtseng dał rozwiązanie.

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

zasadzie to samo, co napisali w swojej ostatniej opcji:

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

To beacuse trzeba unikać pełnego skanowania tabeli/join.

Więc najpierw trzeba swoje odrębne identyfikatory 1-2:

select distinct scan_file_id from resume_points 

potem tylko twoi 1-2 rzędy muszą być połączone na drugim stole zamiast wszystkich 10K, co daje optymalizację wydajności.

jeśli potrzebujesz tego oświadczenia kilka razy, umieściłbym go w widoku. widok nie zmienia wydajności, ale wygląda na bardziej przejrzysty/łatwiejszy do odczytania.

również sprawdzić dokumentację optymalizacji zapytań: http://www.sqlite.org/optoverview.html

Powiązane problemy