2013-06-28 42 views
10

Tôi đang cố gắng tìm ra cách tốt nhất, (có thể không quan trọng trong trường hợp này) để tìm các hàng của một bảng, dựa trên sự tồn tại của một lá cờ, và một id quan hệ trong một hàng trong một bảng khác.Tối ưu hóa truy vấn SQLite3 tham gia vs subselect

đây là các lược đồ:

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

Tôi đang sử dụng SQLite3

có tập tin bảng sẽ rất lớn, 10K-5M hàng thường. các resume_points sẽ nhỏ < 10K với chỉ 1-2 scan_file_id biệt 's

nên suy nghĩ đầu tiên của tôi là:

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

một đồng nghiệp đề nghị chuyển join xung quanh:

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

sau đó Tôi nghĩ rằng kể từ khi chúng tôi biết rằng số lượng khác biệt của scan_file_id sẽ rất nhỏ, có lẽ một subselect sẽ là tối ưu (trong trường hợp hiếm hoi này):

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

kết quả đầu ra explain có các hàng sau: 42, 42 và 48 tương ứng.

+1

Điều này tùy thuộc vào dữ liệu và phần cứng của bạn. Bạn phải tự mình đo lường điều này. –

+1

Bạn đã bỏ lỡ và files.dirty = 1 trên truy vấn cuối cùng – eglasius

Trả lời

11

TL; DR: Truy vấn tốt nhất và chỉ số là:

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; 

Vì tôi thường làm việc với SQL Server, lúc đầu tôi nghĩ rằng chắc chắn tôi ưu truy vấn sẽ tìm ra kế hoạch thực hiện tối ưu cho một truy vấn đơn giản như vậy không phụ thuộc vào cách bạn viết các câu lệnh SQL tương đương này. Vì vậy, tôi đã tải xuống SQLite và bắt đầu chơi xung quanh. Nhiều đến ngạc nhiên của tôi, có một sự khác biệt rất lớn về hiệu suất.

Dưới đây là các mã cài đặt:

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; 

tôi coi hai chỉ số:

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

Dưới đây là những câu hỏi tôi đã cố gắng và thời gian thực hiện trên máy tính xách tay i5 của tôi. Kích thước tệp cơ sở dữ liệu chỉ khoảng 200MB vì ​​nó không có bất kỳ dữ liệu nào khác.

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 

Dường như trình tối ưu hóa truy vấn của SQLite không quá tiên tiến. Các truy vấn tốt nhất đầu tiên giảm resume_points xuống một số lượng nhỏ các hàng (Hai trong trường hợp thử nghiệm. OP cho biết nó sẽ là 1-2.), Và sau đó tìm kiếm tệp để xem nó có bị bẩn hay không. Chỉ số dirtyFiles không tạo ra nhiều khác biệt cho bất kỳ tệp nào. Tôi nghĩ rằng nó có thể là do cách dữ liệu được sắp xếp trong các bảng thử nghiệm. Nó có thể tạo sự khác biệt trong các bảng sản xuất. Tuy nhiên, sự khác biệt không quá lớn vì sẽ có ít hơn một số lần tra cứu. uniqueFiles thực hiện một sự khác biệt vì nó có thể làm giảm 10.000 hàng resume_points thành 2 hàng mà không cần quét qua hầu hết các hàng. fileLookup đã làm cho một số truy vấn nhanh hơn một chút, nhưng không đủ để thay đổi đáng kể kết quả. Đáng chú ý là nó làm cho nhóm rất chậm. Tóm lại, giảm kết quả sớm để tạo ra sự khác biệt lớn nhất.

+1

Bạn có chạy lệnh Analyze sau khi tạo chỉ mục không? – Giorgi

1

Kể từ files.id là chìa khóa chính, hãy thử GROUP ing BY lĩnh vực này chứ không phải là kiểm tra 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 

Một lựa chọn khác để xem xét cho thực hiện được thêm một chỉ số để resume_points.scan_file_id.

CREATE INDEX index_resume_points_scan_file_id ON resume_points (scan_file_id) 
1

Bạn có thể thử exists, mà sẽ không sản xuất bất kỳ trùng lặp files:

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

Tất nhiên nó thể giúp đỡ để có các chỉ số thích hợp:

files.dirty 
resume_points.scan_file_id 

Cho dù một chỉ số là hữu ích sẽ phụ thuộc vào dữ liệu của bạn.

0

Nếu bảng "resume_points" sẽ chỉ có một hoặc hai số id tệp riêng biệt, có vẻ như chỉ cần một hoặc hai hàng và dường như cần scan_file_id làm khóa chính. Bảng đó chỉ có hai cột và số id là vô nghĩa.

Và nếu đó là trường hợp, bạn không cần một trong số 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) 
); 

Và bây giờ bạn không cần tham gia. Chỉ cần truy vấn bảng "tệp".

1

Tôi nghĩ rằng jtseng đã đưa ra giải pháp.

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

Về cơ bản nó giống những gì bạn đã được đăng là lựa chọn cuối cùng của bạn:

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

Đó là beacuse bạn phải tránh một bảng đầy đủ quét/join.

Vì vậy, lúc đầu bạn cần 1-2 id riêng biệt của bạn:

select distinct scan_file_id from resume_points 

sau đó chỉ 1-2 hàng của bạn phải được tham gia vào các bảng khác thay vì tất cả 10K, mang đến cho việc tối ưu hóa hiệu suất.

nếu bạn cần câu lệnh này nhiều lần, tôi sẽ đưa nó vào chế độ xem. xem sẽ không thay đổi hiệu suất nhưng nó trông sạch hơn/dễ đọc hơn.

cũng kiểm tra tài liệu tối ưu hóa truy vấn: http://www.sqlite.org/optoverview.html

Các vấn đề liên quan