MySQL yêu cầu FORCE INDEX trên bảng lớn và các CHỌN đơn giản


8

Chúng tôi có một ứng dụng lưu trữ các bài viết từ các nguồn khác nhau trong bảng MySQL và cho phép người dùng truy xuất các bài viết đó theo thứ tự ngày. Các bài viết luôn được lọc theo nguồn, vì vậy, đối với CHỌN khách hàng, chúng tôi luôn có

WHERE source_id IN (...,...) ORDER BY date DESC/ASC

Chúng tôi đang sử dụng IN, vì người dùng có nhiều đăng ký (một số có hàng ngàn).

Đây là lược đồ của bảng bài viết:

CREATE TABLE `articles` (
  `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `source_id` INTEGER(11) UNSIGNED NOT NULL,
  `date` DOUBLE(16,6) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `source_id_date` (`source_id`, `date`),
  KEY `date` (`date`)
)ENGINE=InnoDB
AUTO_INCREMENT=1
CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT='';

Chúng tôi cần chỉ mục (ngày), bởi vì đôi khi chúng tôi đang chạy các hoạt động nền trên bảng này mà không lọc theo nguồn. Người dùng tuy nhiên không thể làm điều này.

Bảng này có khoảng 1 tỷ bản ghi (vâng, chúng tôi đang xem xét bảo vệ cho tương lai ...). Một truy vấn điển hình trông như thế này:

SELECT a.id, a.date, s.name
FROM articles a FORCE INDEX (source_id_date)
     JOIN sources s ON s.id = a.source_id
WHERE a.source_id IN (1,2,3,...)
ORDER BY a.date DESC
LIMIT 10

Tại sao FORCE INDEX? Bởi vì hóa ra MySQL đôi khi chọn sử dụng chỉ mục (ngày) cho các truy vấn như vậy (có thể do độ dài nhỏ hơn?) Và điều này dẫn đến việc quét hàng triệu bản ghi. Nếu chúng tôi loại bỏ FORCE INDEX trong sản xuất, các lõi CPU của máy chủ cơ sở dữ liệu của chúng tôi sẽ được tối đa hóa trong vài giây (Đó là một ứng dụng OLTP và các truy vấn như trên được thực hiện với tốc độ khoảng 2000 mỗi giây).

Vấn đề với cách tiếp cận này là một số truy vấn (chúng tôi nghi ngờ bằng cách nào đó có liên quan đến số lượng source_ids trong mệnh đề IN) thực sự chạy nhanh hơn với chỉ mục ngày. Khi chúng tôi chạy EXPLAIN trên những cái đó, chúng tôi thấy rằng chỉ mục source_id_date quét hàng chục triệu bản ghi, trong khi chỉ mục ngày chỉ quét một số hàng ngàn. Thông thường đó là cách khác, nhưng chúng ta không thể tìm thấy một mối quan hệ vững chắc.

Lý tưởng nhất là chúng tôi muốn tìm hiểu lý do tại sao trình tối ưu hóa MySQL chọn chỉ mục sai và xóa câu lệnh FORCE INDEX, nhưng một cách để dự đoán khi nào bắt buộc chỉ số ngày cũng sẽ hoạt động với chúng tôi.

Một số làm rõ:

Truy vấn SELECT ở trên được đơn giản hóa rất nhiều cho mục đích của câu hỏi này. Nó có một số THAM GIA với các bảng với khoảng 100 triệu hàng mỗi nhóm, đã tham gia PK (article_user_flags.id = article.id), điều này làm cho vấn đề trở nên trầm trọng hơn khi có hàng triệu hàng sắp xếp. Ngoài ra một số truy vấn có thêm ở đâu, ví dụ:

SELECT a.id, a.date, s.name
FROM articles a FORCE INDEX (source_id_date)
     JOIN sources s ON s.id = a.source_id
     LEFT JOIN articles_user_flags auf ON auf.article_id=a.id AND auf.user_id=1
WHERE a.source_id IN (1,2,3,...)
AND auf.starred=1
ORDER BY a.date DESC
LIMIT 10

Truy vấn này chỉ liệt kê các bài viết được gắn dấu sao cho người dùng cụ thể (1).

Máy chủ đang chạy phiên bản MySQL 5.5.32 (Percona) với XtraDB. Phần cứng là 2xE5-2620, RAM 128 GB, RAID10 4HDDx1TB với bộ điều khiển hỗ trợ Pin. Các CHỌN có vấn đề hoàn toàn bị ràng buộc CPU.

my.cnf như sau (đã xóa một số lệnh không liên quan như id máy chủ, cổng, v.v.):

transaction-isolation           = READ-COMMITTED
binlog_cache_size               = 256K
max_connections                 = 2500
max_user_connections            = 2000
back_log                        = 2048
thread_concurrency              = 12
max_allowed_packet              = 32M
sort_buffer_size                = 256K
read_buffer_size                = 128K
read_rnd_buffer_size            = 256K
join_buffer_size                = 8M
myisam_sort_buffer_size         = 8M
query_cache_limit               = 1M
query_cache_size                = 0
query_cache_type                = 0
key_buffer                      = 10M
table_cache                     = 10000
thread_stack                    = 256K
thread_cache_size               = 100
tmp_table_size                  = 256M
max_heap_table_size             = 4G
query_cache_min_res_unit        = 1K
slow-query-log                  = 1
slow-query-log-file             = /mysql_database/log/mysql-slow.log
long_query_time                 = 1
general_log                     = 0
general_log_file                = /mysql_database/log/mysql-general.log
log_error                       = /mysql_database/log/mysql.log
character-set-server            = utf8

innodb_flush_method             = O_DIRECT
innodb_flush_log_at_trx_commit  = 2
innodb_buffer_pool_size         = 105G
innodb_buffer_pool_instances    = 32
innodb_log_file_size            = 1G
innodb_log_buffer_size          = 16M
innodb_thread_concurrency       = 25
innodb_file_per_table           = 1

#percona specific
innodb_buffer_pool_restore_at_startup           = 60

Theo yêu cầu, đây là một số GIẢI THÍCH của các truy vấn có vấn đề:

mysql> EXPLAIN SELECT a.id,a.date AS date_double
    -> FROM articles a
    -> FORCE INDEX (source_id_date)
    -> JOIN sources s ON s.id = a.source_id WHERE
    -> a.source_id IN (...) --Around 1000 IDs
    -> ORDER BY a.date LIMIT 20;
+----+-------------+-------+--------+-----------------+----------------+---------+---------------------------+----------+------------------------------------------+
| id | select_type | table | type   | possible_keys   | key            | key_len | ref                       | rows     | Extra                                    |
+----+-------------+-------+--------+-----------------+----------------+---------+---------------------------+----------+------------------------------------------+
|  1 | SIMPLE      | a     | range  | source_id_date  | source_id_date | 4       | NULL                      | 13744277 | Using where; Using index; Using filesort |
|  1 | SIMPLE      | s     | eq_ref | PRIMARY         | PRIMARY        | 4       | articles_db.a.source_id   |        1 | Using where; Using index                 |
+----+-------------+-------+--------+-----------------+----------------+---------+---------------------------+----------+------------------------------------------+
2 rows in set (0.01 sec)

CHỌN thực tế mất khoảng một phút và hoàn toàn bị ràng buộc CPU. Khi tôi thay đổi chỉ mục thành (ngày) mà trong trường hợp này, trình tối ưu hóa MySQL cũng tự động chọn:

mysql> EXPLAIN SELECT a.id,a.date AS date_double
    -> FROM articles a
    -> FORCE INDEX (date)
    -> JOIN sources s ON s.id = a.source_id WHERE
    -> a.source_id IN (...) --Around 1000 IDs
    -> ORDER BY a.date LIMIT 20;

+----+-------------+-------+--------+---------------+---------+---------+---------------------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                       | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+---------------------------+------+--------------------------+
|  1 | SIMPLE      | a     | index  | NULL          | date    | 8       | NULL                      |   20 | Using where              |
|  1 | SIMPLE      | s     | eq_ref | PRIMARY       | PRIMARY | 4       | articles_db.a.source_id   |    1 | Using where; Using index |
+----+-------------+-------+--------+---------------+---------+---------+---------------------------+------+--------------------------+

2 rows in set (0.01 sec)

Và CHỌN chỉ mất 10ms.

Nhưng GIẢI THÍCH có thể bị phá vỡ rất nhiều ở đây! Ví dụ: nếu tôi GIẢI THÍCH một truy vấn chỉ có một source_id trong mệnh đề IN và chỉ mục bắt buộc vào (ngày), nó sẽ cho tôi biết rằng nó sẽ chỉ quét 20 hàng, nhưng điều đó là không thể, bởi vì bảng có hơn 1 tỷ hàng và chỉ một vài phù hợp với nguồn_id này.


"Khi chúng tôi chạy phân tích về những ..." Ý bạn là EXPLAIN? ANALYZElà một cái gì đó khác biệt, và có lẽ là một cái gì đó để xem xét nếu bạn không, vì một lời giải thích có thể là số liệu thống kê chỉ số sai lệch đang làm mất tập trung tối ưu hóa từ việc lựa chọn một cách khôn ngoan. Tôi không nghĩ rằng có bất kỳ nhu cầu nào đối với my.cnf trong câu hỏi và không gian đó có thể được sử dụng tốt hơn để đăng một số EXPLAINđầu ra của các biến thể trong hành vi mà bạn thấy ... sau khi bạn điều tra ANALYZE [LOCAL] TABLE...
Michael - sqlbot

Vâng, đây là một lỗi đánh máy, cảm ơn đã sửa chữa. Tôi đã sưa nó. Tất nhiên chúng tôi đã làm ANALYZE, nhưng điều đó không giúp được gì cả. Tôi sẽ cố gắng nắm bắt một số GIẢI THÍCH sau.
Áo khoác

dateDOUBLE...?
ypercubeᵀᴹ

Có, bởi vì chúng tôi cần độ chính xác micro giây ở đây. Tốc độ chèn tại bảng này là khoảng 400.000 mục mỗi giờ và chúng tôi cần ngày càng độc đáo càng tốt.
Áo khoác

@Jquet Bạn có thể đăng GIẢI THÍCH khỏi truy vấn vi phạm không? Tôi nghĩ bởi vì CPU bị ràng buộc bởi máy chủ của bạn đang nhanh chóng ("sử dụng tập tin giải thích)" kết quả của bạn ..
Raymond Nijland

Câu trả lời:


4

Bạn có thể kiểm tra giá trị của mình cho tham số innodb_stats_sample_pages . Nó kiểm soát số lần lặn chỉ mục mà MySQL thực hiện trên một bảng khi cập nhật thống kê chỉ mục, lần lượt được sử dụng để tính chi phí của kế hoạch tham gia ứng cử viên. Giá trị mặc định là 8 cho phiên bản chúng tôi đang sử dụng. Chúng tôi đã thay đổi nó thành 128 và quan sát thấy các kế hoạch tham gia ít bất ngờ hơn.

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.