Tại sao MYSQL bù LIMIT cao hơn làm chậm truy vấn?


173

Tóm lại kịch bản: Một bảng có hơn 16 triệu bản ghi [kích thước 2GB]. Độ lệch LIMIT càng cao với CHỌN, truy vấn càng trở nên chậm hơn, khi sử dụng ORDER BY * chính_key *

Vì thế

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

mất ít hơn nhiều

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

Điều đó chỉ đặt hàng 30 hồ sơ và dù sao đi nữa. Vì vậy, nó không phải là chi phí từ ORDER BY.
Bây giờ khi tìm nạp 30 hàng mới nhất, sẽ mất khoảng 180 giây. Làm thế nào tôi có thể tối ưu hóa truy vấn đơn giản đó?


LƯU Ý: Tôi là tác giả. MySQL không đề cập đến chỉ mục (CHÍNH HÃNG) trong các trường hợp trên. xem liên kết dưới đây của người dùng "Quassnoi" để được giải thích.
Rahman

Câu trả lời:


197

Điều bình thường là độ lệch cao hơn làm chậm truy vấn, vì truy vấn cần phải loại bỏ các OFFSET + LIMITbản ghi đầu tiên (và chỉ lấy LIMITchúng). Giá trị này càng cao, truy vấn càng chạy lâu.

Truy vấn không thể đi đúng OFFSETbởi vì, thứ nhất, các bản ghi có thể có độ dài khác nhau và thứ hai, có thể có những khoảng trống từ các bản ghi bị xóa. Nó cần kiểm tra và đếm từng bản ghi trên đường đi.

Giả sử idlà một PRIMARY KEYcủa một MyISAMbảng, bạn có thể tăng tốc nó lên bằng cách sử dụng thủ thuật này:

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

Xem bài viết này:


7
Hành vi "tra cứu hàng sớm" của MySQL là câu trả lời tại sao nó nói quá lâu. Bằng thủ thuật bạn cung cấp, chỉ các id khớp (theo chỉ mục trực tiếp) bị ràng buộc, lưu các tra cứu hàng không cần thiết của quá nhiều bản ghi. Điều đó đã lừa, hoan hô!
Rahman

4
@harald: chính xác ý bạn là gì khi "không hoạt động"? Đây là một cải tiến hiệu suất thuần túy. Nếu không có chỉ mục nào có thể sử dụng được ORDER BYhoặc chỉ mục bao gồm tất cả các lĩnh vực bạn cần, bạn không cần cách giải quyết này.
Quassnoi

6
@ f055: câu trả lời là "tăng tốc", không phải "thực hiện ngay lập tức". Bạn đã đọc câu đầu tiên của câu trả lời chưa?
Quassnoi

3
Có thể chạy một cái gì đó như thế này cho InnoDB?
NeverEinatingQueue

3
@Lanti: vui lòng gửi nó dưới dạng một câu hỏi riêng biệt và đừng quên gắn thẻ với postgresql. Đây là một câu trả lời dành riêng cho MySQL.
Quassnoi

220

Tôi đã có cùng một vấn đề chính mình. Với thực tế là bạn muốn thu thập một lượng lớn dữ liệu này và không phải là một bộ 30 cụ thể, bạn có thể sẽ chạy một vòng lặp và tăng phần bù lên 30.

Vì vậy, những gì bạn có thể làm thay vào đó là:

  1. Giữ id cuối cùng của một tập hợp dữ liệu (30) (ví dụ lastId = 530)
  2. Thêm điều kiện WHERE id > lastId limit 0,30

Vì vậy, bạn luôn có thể có một số bù ZERO. Bạn sẽ ngạc nhiên bởi sự cải thiện hiệu suất.


Điều này có hoạt động nếu có khoảng trống? Điều gì xảy ra nếu bạn không có một khóa duy nhất (ví dụ: khóa tổng hợp)?
xaisoft

8
Có thể không rõ ràng với tất cả những điều này chỉ hoạt động nếu tập kết quả của bạn được sắp xếp theo khóa đó, theo thứ tự tăng dần (theo thứ tự giảm dần cùng một ý tưởng hoạt động, nhưng thay đổi> cuối cùng thành <lastid.) Không thành vấn đề nếu đó là khóa chính hoặc một trường khác (hoặc nhóm các trường.)
Eloff

Làm tốt lắm người đàn ông đó! Một giải pháp rất đơn giản đã giải quyết vấn đề của tôi :-)
oodavid

30
Chỉ cần lưu ý rằng giới hạn / bù thường được sử dụng trong các kết quả được phân trang và việc giữ lastId đơn giản là không thể vì người dùng có thể chuyển đến bất kỳ trang nào, không phải luôn luôn là trang tiếp theo. Nói cách khác, phần bù thường cần được tính toán linh hoạt dựa trên trang và giới hạn, thay vì theo một mẫu liên tục.
Tom

3
Tôi nói nhiều hơn về việc "nhớ nơi bạn rời đi" trong mysql.rjweb.org/doc.php/pagination
Rick James

17

MySQL không thể truy cập trực tiếp vào bản ghi thứ 10000 (hoặc byte thứ 80000 như đề xuất của bạn) bởi vì nó không thể giả định rằng nó được đóng gói / sắp xếp như thế (hoặc nó có các giá trị liên tục trong 1 đến 10000). Mặc dù có thể là như vậy trong thực tế, MySQL không thể cho rằng không có lỗ hổng / lỗ hổng / id bị xóa.

Vì vậy, như bobs đã lưu ý, MySQL sẽ phải tìm nạp 10000 hàng (hoặc duyệt qua 10000 mục của chỉ mục trên id) trước khi tìm thấy 30 để trả về.

EDIT : Để minh họa quan điểm của tôi

Lưu ý rằng mặc dù

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

sẽ chậm (er) ,

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

sẽ nhanh (er) và sẽ trả về kết quả tương tự với điều kiện là không thiếu ids (tức là khoảng trống).


2
Chính xác. Nhưng vì nó bị giới hạn bởi "id", tại sao phải mất quá lâu khi id đó nằm trong một chỉ mục (khóa chính)? Trình tối ưu hóa nên tham khảo trực tiếp chỉ mục đó và sau đó tìm nạp các hàng có id khớp (xuất phát từ chỉ mục đó)
Rahman

1
Nếu bạn đã sử dụng mệnh đề WHERE trên id, nó có thể đi đúng đến dấu đó. Tuy nhiên, nếu bạn đặt giới hạn cho nó, được sắp xếp theo id, thì đó chỉ là một bộ đếm tương đối so với ban đầu, vì vậy nó phải vượt qua toàn bộ cách.
Riedsio

Bài viết rất hay eversql.com/ Kẻ
Pažout

Làm việc cho tôi @Riedsio Cảm ơn.
mahesh kajale

8

Tôi đã tìm thấy một ví dụ thú vị để tối ưu hóa các truy vấn CHỌN ĐẶT HÀNG B idNG id LIMIT X, Y. Tôi có 35 triệu hàng nên mất 2 phút để tìm một loạt các hàng.

Đây là mẹo:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

Chỉ cần đặt WHERE với id cuối cùng bạn đã tăng hiệu suất rất nhiều. Đối với tôi đó là từ 2 phút đến 1 giây :)

Các thủ thuật thú vị khác tại đây: http://www.iheavy.com/2013/06/19/3-ways-to-optizes-for-paging-in-mysql/

Nó cũng hoạt động với chuỗi


1
cái này chỉ hoạt động cho các bảng, trong đó không có dữ liệu nào bị xóa
miro

1
@miro Điều đó chỉ đúng nếu bạn đang làm việc theo giả định rằng truy vấn của bạn có thể thực hiện tra cứu tại các trang ngẫu nhiên, điều mà tôi không tin rằng áp phích này là giả định. Mặc dù tôi không thích phương pháp này cho hầu hết các trường hợp trong thế giới thực, nhưng nó sẽ hoạt động với các khoảng trống miễn là bạn luôn căn cứ vào id cuối cùng thu được.
Gremio

5

Phần tốn thời gian của hai truy vấn là lấy các hàng từ bảng. Nói một cách logic, trong LIMIT 0, 30phiên bản, chỉ cần lấy 30 hàng. Trong LIMIT 10000, 30phiên bản, 10000 hàng được ước tính và 30 hàng được trả về. Có thể có một số tối ưu hóa có thể được thực hiện trong quá trình đọc dữ liệu của tôi, nhưng hãy xem xét những điều sau:

Điều gì nếu bạn có một mệnh đề WHERE trong các truy vấn? Công cụ phải trả về tất cả các hàng đủ điều kiện, sau đó sắp xếp dữ liệu và cuối cùng nhận được 30 hàng.

Cũng xem xét trường hợp các hàng không được xử lý trong chuỗi ORDER BY. Tất cả các hàng đủ điều kiện phải được sắp xếp để xác định hàng nào sẽ trả về.


1
chỉ tự hỏi tại sao nó tiêu tốn thời gian để lấy 10000 hàng đó. Chỉ mục được sử dụng trên trường đó (id, là khóa chính) sẽ giúp truy xuất các hàng đó nhanh như tìm kiếm chỉ mục PK đó để ghi không. 10000, điều này được cho là nhanh chóng khi tìm kiếm tệp tới phần bù đó nhân với độ dài bản ghi chỉ mục, (nghĩa là tìm 10000 * 8 = byte no 80000 - với điều kiện 8 là độ dài bản ghi chỉ mục)
Rahman

@Rahman - Cách duy nhất để vượt qua 10000 hàng là lần lượt bước qua chúng. Điều này có thể chỉ liên quan đến một chỉ mục, nhưng các hàng chỉ mục vẫn cần thời gian để bước qua. Không cấu trúc MyISAM hoặc InnoDB có thể "tìm kiếm" chính xác (trong mọi trường hợp) "tìm kiếm" để ghi 10000. Đề xuất 10000 * 8 giả định (1) MyISAM, (2) bản ghi độ dài CỐ ĐỊNH và (3) không bao giờ xóa bất kỳ bảng nào . Dù sao, chỉ số MyISAM là BTrees, vì vậy nó sẽ không hoạt động.
Rick James

Như câu trả lời này đã nêu, tôi tin rằng, phần thực sự chậm là tra cứu hàng, không đi qua các chỉ mục (tất nhiên cũng sẽ bổ sung, nhưng không ở đâu nhiều như tra cứu hàng trên đĩa). Dựa trên các truy vấn giải pháp được cung cấp cho vấn đề này, tôi tin rằng việc tra cứu hàng có xu hướng xảy ra nếu bạn đang chọn các cột bên ngoài chỉ mục - ngay cả khi chúng không phải là một phần của mệnh đề theo hoặc mệnh đề. Tôi đã không tìm thấy một lý do tại sao điều này là cần thiết, nhưng dường như đó là lý do tại sao một số cách giải quyết giúp đỡ.
Gremio

1

Đối với những người quan tâm đến một so sánh và số liệu :)

Thí nghiệm 1: Tập dữ liệu chứa khoảng 100 triệu hàng. Mỗi hàng chứa một số BIGINT, TINYINT, cũng như hai trường văn bản (cố ý) chứa khoảng 1k ký tự.

  • Màu xanh: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Cam: = @ Phương pháp của Quassnoi. SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • Tất nhiên, phương pháp thứ ba, ... WHERE id>xxx LIMIT 0,5không xuất hiện ở đây vì nó phải là thời gian không đổi.

Thí nghiệm 2: Điều tương tự, ngoại trừ một hàng chỉ có 3 BIGINT.

  • màu xanh lá cây: = màu xanh trước
  • đỏ: = cam trước

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.