2013-06-28 5 views
10

Ich versuche herauszufinden, die beste Möglichkeit, (wahrscheinlich spielt in diesem Fall keine Rolle), die Zeilen einer Tabelle zu finden, basierend auf der Existenz eines Flags und einer relationalen ID hintereinander in einer anderen Tabelle.SQLite3 Abfrageoptimierung Join vs Subselect

hier sind die Schemata:

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

ich verwende SQLite3

dort Dateien Tabelle sehr groß sein wird, 10K-5M Reihen typisch. die resume_points wird mit kleinen < 10K sein nur 1-2 verschiedene scan_file_id ‚s

so mein erster Gedanke war:

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

ein Mitarbeiter schlugen vor Drehen der Verbindung um:

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

dann Ich dachte, da wir wissen, dass die Anzahl der verschiedenen scan_file_id wird so klein sein, wäre vielleicht ein Subselect optimal (in diesem seltenen Fall):

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

Die Ausgänge explain hatten die folgenden Zeilen: 42, 42 und 48.

+1

Dies hängt von Ihren Daten und Hardware. Das musst du selbst messen. –

+1

Sie haben verpasst und files.dirty = 1 für die letzte Abfrage – eglasius

Antwort

11

TL; DR: Die beste Abfrage und Index:

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; 

Da ich anfangs typischerweise mit SQL Server arbeiten, dachte ich mir, dass sicherlich die Abfrage-Optimierer die optimale Ausführungsplan für eine solche einfache Abfrage finden würde unabhängig davon, auf welche Weise Sie diese äquivalenten SQL-Anweisungen schreiben. Also habe ich SQLite heruntergeladen und angefangen herumzuspielen. Zu meiner Überraschung gab es einen großen Leistungsunterschied.

Hier ist der Setup-Code:

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; 

I als zwei Indizes:

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

Im Folgenden sind die Abfragen Ich habe versucht, und die Ausführungszeiten auf meinem i5 Laptop. Die Datenbankdateigröße beträgt nur ca. 200 MB, da keine anderen Daten vorhanden sind.

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 

Es sieht so aus, als ob der Abfrageoptimierer von SQLite überhaupt nicht sehr weit fortgeschritten ist. Die besten Abfragen reduzieren zuerst resume_points auf eine kleine Anzahl von Zeilen (Zwei im Testfall. Das OP sagte, es wäre 1-2.), Und dann die Datei nachschauen, um festzustellen, ob sie schmutzig ist oder nicht. dirtyFiles Index machte keinen großen Unterschied für eine der Dateien. Ich denke, es könnte wegen der Art sein, wie die Daten in den Testtabellen angeordnet sind. Dies kann bei Produktionstabellen einen Unterschied machen. Der Unterschied ist jedoch nicht so groß, da es weniger als eine Handvoll Suchen gibt. uniqueFiles macht einen Unterschied, da es 10000 Reihen von resume_points auf 2 Zeilen reduzieren kann, ohne die meisten davon zu scannen. fileLookup hat einige Abfragen etwas schneller gemacht, aber nicht genug, um die Ergebnisse signifikant zu ändern. Vor allem machte es die Gruppe sehr langsam. Zusammenfassend reduzieren Sie das Ergebnis früh, um die größten Unterschiede zu erzielen.

+1

Haben Sie nach dem Erstellen der Indizes den Befehl "Analysieren" ausgeführt? – Giorgi

1

Da files.id ist der Primärschlüssel, versuchen GROUPBY dieses Feld ing anstatt zu prüfen, 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 

Eine weitere Option für die Leistung zu betrachten ist, einen Index zu resume_points.scan_file_id hinzufügen.

CREATE INDEX index_resume_points_scan_file_id ON resume_points (scan_file_id) 
1

Sie könnten exists versuchen, die alle doppelten nicht produzieren files:

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

Natürlich ist es könnte helfen, die richtigen Indizes haben:

files.dirty 
resume_points.scan_file_id 

Ob ein Index hilfreich ist, hängt von Ihren Daten ab.

0

Wenn die Tabelle "resume_points" nur eine oder zwei unterschiedliche Datei-ID-Nummern hat, scheint sie nur eine oder zwei Zeilen zu benötigen und scheint scan_file_id als Primärschlüssel zu benötigen. Diese Tabelle hat nur zwei Spalten und die ID-Nummer ist bedeutungslos.

Und wenn das ist der Fall, Sie benötigen keine der ID-Nummern.

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

Und jetzt brauchen Sie die Verbindung auch nicht. Fragen Sie einfach die Tabelle "files" ab.

1

Ich denke, jtseng gab die Lösung.

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

Im Grunde ist es das gleiche, was Sie als letzte Option Beiträge geschrieben haben:

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

Es ist beacuse Sie einen vollständigen Tabellenscan zu vermeiden, haben/beitreten.

So benötigen Sie zunächst Ihre 1-2 verschiedene ids:

select distinct scan_file_id from resume_points 

danach nur Ihre 1-2 Zeilen auf der anderen Tabelle verbunden werden müssen, anstatt alle 10K, der die Performance-Optimierung gibt.

Wenn Sie diese Aussage mehrmals benötigen, würde ich es in eine Ansicht bringen. Die Ansicht ändert die Leistung nicht, aber sie sieht sauberer/leichter lesbar aus.

überprüfen Sie auch die Abfrageoptimierung Dokumentation: http://www.sqlite.org/optoverview.html