Tối ưu hóa điều kiện WHERE cho trường TIMESTAMP trong câu lệnh MySQL SELECT


8

Tôi đang làm việc trên một lược đồ cho một hệ thống phân tích theo dõi thời gian sử dụng và cần phải xem tổng thời gian sử dụng trong một phạm vi ngày nhất định.

Để đưa ra một ví dụ đơn giản, loại truy vấn này sẽ được chạy thường xuyên:

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Truy vấn này thường mất khoảng 7 giây trên một bảng được đông dân cư. Nó có ~ 35 triệu hàng, MyISAM trên MySQL chạy trên Amazon RDS (db.m3.xlarge).

Việc loại bỏ mệnh đề WHERE làm cho truy vấn chỉ mất 4 giây và thêm mệnh đề thứ hai (time_off> XXX) thêm 1,5 giây nữa, đưa thời gian truy vấn lên 8,5 giây.

Vì tôi biết các loại truy vấn này sẽ được thực hiện phổ biến, tôi muốn tối ưu hóa mọi thứ để chúng nhanh hơn, lý tưởng là dưới 5 giây.

Tôi đã bắt đầu bằng cách thêm một chỉ mục vào time_on và mặc dù điều đó đã tăng tốc truy vấn WHERE "=", nhưng nó không có tác dụng đối với truy vấn ">". Có cách nào để tạo một chỉ mục giúp tăng tốc các truy vấn WHERE ">" hoặc "<" không?

Hoặc nếu có bất kỳ đề xuất nào khác về hiệu suất của loại truy vấn này, vui lòng cho tôi biết.

Lưu ý: Tôi đang sử dụng trường "diff_ms" làm bước không chuẩn hóa (bằng với time_off - time_on) giúp cải thiện hiệu suất tổng hợp khoảng 30% -40%.

Tôi đang tạo chỉ mục với lệnh này:

ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;

Chạy "giải thích" trên truy vấn ban đầu (với "time_on>") cho biết time_on là "could_key" và select_type là "SIMPLE". Cột "phụ" cho biết "Sử dụng ở đâu" và "loại" là "TẤT CẢ". Sau khi chỉ mục được thêm vào, bảng cho biết "time_on" là loại khóa "MUL", có vẻ đúng vì cùng một lúc có thể xuất hiện hai lần.

Đây là lược đồ bảng:

CREATE TABLE `writetest_table` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sessionID` int(11) DEFAULT NULL,
  `time_on` timestamp NULL DEFAULT NULL,
  `time_off` timestamp NULL DEFAULT NULL,
  `diff_ms` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `time_on` (`time_on`)
) ENGINE=MyISAM AUTO_INCREMENT=50410902 DEFAULT CHARSET=latin1;

CẬP NHẬT: Tôi đã tạo chỉ mục sau dựa trên phản hồi của ypercube, nhưng điều này làm tăng thời gian truy vấn cho truy vấn đầu tiên lên khoảng 17 giây!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;

CẬP NHẬT 2: đầu ra GIẢI THÍCH

mysql> explain select sum(diff_ms) from writetest_table where time_on > '2015-07-13 15:11:56';
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| id | select_type | table               | type  | possible_keys        | key                  | key_len | ref  | rows     | Extra                    |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | writetest_table_old | index | time_on__diff_ms__ix | time_on__diff_ms__ix | 10      | NULL | 35831102 | Using where; Using index |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)

Cập nhật 3: kết quả của truy vấn được yêu cầu

mysql> SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
+---------------------+
| time_on             |
+---------------------+
| 2015-07-13 15:11:56 |
+---------------------+
1 row in set (0.01 sec)

Bạn có thực sự có null trong 2 cột này ( time_ondiff_ms) không? Điều gì xảy ra nếu bạn thêm vào truy vấn WHERE ... AND diff_ms IS NOT NULL?
ypercubeᵀᴹ

Bạn có thể vui lòng chỉ cho chúng tôi đầu ra củaSELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
ypercubeᵀᴹ

Ngoài ra phần giải thích trong bảng "Cập nhật 2" của bạn hiển thị " bảng:writetest_table_old " trong khi truy vấn có from writetest_table. Đó có phải là một lỗi đánh máy hoặc bạn chạy truy vấn trong bảng khác nhau?
ypercubeᵀᴹ 14/07/2015

Câu trả lời:


3

Tôi nghĩ rằng tôi đang bắt đầu hiểu.

Khi tôi yêu cầu bạn chạy

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

Bạn nói đó là 2015-07-13 15:11:56những gì bạn có trong WHEREmệnh đề của bạn

Khi bạn đã thực hiện truy vấn

select sum(diff_ms) from writetest_table;

Nó thực hiện quét toàn bộ bảng 35,8 triệu hàng.

Khi bạn đã thực hiện truy vấn

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Nó thực hiện quét chỉ mục đầy đủ 35,8 triệu hàng.

Hoàn toàn có ý nghĩa rằng truy vấn không có mệnh đề WHERE nhanh hơn. Tại sao ?

Quét bảng sẽ đọc 35,8 triệu hàng trong một lần truyền tuyến tính.

GIẢI THÍCH về truy vấn với WHERE cũng đã tăng 35,8 triệu hàng. Quét chỉ mục sẽ hành xử một chút khác nhau. Trong khi BTREE giữ trật tự của các phím, thật kinh khủng khi thực hiện quét phạm vi. Trong trường hợp cụ thể của bạn, bạn đang thực hiện quét phạm vi tồi tệ nhất có thể, có cùng số lượng mục BTREE như có các hàng trong bảng. MySQL phải duyệt qua các trang BTREE (ít nhất là qua các nút lá) để đọc các giá trị. Ngoài ra, time_oncột phải được so sánh dọc theo thứ tự được chỉ định bởi chỉ số. Do đó, các nút BTREE không lá cũng phải được duyệt qua.

Xin vui lòng xem bài viết của tôi trên BTREE

Nếu truy vấn là vào nửa đêm hôm nay

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

hoặc thậm chí buổi trưa ngày hôm nay

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

nó sẽ mất ít thời gian hơn

MORAL CỦA CÂU CHUYỆN: Không sử dụng mệnh đề WHERE thực hiện quét phạm vi theo thứ tự bằng với số lượng hàng trong bảng mục tiêu.


Vấn đề duy nhất của tôi là làm thế nào để đi từ đây. Tôi đã thực hiện một truy vấn với một ngày chỉ có 1 triệu hàng được lọc và tổng số chỉ mất 1 giây. Nhưng đôi khi tôi có thể phải tính tổng số tiền trên hầu hết các dữ liệu. Bất kỳ đề nghị làm thế nào để xử lý này? Tôi đã hy vọng rằng MySQL sẽ đủ thông minh để biết khi nào nên sử dụng chỉ mục và khi nào thì không, nhưng tôi đoán nó không có đủ thông tin trong trường hợp này.
Lockleyu

Tôi thực sự muốn có một số loại chỉ mục được tổ chức để làm cho các mệnh đề WHERE chỉ định phạm vi ngày nhanh chóng, có vẻ như về mặt kỹ thuật có thể thực hiện được, nhưng tôi đoán nó không được hỗ trợ.
Lockleyu

Bạn có quá nhiều dữ liệu trong một phạm vi ngắn như vậy. Không có điều khoản WHERE bao giờ có thể được bồi thường. Tại sao ? Đó không phải là chỉ số đó là vấn đề. Đó là ý kiến ​​của Trình tối ưu hóa truy vấn MySQL. Khi bạn bắt đầu tích lũy nhiều dữ liệu hơn (giả sử giá trị khoảng hai tuần), các số liệu thống kê chỉ số sẽ chững lại và bạn sẽ thấy sự cải thiện hiệu suất. Chỉ không thực hiện quét chỉ mục đầy đủ.
RolandoMySQLDBA 14/07/2015

4

Đối với truy vấn cụ thể:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

một chỉ số trên (time_on, diff_ms)sẽ là lựa chọn tốt nhất. Vì vậy, nếu truy vấn chạy thường xuyên đủ hoặc hiệu quả của nó là rất quan trọng đối với ứng dụng của bạn, hãy thêm chỉ mục này:

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(Không liên quan đến câu hỏi)
Và thực sự, hãy thay đổi công cụ của bảng thành InnoDB. Đó là năm 2015 và đám tang của MyISAM đã cách đây vài năm.
(/ rant)


Tôi đã tạo chỉ mục chính xác mà bạn đề xuất và sau đó chạy truy vấn chính xác mà bạn đã đề cập đầu tiên trong phản hồi của mình, nhưng thời gian bây giờ tệ hơn nhiều, mất khoảng 17 giây liên tục (tôi đã thử vài lần).
Lockleyu

Tôi không biết cái gì gây ra nó. Trong trường hợp có vấn đề, chỉ có 3671 giá trị riêng biệt của time_on trong bảng (điều này là do cách tập lệnh kiểm tra của tôi đang điền dữ liệu).
Lockleyu

Bạn nên thực hiện ba (3) việc: 1. chạy ALTER TABLE writetest_table DROP INDEX time_on;, 2) chạy ANALYZE TABLE writetest_table;và 3) chạy lại truy vấn. Thời gian có quay trở lại 7 giây không?
RolandoMySQLDBA

1
Bạn cũng nên chạy EXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");. Là chỉ số mới đang được sử dụng? Nếu nó không được sử dụng, tôi sẽ nói đó là dân số chính của bạn, nhất là nếu thời gian sớm nhất của bạn chỉ là một vài ngày trước. Vì số lượng hàng tăng lên trong những ngày khác biệt hơn, phân phối khóa sẽ chững lại và GIẢI THÍCH nên tốt hơn .
RolandoMySQLDBA 14/07/2015

RolandoMySQLDBA - Tôi đã thử ba bước của bạn và có thời gian quay lại 7 giây. Tôi đã giải thích và nó nói rằng chỉ số đang được sử dụng. Tôi vẫn không biết tại sao việc thêm một chỉ mục như thế này có thể khiến hiệu suất trên 2 lần trở nên tồi tệ.
Lockleyu
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.