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
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
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
Đ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?) ... –