Tại sao mysql sử dụng chỉ mục sai cho thứ tự theo truy vấn?


9

Đây là bảng của tôi với ~ 10.000.000 hàng dữ liệu

CREATE TABLE `votes` (
  `subject_name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
  `subject_id` int(11) NOT NULL,
  `voter_id` int(11) NOT NULL,
  `rate` int(11) NOT NULL,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`subject_name`,`subject_id`,`voter_id`),
  KEY `IDX_518B7ACFEBB4B8AD` (`voter_id`),
  KEY `subject_timestamp` (`subject_name`,`subject_id`,`updated_at`),
  KEY `voter_timestamp` (`voter_id`,`updated_at`),
  CONSTRAINT `FK_518B7ACFEBB4B8AD` FOREIGN KEY (`voter_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Đây là chỉ số chính

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

Vì vậy, khi tôi thực hiện truy vấn này:

SELECT SQL_NO_CACHE * FROM votes WHERE 
    voter_id = 1099 AND 
    rate = 1 AND 
    subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;

Tôi đã mong đợi nó sử dụng chỉ mục voter_timestamp nhưng mysql chọn sử dụng thay thế này:

explain select SQL_NO_CACHE * from votes  where subject_name = 'medium' and voter_id = 1001 and rate = 1 order by updated_at desc limit 20 offset 100;`

type:
    index_merge
possible_keys: 
    PRIMARY,IDX_518B7ACFEBB4B8AD,subject_timestamp,voter_timestamp
key:
    IDX_518B7ACFEBB4B8AD,PRIMARY
key_len:
    102,98
ref:
    NULL
rows:
    9255
filtered:
    10.00
Extra:
    Using intersect(IDX_518B7ACFEBB4B8AD,PRIMARY); Using where; Using filesort

Và tôi đã nhận được 200-400ms thời gian truy vấn.

Nếu tôi buộc nó sử dụng đúng chỉ mục như:

SELECT SQL_NO_CACHE * FROM votes USE INDEX (voter_timestamp) WHERE 
    voter_id = 1099 AND 
    rate = 1 AND 
    subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;

Mysql có thể trả về kết quả sau 1-2ms

và đây là lời giải thích:

type:
    ref
possible_keys:
    voter_timestamp
key:
    voter_timestamp
key_len:
    4
ref:
    const
rows:
    18714
filtered:
    1.00
Extra:
    Using where

Vậy tại sao mysql không chọn voter_timestampchỉ mục cho truy vấn ban đầu của tôi?

Những gì tôi đã thử là analyze table votes, optimize table votesbỏ chỉ mục đó và thêm lại, nhưng mysql vẫn sử dụng chỉ mục sai. không hiểu vấn đề là gì


1
@ ypercubeᵀᴹ Tôi không nghĩ cần phải lập chỉ mục tất cả các cột trong điều kiện ở đâu, như bạn thấy nếu tôi buộc phải sử dụng chỉ mục (Voteer_id, update_at), nó có thể sử dụng nó và rất hiệu quả. Nếu tôi loại bỏ subject_name = "medium"phần đó, nó cũng có thể chọn đúng chỉ mục, không cần lập chỉ mụcrate
Phoenix

Tuy nhiên, chỉ số 4 cột sẽ hiệu quả hơn so với 2 (voter_id, updated_at). Một chỉ số khác sẽ là (voter_id, subject_name, updated_at)hoặc (subject_name, voter_id, updated_at)(không có tỷ lệ).
ypercubeᵀᴹ

1
Và vâng, bạn đang - trên một số điểm - đúng. Bạn không cần chỉ số 4 cột. Đây chỉ là chỉ mục tốt nhất có thể cho truy vấn này. Cột 2 (mà bạn nghĩ là "đúng") có thể ok cho dữ liệu và phân phối bạn hiện có. Với một bản phân phối khác, nó có thể là khủng khiếp. Ví dụ: Giả sử 99% các hàng có tỷ lệ> 1 và chỉ 1% có tỷ lệ = 1. Bạn có nghĩ rằng sử dụng chỉ số 2 cột sẽ hiệu quả?
ypercubeᵀᴹ

Nó sẽ phải đi qua một phần lớn của chỉ mục và thực hiện hàng ngàn lần tra cứu trên bảng, chỉ để tìm tỷ lệ đó> 1 và từ chối các hàng, cho đến khi tìm thấy 120 phù hợp với tiêu chí mà chỉ số không thể đánh giá được ( subject_name='medium' and rate=1)
ypercubeᵀᴹ

ypercube, Phoenix - MySQL sẽ không nhận được LIMIThoặc thậm chí ORDER BYtrừ khi chỉ mục đầu tiên thỏa mãn tất cả các bộ lọc. Nghĩa là, không có 4 cột đầy đủ, nó sẽ thu thập tất cả các hàng có liên quan, sắp xếp tất cả chúng, sau đó chọn ra LIMIT. Với chỉ mục 4 cột, truy vấn có thể tránh sắp xếp và dừng lại sau khi chỉ đọc các LIMIThàng.
Rick James

Câu trả lời:


5

MySQL đang sử dụng một mô hình chi phí tương đối đơn giản (đơn giản hơn các RDBMS khác) để lập kế hoạch truy vấn trong đó lọc dữ liệu của bạn có mức độ ưu tiên khá cao. Trong truy vấn đầu tiên của bạn với chỉ mục hợp nhất, người ta ước tính rằng việc quét ~ 9000 hàng sẽ là cần thiết trong khi truy vấn thứ hai với gợi ý chỉ số sẽ yêu cầu 18000. Đặt cược của tôi là điều này sẽ cân nhắc trong việc tính toán đủ để di chuyển thang đo về phía hợp nhất . Bạn có thể xác nhận điều này (hoặc tìm lý do khác) bằng cách bật optimizer_trace, chạy truy vấn của bạn và đánh giá kết quả.

set global optimizer_trace='enabled=on';

-- run your query 

SELECT SQL_NO_CACHE * FROM votes WHERE 
    voter_id = 1099 AND 
    rate = 1 AND 
    subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;

select * from information_schema.`OPTIMIZER_TRACE`;

Một nhận xét về index_merge: trong hầu hết các trường hợp, bạn sẽ thấy rằng nó khá đắt. Mặc dù rất hữu ích cho các kịch bản loại OLAP nhưng nó có thể không phù hợp lắm với OLTP vì thao tác có thể mất thời gian đáng kể cho truy vấn của bạn và như bạn có thể thấy đôi khi kế hoạch thực hiện dưới mức tối ưu thực sự nhanh hơn.

May mắn thay, MySQL cung cấp các công tắc để tối ưu hóa để bạn có thể tùy chỉnh nó theo ý muốn.

Đối với tất cả các tùy chọn bạn có thể chạy:

show global variables like 'optimizer_switch';

Để thay đổi, bạn không cần phải sao chép toàn bộ chuỗi. Nó hoạt động như dict.update()trong python.

 set global optimizer_switch='index_merge=off';

Nếu có thể tôi cũng sẽ xem xét cấu trúc bảng của bạn và cải thiện. Có một khóa chính ~ 100 byte với nhiều khóa phụ không thực sự được khuyến khích.

Bạn có bốn khóa phụ và một số trong số chúng là không cần thiết, ví dụ (voter_id)chỉ mục là tập con của(voter_id, updated_at)


"Giao nhau hợp nhất chỉ mục" hiếm khi được sử dụng bởi MySQL. Trong mọi trường hợp, có lẽ tốt hơn là có một chỉ mục có nhiều cột hơn. "Liên minh hợp nhất chỉ mục" đôi khi hữu ích; biến ORthành UNIONthường là tốt hoặc tốt hơn.
Rick James

5

Đối với truy vấn đó, bạn cần chỉ mục này:

INDEX(voter_id, rate, subject_name, updated_at)

Các updated_atphải làm người rốt; ba cái còn lại có thể theo thứ tự bất kỳ. (các chỉ mục 3 cột của ypercube không hữu ích lắm vì chúng không hoàn thành các WHEREcột trước khi nhấn ORDER BYcột.)

Khi bạn thêm chỉ mục này, bạn có thể thoát khỏi tất cả các khóa phụ khác:

KEY IDX_518B7ACFEBB4B8AD( voter_id), - Các FK có thể sử dụng KEY chỉ số của tôi subject_timestamp( subject_name, subject_id, updated_at), - chủ yếu là dư thừa KEY voter_timestamp( voter_id, updated_at), - có thể là nỗ lực của bạn

Với chỉ số 4 cột, bạn có cơ hội tối ưu hóa "phân trang" và tránh OFFSET. Xem blog này.

Ở một chủ đề khác ... Khi tôi thấy X_nameX_id, tôi cho rằng "bình thường hóa" đang diễn ra. Tôi hy vọng sẽ thấy hai cột đó trong một bảng, mà hầu như không có gì khác. Tôi sẽ không mong đợi để xem cả hai trong một số bảng khác.

(voter_id, updated_at)sẽ không vượt qua voter_idvì nó chưa kết thúc với quá trình lọc ( WHERE). Sau đó, vì một chỉ số khác nhỏ hơn, nó được chọn. Của tôi có 3 cột để chăm sóc lọc, sau đó là cột cho ORDER BY.

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.