2012-01-24 33 views
25

Tôi gặp sự cố về hiệu suất trong SQLite với SELECT COUNT (*) trên một bảng lớn.SQLite: COUNT chậm trên các bảng lớn

Vì tôi chưa nhận được câu trả lời có thể sử dụng và tôi đã thực hiện thêm một số thử nghiệm, tôi đã chỉnh sửa câu hỏi của mình để kết hợp các phát hiện mới của tôi.

Tôi có 2 bảng:

CREATE TABLE Table1 (
Key INTEGER NOT NULL, 
... several other fields ..., 
Status CHAR(1) NOT NULL, 
Selection VARCHAR NULL, 
CONSTRAINT PK_Table1 PRIMARY KEY (Key ASC)) 

CREATE Table2 (
Key INTEGER NOT NULL, 
Key2 INTEGER NOT NULL, 
... a few other fields ..., 
CONSTRAINT PK_Table2 PRIMARY KEY (Key ASC, Key2 ASC)) 

Table1 có khoảng 8 triệu hồ sơ và Table2 có khoảng 51 triệu hồ sơ, và databasefile là trên 5GB.

Table1 có thêm 2 chỉ số:

CREATE INDEX IDX_Table1_Status ON Table1 (Status ASC, Key ASC) 
CREATE INDEX IDX_Table1_Selection ON Table1 (Selection ASC, Key ASC) 

"Status" là bắt buộc lĩnh vực, nhưng chỉ có 6 giá trị khác biệt, "Lựa chọn" là không cần thiết và chỉ có khoảng 1,5 triệu giá trị khác nhau từ vô chỉ khoảng 600k giá trị khác biệt.

Tôi đã thực hiện một số thử nghiệm trên cả hai bảng, bạn có thể xem thời gian bên dưới và tôi đã thêm "giải thích kế hoạch truy vấn" cho mỗi yêu cầu (QP). Tôi đặt tập tin cơ sở dữ liệu trên một bộ nhớ USB để tôi có thể loại bỏ nó sau mỗi lần kiểm tra và nhận được kết quả đáng tin cậy mà không có sự can thiệp của bộ đệm đĩa. Một số yêu cầu nhanh hơn trên USB (tôi giả sử do thiếu thời gian tìm kiếm), nhưng một số yêu cầu chậm hơn (quét bảng).

SELECT COUNT(*) FROM Table1 
    Time: 105 sec 
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows) 
SELECT COUNT(Key) FROM Table1 
    Time: 153 sec 
    QP: SCAN TABLE Table1 (~1000000 rows) 
SELECT * FROM Table1 WHERE Key = 5123456 
    Time: 5 ms 
    QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) 
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1 
    Time: 16 sec 
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows) 
SELECT * FROM Table1 WHERE Selection = 'SomeValue' AND Key > 5123456 LIMIT 1 
    Time: 9 ms 
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Selection (Selection=?) (~3 rows) 

Như bạn có thể thấy số lượng rất chậm, nhưng lựa chọn bình thường rất nhanh (ngoại trừ lựa chọn bình thường, mất 16 giây).

Cũng vậy với Table2:

SELECT COUNT(*) FROM Table2 
    Time: 528 sec 
    QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~1000000 rows) 
SELECT COUNT(Key) FROM Table2 
    Time: 249 sec 
    QP: SCAN TABLE Table2 (~1000000 rows) 
SELECT * FROM Table2 WHERE Key = 5123456 AND Key2 = 0 
    Time: 7 ms 
    QP: SEARCH TABLE Table2 USING INDEX sqlite_autoindex_Table2_1 (Key=? AND Key2=?) (~1 rows) 

Tại sao SQLite không sử dụng chỉ số tự động tạo ra trên khóa chính trên Table1? Và tại sao, khi anh ta sử dụng chỉ mục tự động trên Bảng 2, nó vẫn mất rất nhiều thời gian?

Tôi đã tạo cùng một bảng với cùng nội dung và chỉ mục trên SQL Server 2008 R2 và có số lượng gần như tức thời.

Một trong các nhận xét bên dưới đề xuất thực thi ANALYZE trên cơ sở dữ liệu. Tôi đã làm và mất 11 phút để hoàn thành. Sau đó, tôi chạy một số các xét nghiệm một lần nữa:

SELECT COUNT(*) FROM Table1 
    Time: 104 sec 
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~7848023 rows) 
SELECT COUNT(Key) FROM Table1 
    Time: 151 sec 
    QP: SCAN TABLE Table1 (~7848023 rows) 
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1 
    Time: 5 ms 
    QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid>?) (~196200 rows) 
SELECT COUNT(*) FROM Table2 
    Time: 529 sec 
    QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~51152542 rows) 
SELECT COUNT(Key) FROM Table2 
    Time: 249 sec 
    QP: SCAN TABLE Table2 (~51152542 rows) 

Như bạn có thể thấy, các truy vấn đã dành thời gian cùng (trừ kế hoạch truy vấn hiện đang hiển thị số thực của hàng), chỉ chọn chậm là bây giờ cũng nhanh.

Tiếp theo, tôi tạo chỉ mục bổ sung trên trường Khóa của Bảng 1, tương ứng với chỉ mục tự động. Tôi đã làm điều này trên cơ sở dữ liệu gốc, mà không có dữ liệu ANALYZE. Phải mất hơn 23 phút để tạo chỉ mục này (hãy nhớ, đây là trên một thanh USB).

CREATE INDEX IDX_Table1_Key ON Table1 (Key ASC) 

Sau đó, tôi chạy các bài kiểm tra một lần nữa:

SELECT COUNT(*) FROM Table1 
    Time: 4 sec 
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Key(~1000000 rows) 
SELECT COUNT(Key) FROM Table1 
    Time: 167 sec 
    QP: SCAN TABLE Table2 (~1000000 rows) 
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1 
    Time: 17 sec 
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows) 

Như bạn thấy, chỉ số giúp với count (*), nhưng không phải với count (Key).

Finaly, tôi tạo bảng bằng cách sử dụng một cột hạn chế thay vì một hạn chế bảng:

CREATE TABLE Table1 (
Key INTEGER PRIMARY KEY ASC NOT NULL, 
... several other fields ..., 
Status CHAR(1) NOT NULL, 
Selection VARCHAR NULL) 

Sau đó, tôi chạy các bài kiểm tra một lần nữa:

SELECT COUNT(*) FROM Table1 
    Time: 6 sec 
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows) 
SELECT COUNT(Key) FROM Table1 
    Time: 28 sec 
    QP: SCAN TABLE Table1 (~1000000 rows) 
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1 
    Time: 10 sec 
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows) 

Mặc dù kế hoạch truy vấn là như nhau, các thời gian tốt hơn rất nhiều. Tại sao điều này?

Vấn đề là ALTER TABLE không cho phép chuyển đổi bảng hiện có và tôi có rất nhiều cơ sở dữ liệu hiện có mà tôi không thể chuyển đổi sang biểu mẫu này. Bên cạnh đó, sử dụng một cột contraint thay vì ràng buộc bảng sẽ không làm việc cho Table2.

Có ai biết tôi đang làm gì sai và cách giải quyết vấn đề này không?

Tôi đã sử dụng System.Data.SQLite phiên bản 1.0.74.0 để tạo các bảng và chạy thử nghiệm tôi đã sử dụng SQLiteSpy 1.9.1.

Cảm ơn,

Marc

+4

Nếu bạn có vấn đề về hiệu năng với SQLite, giải pháp thường là chuyển sang máy chủ DB lớn hơn (tôi khuyên bạn nên sử dụng Postgres trên MS SQL). – Borealid

+0

Tôi không gặp phải bất kỳ vấn đề hiệu suất nào khác, tất cả các lựa chọn khác đều nhanh (và sử dụng các chỉ mục chính xác), chèn và cập nhật nhanh, chỉ có số lượng làm phiền tôi. – Marc

+0

Điều này thực sự khó hiểu, bởi vì (đối với DB2, ít nhất) hầu hết các RDBMS có thể sử dụng thông tin được lưu trữ hiệu quả - nếu bạn yêu cầu đếm các hàng _all_ (hoặc bị hạn chế bởi một cái gì đó trong chỉ mục), nó thường có thể đọc thông tin đó bản thân chỉ mục - chỉ mục biết số lượng mục nhập. Đó là điều kỳ diệu gấp đôi trong đó bạn nói rằng tất cả '' '' '' '' '' '' '' '' 'nhanh - họ cần phải biết số lượng bản ghi để có thể tối ưu hóa đúng cách! Trừ khi có điều gì đó đang xảy ra và bạn đang khóa bảng (cấp độ giao dịch đọc lặp lại hoặc một số thứ như vậy?) ... –

Trả lời

1

này có thể không giúp được gì nhiều, nhưng bạn có thể chạy ANALYZE lệnh để xây dựng lại cơ sở dữ liệu thống kê về bạn. Hãy thử chạy "ANALYZE;" để xây dựng lại các thống kê về toàn bộ cơ sở dữ liệu, sau đó chạy lại truy vấn của bạn và xem nó có nhanh hơn không.

+0

Tôi đã thực hiện lệnh ANALYZE, mất nhiều thời gian để hoàn thành, nhưng nó không thay đổi kết quả, số đếm vẫn chậm. – Marc

+0

'ANALYZE' đã khắc phục sự cố cho DB của tôi khi thực hiện' LEFT JOIN' –

0

Về vấn đề ràng buộc cột, các cột bản đồ SQLite được khai báo là INTEGER PRIMARY KEY với id hàng nội bộ (lần lượt thừa nhận một số tối ưu hóa nội bộ). Về mặt lý thuyết, nó có thể làm tương tự cho một ràng buộc khóa chính được khai báo riêng, nhưng nó dường như không làm như vậy trong thực tế, ít nhất là với phiên bản SQLite đang sử dụng. (System.Data.SQLite 1.0.74.0 tương ứng với lõi SQLite 3.7.7.1. Bạn có thể thử kiểm tra lại các con số của mình với 1.0.79.0; bạn không cần phải thay đổi cơ sở dữ liệu của mình để làm điều đó, chỉ là thư viện.)

+0

Tôi đã thử cả hai truy vấn (count (*) và count (key)) với phiên bản mới nhất của System.Data.SQlite (1.0.79.0) và I thu được kết quả tương tự như trước. – Marc

+0

Vì tôi đã phải viết một chương trình thử nghiệm nhỏ (vì SQLIteSpy sử dụng phiên bản cũ hơn của SQLite, 3.7.8), tôi đã thử cả 32 và 64 bit, nhưng tôi thu được kết quả tương tự cho cả hai. – Marc

0

Đầu ra cho các truy vấn nhanh đều bắt đầu bằng văn bản "QP: SEARCH". Trong khi những người cho các truy vấn chậm bắt đầu với văn bản "QP: SCAN", cho thấy rằng sqlite đang thực hiện quét toàn bộ bảng để tạo ra số lượng.

Googling cho "số lần quét bảng sqlite" tìm thấy the following, cho thấy rằng việc sử dụng tính năng quét toàn bộ bảng để truy xuất số chỉ là cách hoạt động của sqlite và do đó có thể không tránh khỏi.

Như một giải pháp thay thế và cho rằng trạng thái đó chỉ có tám giá trị, tôi tự hỏi liệu bạn có thể đếm nhanh bằng truy vấn như sau không?

chọn 1 nơi status = 1 đoàn chọn 1 nơi status = 2 ...

sau đó đếm các dòng trong kết quả. Điều này rõ ràng là xấu, nhưng nó có thể hoạt động nếu nó thuyết phục sqlite chạy truy vấn dưới dạng tìm kiếm thay vì quét. Ý tưởng trả về "1" mỗi lần là để tránh chi phí trả lại dữ liệu thực.

+0

Tôi đã tìm thấy [post] này (http://www.mail-archive.com/[email protected]/msg10279.html) từ tác giả của SQLite, vì vậy tôi đã từ bỏ hy vọng, vì việc thêm trình kích hoạt sẽ quá phạt khi chèn và xóa. Nhưng tôi đã thử đề nghị của bạn. Lần đầu tiên tôi đã thử 'SELECT COUNT (*) FROM table1 trong đó Status in (1,2,3,4,5,6)', nó được thực hiện trong 86 giây (nhanh hơn một chút), QP: 'TABLE TÌM KIẾM Bảng1 SỬ DỤNG BÌA CHỈNH SỬA IDX_Table1_Status (Trạng thái =?) (~ 60 hàng); DANH SÁCH DANH SÁCH THỰC HIỆN 1'. Tốt hơn nhưng không đủ tốt. – Marc

+0

Tôi đã thử đề xuất công đoàn của bạn. 'SELECT COUNT (*) TỪ (SELECT 1 FROM Table1 WHERE Status = 1 UNION SELECT 1 FROM Table1 WHERE Status = 2 UNION ...)' trả về 1, tương tự cho SUM (*), tôi đoán là do các đặc tính a liên hiệp. 'SELECT COUNT (*) TỪ (SELECT 1 FROM Table1 WHERE Status = 1 UNION SELECT 2 FROM Table1 WHERE Status = 2 UNION ...)' trả về 6. Vì vậy, cuối cùng tôi đã thử 'SELECT COUNT (*) FROM (SELECT Key FROM Table1 WHERE Status = 1 UNION SELECT Key FROM Table1 WHERE Status = 2 UNION ...) 'trả về kết quả chính xác, nhưng rất chậm (116 giây). Nhờ đề nghị mặc dù. – Marc

+0

Và lần thử đầu tiên của tôi ('SELECT COUNT (*) FROM table1 trong đó Status in (1,2,3,4,5,6)') tốt hơn một chút, sẽ không hoạt động đối với bảng khác của tôi (Table2). – Marc

0

Đây là giải pháp tiềm năng để cải thiện hiệu suất truy vấn. Từ ngữ cảnh, có vẻ như truy vấn của bạn mất khoảng một phút rưỡi để chạy.

Giả sử bạn có cột date_created (hoặc có thể thêm một cột), chạy truy vấn dưới nền mỗi ngày lúc nửa đêm (lúc 00:05 sáng) và duy trì giá trị ở đâu đó cùng với ngày last_updated được tính (I ' sẽ quay trở lại điều đó một chút).

Sau đó, chạy với cột date_created (có chỉ mục), bạn có thể tránh quét toàn bộ bảng bằng cách thực hiện truy vấn như SELECT COUNT (*) FROM TABLE WHERE date_updated> "[TODAY] 00:00:05".

Thêm giá trị đếm từ truy vấn đó vào giá trị được duy trì của bạn và bạn có số đếm nhanh tương đối chính xác.

Điểm duy nhất là từ 12:05 sáng đến 12:07 sáng (thời gian truy vấn tổng số của bạn đang chạy) bạn có điều kiện chủng tộc để bạn có thể kiểm tra giá trị last_updated của bảng quét toàn bộ(). Nếu đó là> 24 giờ, thì truy vấn tính gia tăng của bạn cần phải kéo tổng số ngày của một ngày cộng với thời gian trôi qua ngày hôm nay. Nếu đó là < 24 giờ, thì truy vấn đếm gia tăng của bạn cần phải kéo số đếm một ngày (chỉ thời gian trôi qua ngày hôm nay).

+0

Xin lỗi vì đã trả lời trễ, tôi bị ốm vài ngày. SQLite không phải là một máy chủ SQL, nó là một cơ sở dữ liệu độc lập. Vì vậy, bạn không thể lên lịch các tác vụ, trừ khi bạn sử dụng trình lên lịch cửa sổ (hoặc hệ điều hành khác). Dù sao, chỉ có một kết nối tại thời điểm cho một cơ sở dữ liệu được cho phép, do đó, trong khi số lượng lịch biểu của bạn đang chạy cơ sở dữ liệu sẽ bị chặn cho tất cả các truy cập khác. Nó không phải là một giải pháp mà sẽ làm việc cho tôi. – Marc

18

Từ http://old.nabble.com/count(*)-slow-td869876.html

SQLite luôn quét toàn bộ bảng (*). Nó
không giữ thông tin meta trên bảng để tăng tốc quá trình này
.

Không lưu thông tin meta là thiết kế có chủ ý
quyết định. Nếu mỗi bảng lưu trữ một số (hoặc tốt hơn, mỗi số
nút của số btree được lưu trữ) sau đó cập nhật nhiều hơn
sẽ phải xảy ra trên mọi INSERT hoặc DELETE. Điều này
sẽ làm chậm INSERT và DELETE, ngay cả trong trường hợp phổ biến
trường hợp tốc độ đếm (*) không quan trọng.

Nếu bạn thực sự cần một COUNT nhanh, sau đó bạn có thể tạo
một kích hoạt trên INSERT và DELETE mà cập nhật một chạy
đếm trong một bảng riêng biệt sau đó truy vấn mà tách
bảng để tìm ra số mới nhất.

Tất nhiên, nó không đáng giữ một số lượng hàng ĐẦY ĐỦ nếu bạn
cần COUNTgiây phụ thuộc vào mệnh đề WHERE (ví dụ: Ở ĐÂU field1> 0 và field2 < 1000000000).

+1

Xin lỗi vì trả lời muộn, tôi đã bị ốm vài ngày. Tôi đã đăng một liên kết đến cùng một bài đăng trong một trong các nhận xét của tôi. Tôi cho rằng việc thêm trình kích hoạt sẽ quá đáng ngại khi chèn và xóa hàng loạt. Tôi nghĩ rằng nó sẽ là tốt nhất theo dõi số lượng bảng vào cuối mỗi chèn và/hoặc xóa giao dịch, do đó, truy cập chỉ được cập nhật một lần và không phải trên mỗi chèn/xóa. – Marc

+0

Ngoài ra, 'COUNT (1)' phải nhanh hơn 'COUNT (*)' và thậm chí 'COUNT (" id ")'. –

+0

@AlixAxel trong tất cả các thử nghiệm của tôi 'COUNT()' và 'COUNT (*)' là những cái nhanh nhất, 'COUNT (1)' lấy gấp đôi và 'COUNT (ROWID)' lấy ba lần. – springy76

0

Tôi đã có cùng một vấn đề, trong tình huống của tôi lệnh VACUUM đã giúp. Sau khi thực hiện trên cơ sở dữ liệu COUNT (*) tốc độ tăng gần 100 lần. Tuy nhiên, lệnh của chính nó cần một vài phút trong cơ sở dữ liệu của tôi (20 triệu bản ghi). Tôi giải quyết vấn đề này bằng cách chạy VACUUM khi phần mềm của tôi thoát sau khi phá hủy cửa sổ chính, vì vậy sự chậm trễ không gây ra vấn đề cho người dùng.

+3

VACUUM sẽ buộc toàn bộ tập tin được đọc và ghi, vì vậy nó sẽ lấp đầy nội dung đĩa vào bộ nhớ cache, đó là lý do tại sao nó nhanh hơn. PC của bạn, bạn sẽ tìm thấy nó chậm lại, tôi giả sử: –

18

Nếu bạn chưa DELETE d bất kỳ hồ sơ, thực hiện:

SELECT MAX(_ROWID_) FROM "table" LIMIT 1; 

sẽ tránh được đầy đủ bảng scan. Lưu ý rằng _ROWID_ is a SQLite identifier.

+0

Nên là câu trả lời tốt nhất Trả về ngay lập tức và đưa ra một xấp xỉ tốt (thường là tất cả những gì bạn muốn) – easytiger

+1

Xác nhận, điều này trả về một giá trị trong vài ms cho DB của tôi với 115 triệu các bản ghi trong một bảng. Thực hiện đầy đủ COUNT (*) không bao giờ thực sự tuân thủ eted (Tôi đã từ bỏ chờ đợi sau 4 giờ). –

+1

Điều này là tốt nhưng hãy nhớ những gì Alix đã nói - ngay cả khi bạn đã xóa một bản ghi trong bảng này - bạn sẽ nhận được kết quả không chính xác (vì _ROWID_ là ID bản ghi tăng dần và 'xóa' sẽ không làm cho _ROWID_ giảm xuống). – strangetimes

2

Không tính các ngôi sao, đếm các bản ghi!Hoặc bằng ngôn ngữ khác, không bao giờ phát hành

CHỌN COUNT (*) TỪ tablename;

sử dụng

CHỌN COUNT (ROWID) TỪ tablename;

Gọi EXPLAIN QUERY PLAN cho cả hai để thấy sự khác biệt. Hãy chắc chắn rằng bạn có một chỉ mục tại chỗ có chứa tất cả các cột được đề cập trong mệnh đề WHERE.

+0

Dường như không tạo ra sự khác biệt cho tôi. – Fidel

+0

@Fidel Tùy thuộc vào mô hình và cài đặt cơ sở dữ liệu của bạn. Trong thử nghiệm của tôi, SQLite đã thực hiện quét toàn bộ tìm kiếm dấu hoa thị thay vì tìm kiếm chỉ mục khi được sử dụng với ROWID để đếm toàn bộ bảng. Có lẽ tôi cũng đã bỏ qua cái gì khác, tôi không tuyên bố là hoàn hảo. Tuy nhiên, tôi vẫn khuyên bạn nên sử dụng ** giải thích kế hoạch truy vấn **! Chỉ cần buộc DB sử dụng chỉ mục trên PK thay vì quét toàn bộ và xem ra hiệu ứng bộ nhớ đệm của hệ điều hành như Arnaud nhận xét dưới đây. Có thể truy vấn của bạn luôn nhanh chóng! – Thinkeye

+1

Điều này là sai trong hầu hết các trường hợp. Ít nhất trong tất cả các bảng của tôi tìm kiếm theo rowid mất 22 giây, so với tổng số 4 giây – easytiger

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