2009-02-25 28 views
42

Tôi đang cố gắng tối ưu hóa một số truy vấn cơ sở dữ liệu trong ứng dụng Rails của mình và tôi có một số vấn đề khiến tôi bối rối. Tất cả chúng đều sử dụng một IN trong mệnh đề WHERE và tất cả đều thực hiện quét toàn bộ bảng ngay cả khi một chỉ mục thích hợp xuất hiện.MySQL không sử dụng các chỉ mục với mệnh đề WHERE IN?

Ví dụ:

SELECT `user_metrics`.* FROM `user_metrics` WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

thực hiện một bảng đầy đủ quét và GIẢI THÍCH nói:

select_type: simple 
type: all 
extra: using where 
possible_keys: index_user_metrics_on_user_id (which is an index on the user_id column) 
key: (none) 
key_length: (none) 
ref: (none) 
rows: 208 

có chỉ số không được sử dụng khi một tuyên bố IN được sử dụng hoặc làm tôi cần phải làm một cái gì đó khác nhau? Các truy vấn ở đây đang được tạo bởi Rails để tôi có thể xem lại các mối quan hệ của tôi được xác định như thế nào, nhưng tôi nghĩ tôi sẽ bắt đầu với các bản sửa lỗi tiềm năng ở cấp DB trước.

+0

Ý của bạn là gì? Chúng có phải là hằng số, cột hoặc biến không? Đó là quan trọng. – Quassnoi

+0

Xin lỗi, những người đã được dán từ đầu ra của plugin query_reviewer của tôi. Các truy vấn thực tế có số nguyên ở đó - tức là. IN (25, 26, 27) – jasonlong

+0

@blackant: bạn có chạy phân tích trên các bảng của mình không? – vladr

Trả lời

0

Liệu có tốt hơn không nếu bạn loại bỏ các dấu ngoặc thừa xung quanh mệnh đề where?

Mặc dù nó chỉ có thể là vì bạn chỉ có 200 hàng hoặc hơn, nó quyết định quét bảng sẽ nhanh hơn. Hãy thử với một bảng với nhiều hồ sơ trong đó.

+0

Bổ sung parens dường như không quan trọng. Ngoài ra, tập dữ liệu nhỏ dường như không quan trọng - tôi đã thêm 5000 hồ sơ bổ sung và vẫn quét tất cả. – jasonlong

7

Hãy thử buộc chỉ số này:

SELECT `user_metrics`.* 
FROM `user_metrics` FORCE INDEX (index_user_metrics_on_user_id) 
WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

tôi chỉ kiểm tra, nó sử dụng một chỉ mục trên chính xác cùng một truy vấn:

EXPLAIN EXTENDED 
SELECT * FROM tests WHERE (test IN ('test 1', 'test 2', 'test 3', 'test 4', 'test 5', 'test 6', 'test 7', 'test 8', 'test 9')) 

1, 'SIMPLE', 'tests', 'range', 'ix_test', 'ix_test', '602', '', 9, 100.00, 'Using where' 
+2

Đặc biệt đau đớn khi thực hiện trong Rails – vladr

+0

Vâng, điều đó dường như buộc nó phải sử dụng chỉ mục. Như Vlad nói, một nỗi đau phải làm ở Rails. – jasonlong

+0

@blackant, bạn có chạy phân tích trên các bảng không? vẫn nhận được kế hoạch giải thích tương tự? – vladr

37

Xem How MySQL Uses Indexes.

Đồng thời xác thực xem liệu MySQL vẫn thực hiện một full table scan sau khi bạn thêm một hàng bổ sung 2000 vào bảng user_metrics của mình. Trong các bảng nhỏ, truy cập theo chỉ mục thực sự đắt hơn (I/O-wise) so với quét bảng và trình tối ưu hóa của MySQL có thể tính đến điều này.

Trái với bài trước của tôi, nó chỉ ra rằng MySQL cũng là using a cost-based optimizer, đó là tin rất tốt - có nghĩa là, với điều kiện bạn chạy bạn ANALYZE ít nhất một lần khi bạn tin rằng khối lượng dữ liệu trong cơ sở dữ liệu của bạn là đại diện sử dụng hàng ngày trong tương lai.

Khi xử lý các trình tối ưu hóa dựa trên chi phí (Oracle, Postgres, v.v.), bạn cần đảm bảo chạy định kỳ ANALYZE trên các bảng khác nhau khi kích thước của chúng tăng hơn 10-15%. (Postgres sẽ tự động làm điều này cho bạn, theo mặc định, trong khi các RDBMS khác sẽ để lại trách nhiệm này cho một DBA, tức là bạn.) Thông qua phân tích thống kê, ANALYZE sẽ giúp trình tối ưu hóa hiểu rõ hơn về I/O (và các liên kết khác) tài nguyên, chẳng hạn như CPU, cần thiết, ví dụ như để sắp xếp) sẽ được tham gia khi lựa chọn giữa các kế hoạch thực thi ứng cử viên khác nhau. Thất bại trong việc chạy ANALYZE có thể dẫn đến những quyết định lên kế hoạch rất kém, đôi khi thảm hại (ví dụ như millisecond-truy vấn lấy, đôi khi, giờ vì vòng lồng nhau xấu trên JOIN s.)

Nếu hiệu suất là vẫn không đạt yêu cầu sau khi chạy ANALYZE, sau đó bạn thường sẽ có thể giải quyết vấn đề bằng cách sử dụng các gợi ý, ví dụ FORCE INDEX, trong khi trong các trường hợp khác, bạn có thể gặp phải lỗi MySQL (ví dụ: older one, có thể đã cắn bạn là bạn sử dụng Rails 'nested_set).

Bây giờ, kể từ khi bạn đang ở trong một ứng dụng Rails, nó sẽ được cồng kềnh (và đánh bại các mục đích của ActiveRecord) phát hành các truy vấn tùy chỉnh của bạn với những gợi ý thay vì tiếp tục sử dụng ActiveRecord những -generated.

tôi đã đề cập rằng trong ứng dụng Rails của chúng tôi tất cảSELECT truy vấn giảm xuống dưới 100ms sau khi chuyển sang Postgres, trong khi một số khu phức hợp tham gia tạo ra bởi ActiveRecord thỉnh thoảng sẽ mất càng nhiều càng 15s hoặc nhiều hơn với MySQL 5.1 vì vòng lồng nhau với quét bảng bên trong, ngay cả khi chỉ mục có sẵn. Không có trình tối ưu hóa nào là hoàn hảo và bạn nên biết các tùy chọn. Các vấn đề hiệu suất tiềm năng khác cần lưu ý, ngoài việc tối ưu hóa kế hoạch truy vấn, đang khóa. Điều này nằm ngoài phạm vi của vấn đề của bạn mặc dù.

+0

Cảm ơn Vlad . Tôi hy vọng sẽ giải quyết vấn đề này với quá nhiều thiết lập hiện tại của chúng tôi, nhưng tôi đánh giá cao việc nghe về thành công của bạn với Postgres. – jasonlong

+0

Tôi là Postgres thứ hai. Nó là một cơ sở dữ liệu tuyệt vời. –

+0

Xin chào, bạn đã từng thực hiện bất kỳ tiến trình nào với vấn đề MySQL này? – vladr

6

Đôi khi MySQL không sử dụng chỉ mục, ngay cả khi có sẵn chỉ mục. Một trường hợp mà điều này xảy ra là khi trình tối ưu hóa ước tính rằng việc sử dụng chỉ mục sẽ yêu cầu MySQL truy cập phần trăm rất lớn các hàng trong bảng. (Trong trường hợp này, việc quét bảng có thể nhanh hơn nhiều vì nó đòi hỏi ít người tìm kiếm hơn.)

Tỷ lệ hàng nào khớp với mệnh đề IN của bạn?

+0

Thử nghiệm ban đầu của tôi là trên một bảng chỉ có ~ 200 hàng, vì vậy tỷ lệ phần trăm tương đối cao. Tuy nhiên, tôi đã thêm 5000 hàng khác để kiểm tra và nó vẫn thực hiện quét toàn bộ bảng. Tỷ lệ bây giờ sẽ khá nhỏ. – jasonlong

+0

Theo kinh nghiệm của tôi, việc cắt giảm là _usually_ từ 10% đến 30%. –

3

Tôi biết tôi đã đến trễ bữa tiệc. Nhưng hy vọng tôi có thể giúp đỡ người khác có vấn đề tương tự.

Gần đây, tôi gặp sự cố tương tự. Sau đó, tôi quyết định sử dụng self-join-thing để giải quyết vấn đề của mình. Vấn đề không phải là MySQL. Vấn đề là chúng ta. Kiểu trả về từ truy vấn con là sự khác biệt so với bảng của chúng ta. Vì vậy, chúng tôi phải bỏ loại truy vấn phụ vào loại cột chọn. Dưới đây là mã ví dụ:

select `user_metrics`.* 
from `user_metrics` um 
join (select `user_metrics`.`user_id` in (N, N, N, N)) as temp 
on um.`user_id` = temp.`user_id` 

Hoặc mã của riêng tôi:

Cũ: (Không sử dụng chỉ mục: ~ 4s)

SELECT 
    `jxm_character`.* 
FROM 
    jxm_character 
WHERE 
    information_date IN (SELECT DISTINCT 
      (information_date) 
     FROM 
      jxm_character 
     WHERE 
      information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) 
     AND `jxm_character`.`ranking_type` = 1 
     AND `jxm_character`.`character_id` = 3146089; 

mới: (Sử dụng chỉ số: ~ 0.02s)

SELECT 
    * 
FROM 
    jxm_character jc 
     JOIN 
    (SELECT DISTINCT 
     (information_date) 
    FROM 
     jxm_character 
    WHERE 
     information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) AS temp 
     ON jc.information_date = STR_TO_DATE(temp.information_date, '%Y-%m-%d') 
     AND jc.ranking_type = 1 
     AND jc.character_id = 3146089; 

jxm_character:

  • Records: ~ 3,5 triệu
  • PK: jxm_character (information_date, ranking_type, character_id)

SHOW VARIABLES LIKE '%version%';

'protocol_version', '10' 
'version', '5.1.69-log' 
'version_comment', 'Source distribution' 

lưu ý cuối: Hãy chắc chắn rằng bạn hiểu chỉ số MySQL trái nhất quy tắc.

P/s: Xin lỗi vì tiếng Anh không tốt của tôi. Tôi gửi mã của tôi (sản xuất, tất nhiên) để xóa giải pháp của tôi: D.

+0

'IN (SELECT ...)' đã nổi tiếng vì được tối ưu hóa kém. Và bạn đã làm 'điều đúng' để biến nó thành 'JOIN'. –

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