2009-08-07 23 views
21

tôi nhận được vấn đề hiệu suất khi LIMIT ing một mysql SELECT với một lượng lớn bù đắp:Làm cách nào để tăng tốc truy vấn MySQL với chênh lệch lớn trong mệnh đề LIMIT?

SELECT * FROM table LIMIT m, n; 

Nếu bù đắp m được, nói, lớn hơn 1.000.000, các hoạt động diễn ra rất chậm.

Tôi phải sử dụng limit m, n; Tôi không thể sử dụng một cái gì đó như id > 1,000,000 limit n.

Tôi làm cách nào để tối ưu hóa tuyên bố này để có hiệu suất tốt hơn?

Trả lời

13

Có lẽ bạn có thể tạo bảng lập chỉ mục cung cấp khóa tuần tự liên quan đến khóa trong bảng mục tiêu của bạn. Sau đó, bạn có thể tham gia bảng lập chỉ mục này vào bảng mục tiêu của bạn và sử dụng mệnh đề where để có được các hàng bạn muốn một cách hiệu quả hơn.

#create table to store sequences 
CREATE TABLE seq (
    seq_no int not null auto_increment, 
    id int not null, 
    primary key(seq_no), 
    unique(id) 
); 

#create the sequence 
TRUNCATE seq; 
INSERT INTO seq (id) SELECT id FROM mytable ORDER BY id; 

#now get 1000 rows from offset 1000000 
SELECT mytable.* 
FROM mytable 
INNER JOIN seq USING(id) 
WHERE seq.seq_no BETWEEN 1000000 AND 1000999; 
+3

cách tiếp cận này chỉ hoạt động trong các câu lệnh chọn không chứa điều kiện. theo ý kiến ​​của tôi nó không phải là một giải pháp tốt. –

+3

Làm cách nào để giữ bảng chỉ mục này được cập nhật? Trong trường hợp của tôi, tôi phải sắp xếp theo cột datetime và sử dụng các offset lớn dẫn đến các truy vấn chậm. Nếu tôi tạo bảng hỗ trợ này, tôi sẽ cần phải cài đặt lại mỗi khi tôi có một ngày mới, vì nó không đến theo thứ tự. Tôi đã thấy giải pháp này, nhưng với các bảng tạm thời. –

9

Có một bài viết trên blog ở đâu đó trên internet về cách bạn nên thực hiện tốt nhất việc lựa chọn các hàngđể hiển thị nên càng nhỏ gọn càng tốt, do đó: chỉ cần id; và tạo ra kết quả hoàn chỉnh sẽ lần lượt tìm nạp tất cả dữ liệu bạn muốn chỉ cho các hàng bạn đã chọn.

Như vậy, SQL có thể là một cái gì đó tương tự (chưa được kiểm tra, tôi không chắc chắn nó sẽ thực sự làm bất cứ tốt):

select A.* from table A 
    inner join (select id from table order by whatever limit m, n) B 
    on A.id = B.id 
order by A.whatever 

Nếu động cơ SQL của bạn là quá thô sơ để cho phép loại này các câu lệnh SQL, hoặc nó không cải thiện bất cứ điều gì, chống lại hy vọng, nó có thể là đáng giá để phá vỡ tuyên bố đơn này thành nhiều câu lệnh và nắm bắt các id vào một cấu trúc dữ liệu.

Cập nhật: Tôi đã tìm thấy bài đăng trên blog mà tôi đã nói đến: đó là số "All Abstractions Are Failed Abstractions" của Jeff Atwood về Horror Horror.

+0

Tôi đã thử nghiệm SQL của bạn được đề xuất. nhưng nó không thực hiện bất kỳ cải tiến nào. –

+1

Điều gì sẽ xảy ra nếu bạn có mệnh đề where dựa trên bảng A? Nó sẽ không hoạt động, vì nó giới hạn đầu tiên, sau đó áp dụng mệnh đề where. Nếu bạn sử dụng tham gia vào bên trong của truy vấn phụ của bạn, bạn sẽ mất hiệu suất, phải không? –

+0

Nó làm việc cho tôi, truy vấn 'SELECT id FROM ...' đã được thực hiện nhanh hơn khoảng 50 lần trên một tập hợp gần một triệu hàng so với 'SELECT bunch, of, fields FROM ...'. –

2

Câu trả lời của Paul Dixon thực sự là một giải pháp cho vấn đề, nhưng bạn sẽ phải duy trì bảng tuần tự và đảm bảo rằng không có khoảng trống hàng.

Nếu điều đó khả thi, giải pháp tốt hơn sẽ đơn giản là đảm bảo rằng bảng gốc không có khoảng trống hàng và bắt đầu từ id 1. Sau đó, lấy các hàng bằng cách sử dụng id để phân trang.

CHỌN * TỪ bảng A WHERE id > = 1 VÀ id < = 1000;
SELECT * FROM table A WHERE id > = 1001 AND id < = 2000;

và vân vân ...

+0

SELECT * FROM table WHERE id> 1000 LIMIT 1000 –

+1

Một lần nữa, nó sẽ không hoạt động nếu các bộ lọc khác được áp dụng. – devXen

2

Tôi không nghĩ rằng có bất kỳ cần phải tạo ra một chỉ số riêng biệt nếu bảng của bạn đã có một. Nếu vậy, thì bạn có thể đặt hàng theo khóa chính này và sau đó sử dụng các giá trị của chìa khóa để bước qua:

SELECT * FROM myBigTable WHERE id > :OFFSET ORDER BY id ASC; 

tối ưu hóa khác sẽ không sử dụng SELECT * nhưng chỉ ID để nó chỉ đơn giản là có thể đọc các chỉ số và không phải định vị tất cả dữ liệu (giảm chi phí IO).Nếu bạn cần một số cột khác thì có lẽ bạn có thể thêm chúng vào chỉ mục để chúng được đọc bằng khóa chính (rất có thể sẽ được giữ trong bộ nhớ và do đó không yêu cầu tra cứu đĩa) - mặc dù điều này sẽ không phù hợp cho tất cả các trường hợp, do đó bạn sẽ phải có một vở kịch.

tôi đã viết một bài viết với nhiều chi tiết:

http://www.4pmp.com/2010/02/scalable-mysql-avoid-offset-for-large-tables/

+0

Chỉ là mysql hoặc hầu hết dbs hoạt động theo cách kỳ lạ này? Cho đến nay, giải pháp tốt nhất là truy vấn phụ (khi bạn không có chỉ mục có thứ tự). Truy vấn và đặt hàng tất cả đầu tiên, sau đó đặt bù đắp. –

+0

Ý tưởng chỉ sử dụng ID có thể là một giải pháp rất tốt thực sự, nó phụ thuộc vào công cụ lưu trữ tôi cho là! – twicejr

4

Nếu hồ sơ là lớn, chậm chạp có thể đến từ tải dữ liệu. Nếu cột id được lập chỉ mục, thì việc chọn nó sẽ nhanh hơn nhiều. Sau đó bạn có thể thực hiện một truy vấn thứ hai với một khoản TRÊN cho id thích hợp (hoặc có thể xây dựng một mệnh đề WHERE sử dụng min và id max từ truy vấn đầu tiên.)

chậm:

SELECT * FROM table ORDER BY id DESC LIMIT 10 OFFSET 50000 

nhanh:

SELECT id FROM table ORDER BY id DESC LIMIT 10 OFFSET 50000 

SELECT * FROM table WHERE id IN (1,2,3...10) 
0

Gần đây, tôi đã gặp sự cố này. Vấn đề là hai phần cần sửa. Trước tiên tôi đã phải sử dụng một lựa chọn bên trong mệnh đề FROM của tôi rằng đã làm hạn chế và bù đắp cho tôi trên chỉ là khóa chính của tôi:

$subQuery = DB::raw("(SELECT id FROM titles WHERE id BETWEEN {$startId} AND {$endId} ORDER BY title) as t"); 

Sau đó, tôi có thể sử dụng như là từ một phần của truy vấn của tôi:

'titles.id', 
          'title_eisbns_concat.eisbns_concat', 
          'titles.pub_symbol', 
          'titles.title', 
          'titles.subtitle', 
          'titles.contributor1', 
          'titles.publisher', 
          'titles.epub_date', 
          'titles.ebook_price', 
          'publisher_licenses.id as pub_license_id', 
          'license_types.shortname', 
          $coversQuery 
         ) 
         ->from($subQuery) 
         ->leftJoin('titles', 't.id', '=', 'titles.id') 
         ->leftJoin('organizations', 'organizations.symbol', '=', 'titles.pub_symbol') 
         ->leftJoin('title_eisbns_concat', 'titles.id', '=', 'title_eisbns_concat.title_id') 
         ->leftJoin('publisher_licenses', 'publisher_licenses.org_id', '=', 'organizations.id') 
         ->leftJoin('license_types', 'license_types.id', '=', 'publisher_licenses.license_type_id') 

Lần đầu tiên tôi tạo truy vấn này, tôi đã sử dụng OFFSET và LIMIT trong MySql. Điều này làm việc tốt cho đến khi tôi đã qua trang 100 sau đó bù đắp bắt đầu nhận được không thể chịu đựng chậm. Thay đổi điều đó thành GIỮA trong truy vấn bên trong của tôi đã tăng tốc nó cho bất kỳ trang nào. Tôi không chắc chắn tại sao MySql không tăng tốc OFFSET nhưng giữa dường như cuộn nó trở lại.

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