Làm cách nào tôi có thể cải thiện tốc độ truy vấn trên bảng 20 triệu + hàng?


7

Tôi có một truy vấn được sử dụng để lấy số liệu thống kê lưu lượng truy cập internet của một số địa chỉ IP nhất định.

Có các trường địa chỉ IP riêng cho hostsvà các khối IP được gọi assignments. Dữ liệu được lưu trữ trong khoảng thời gian 5 phút.

Các kết quả truy vấn được nhóm trên cột thời gian và tổng số SUM trong và ngoài khoảng thời gian 5 phút này được sử dụng để vẽ đồ thị.

Bảng được gọi trafficvà chứa (vào cuối tháng) khoảng 21 triệu hồ sơ.

SHOW CREATE table traffic:
CREATE TABLE `traffic` (
  `type` enum('v4_assignment','v4_host','v6_subnet','v6_assignment','v6_host') NOT NULL,
  `type_id` int(11) unsigned NOT NULL,
  `time` int(32) unsigned NOT NULL,
  `bytesin` bigint(20) unsigned NOT NULL default '0',
  `bytesout` bigint(20) unsigned NOT NULL default '0',
  KEY `basic_select` (`type_id`,`time`,`type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
SELECT traffic.time, SUM(traffic.bytesin), SUM(traffic.bytesout) FROM traffic 
WHERE (
    ( traffic.type = 'v4_assignment' AND type_id IN (231, between 20 to 100 ids,265)) OR 
    ( traffic.type = 'v4_host' AND type_id IN (131, ... a lot of ids... ,1506))) 
    AND traffic.time >= 1343772000 AND traffic.time < 1346450399 
GROUP BY traffic.time
ORDER BY traffic.time;

Sau đây là explainđầu ra cho truy vấn trên:

+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
| id | select_type | table   | type  | possible_keys | key          | key_len | ref  | rows   | Extra                                        |
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
|  1 | SIMPLE      | traffic | range | basic_select  | basic_select | 8       | NULL | 891319 | Using where; Using temporary; Using filesort |
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+

show indexes from traffic;
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table   | Non_unique | Key_name     | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| traffic |          1 | basic_select |            1 | type_id     | A         |       13835 |     NULL | NULL   |      | BTREE      |         |
| traffic |          1 | basic_select |            2 | time        | A         |    18470357 |     NULL | NULL   |      | BTREE      |         |
| traffic |          1 | basic_select |            3 | type        | A         |    18470357 |     NULL | NULL   |      | BTREE      |         |
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

Truy vấn này mất từ ​​30 giây đến 30 phút để hoàn thành. Tôi hy vọng tôi có thể cải thiện mọi thứ bằng cách sử dụng các chỉ mục tốt hơn hoặc có thể sử dụng một truy vấn khác, nhưng tôi không thể tìm ra nó.

CẬP NHẬT:

Theo lời khuyên của các nhà bình luận hữu ích, tôi đã tạo khóa chính và thêm chỉ mục traffic_pk (time, type, type_id, id). Thật không may, hóa ra cardinality của chỉ mục mới này bằng / thấp hơn chỉ số ban đầu của tôi (basic_select) và MySQL vẫn sử dụng khóa gốc của tôi.

CẬP NHẬT 2: Tôi đã bỏ chỉ mục ban đầu của mình basic_selectvà bây giờ nó EXPLAINhiển thị rowsgiá trị cao hơn , nhưng ít bước hơn trong các EXTRAtrường. Ngoài ra thời gian thực hiện truy vấn đã giảm xuống dưới một phút! (vẫn còn hơi chậm, nhưng là một cải tiến lớn!).

mysql> SHOW CREATE TABLE traffic_test \G;
*************************** 1. row ***************************
       Table: traffic_test
Create Table: CREATE TABLE `traffic_test` (
  `traffic_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` enum('v4_assignment','v4_host','v6_subnet','v6_assignment','v6_host') NOT NULL,
  `type_id` int(11) unsigned NOT NULL,
  `time` int(32) unsigned NOT NULL,
  `bytesin` bigint(20) unsigned NOT NULL DEFAULT '0',
  `bytesout` bigint(20) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`time`,`type`,`type_id`,`traffic_id`),
  KEY `traffic_id_IDX` (`traffic_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24545159 DEFAULT CHARSET=latin1

Các chỉ mục trên bảng:

mysql> SHOW INDEX FROM traffic;
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table        | Non_unique | Key_name       | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| traffic_test |          0 | PRIMARY        |            1 | time        | A         |          18 |     NULL | NULL   |      | BTREE      |         |
| traffic_test |          0 | PRIMARY        |            2 | type        | A         |       38412 |     NULL | NULL   |      | BTREE      |         |
| traffic_test |          0 | PRIMARY        |            3 | type_id     | A         |    24545609 |     NULL | NULL   |      | BTREE      |         |
| traffic_test |          0 | PRIMARY        |            4 | traffic_id  | A         |    24545609 |     NULL | NULL   |      | BTREE      |         |
| traffic_test |          1 | traffic_id_IDX |            1 | traffic_id  | A         |    24545609 |     NULL | NULL   |      | BTREE      |         |
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

Ngoài ra tôi đã đơn giản hóa truy vấn bằng cách không sử dụng OR:

SELECT SQL_NO_CACHE traffic.time, SUM(traffic.bytesin), SUM(traffic.bytesout) 
FROM    traffic
WHERE traffic.type LIKE 'v4_host' AND type_id IN (131,1974,1976,1514,1516,2767,2730,2731,2732,2733,2734,2769,2994,2709,1,4613,4614,4615,4616,326,1520,2652,1518,1521,1522,1523,1524,1525,2203,1515,1513,1467,1508,1973,1510,1975,1511,1475,1476,1468,1469,1470,1471,1472,1473,1500,1507,1478,1480,1481,1482,1483,1484,1485,1479,1486,1487,1488,1489,1490,1491,1495,1499,1494,2269,1474,1519,2204,2976,1922,1493,1492,1497,1496,1498,1501,1502,1503,1526,1509,1506) 
AND traffic.time >= 1342181721 
AND traffic.time < 1343391321 
GROUP BY traffic.time ASC;

Thực thi cũ của truy vấn này:

3980 rows in set (6 min 15.27 sec)

Thời gian thực hiện mới:

3980 rows in set (24.80 sec)

GIẢI THÍCH đầu ra:

+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows     | Extra       |
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
|  1 | SIMPLE      | traffic | range | PRIMARY       | PRIMARY | 4       | NULL | 12272804 | Using where |
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+

Giá trị hàng vẫn còn khá cao. Tôi nghĩ rằng tôi có thể cải thiện điều này bằng cách chuyển đổi thứ tự typetype_idtrong chỉ mục vì chỉ có 4 loại có thể và nhiều loại_ids nữa.

Đây có phải là một giả định chính xác?


cắt ra các cột trong bảng không tương ứng với truy vấn của bạn.
Aaron Kempf

Câu trả lời:


6

1. Phân vùng bảng

Do mệnh đề [AND Traffic.time> = 1343772000 AND Traffic.time <1346450399], tôi tưởng tượng rằng bạn không bao giờ xóa dữ liệu khỏi bảng này hoặc bảng hiện đang lưu trữ dữ liệu trong nhiều tháng. Các giá trị trong cột [thời gian] dường như là dấu thời gian unix (1346450399 = Thứ Sáu, ngày 31 tháng 8 năm 2012 21:59:59 GMT) Phân vùng bảng dựa trên cột thời gian. Điều đó sẽ tăng tốc độ truy xuất dữ liệu, vì DB sẽ quét phân vùng lõi (nhanh hơn nhiều để quét toàn bộ bảng).

  • Một hướng dẫn phân vùng tuyệt vời có thể được tìm thấy ở đây: http://www.arachna.com/cont/spidaman/entry/scaling_rails_with_mysql_table
  • Bạn sẽ cần tính toán phạm vi dấu thời gian cho điều đó, nhưng điều đó không khó thực hiện.
  • Vd: (1346450399 - 1343772000) / 60/60/24 = ~ 31 ngày. Vì vậy, giá trị tối đa cho phân vùng giữ dữ liệu cho tháng chín (cũng đã 31 ngày) sẽ là: 1346450399 + (31 * 24 * 60 * 60)
  • Một máy tính unix to date có thể được tìm thấy ở đây: http://www.onlineconversion.com/unix_time.htmlm

2. Viết lại truy vấn

Vì "HOẶC" trong khối WHERE của bạn, trình tối ưu hóa sẽ chọn không sử dụng chỉ mục được xác định. Cố gắng phân chia truy vấn trong 2 lựa chọn và tạo liên kết.

SELECT 
    traffic.time, 
    SUM(traffic.bytesin), 
    SUM(traffic.bytesout) 
FROM 
    traffic 
WHERE traffic.type LIKE 'v4_assignment' 
    AND type_id IN (1,2,3,4)
    AND traffic.time >= 1343772000 AND traffic.time <= 1346450399 
GROUP BY 
    traffic.time
UNION
SELECT 
    traffic.time, 
    SUM(traffic.bytesin), 
    SUM(traffic.bytesout) 
FROM 
    traffic 
WHERE traffic.type LIKE 'v4_host' 
    AND type_id IN (5,6,7,8)
    AND traffic.time >= 1343772000 AND traffic.time <= 1346450399 
GROUP BY 
    traffic.time
ORDER BY 
    traffic.time

3. Chỉ mục mới dựa trên số liệu dữ liệu

Dựa trên đầu ra giải thích của bạn, tôi không thấy chỉ số được sử dụng. Có thể bởi vì trình tối ưu hóa quyết định rằng sẽ dễ dàng hơn (rẻ hơn) để thực hiện quét toàn bộ bảng sau đó để theo chỉ mục. Ngoài ra, trong chỉ mục hiện tại của bạn, cột đầu tiên có số lượng thẻ thấp hơn sau đó là 2 cột tiếp theo. Cột đầu tiên trong bất kỳ chỉ mục nào phải là cột có số lượng thẻ (tối đa) tốt nhất.

Tạo một chỉ mục mới như:

MYSQL> CREATE INDEX MTIhai_traffic_idx1 ON traffic(time, type, type_id)

@Steven V: Nếu bạn cần trợ giúp về phân vùng + tạo phạm vi tự động, tôi rất vui lòng hỗ trợ bạn.
MTIhai

Cảm ơn bạn @MTIhai vì phản hồi thông báo của bạn! Tôi đã có rất nhiều thông tin mới để xem xét nhưng sẽ bắt đầu bằng việc tạo chỉ mục mới vì sẽ mất vài giờ để xử lý. Nếu tôi cần giúp đỡ, tôi sẽ gửi cho bạn một tin nhắn :)
Steven V

3 khuyến nghị nên được coi là một "nhóm". Chỉ cần tạo chỉ mục mới (để phù hợp với số lượng thẻ tốt hơn) sẽ không đảm bảo rằng bạn sẽ có được tối ưu hóa rõ ràng. Làm tại danh sách R2 + R3 (Chỉ mục mới + Truy vấn với union). Ngoài ra, sau khi bạn tạo chỉ mục, vui lòng đăng kế hoạch Giải thích cho truy vấn với công đoàn (Tôi có một sự tò mò kỳ lạ :) ...)
MTIhai

Các UNIONtruy vấn không tương đương với bản gốc.
ypercubeᵀᴹ

Tôi sử dụng UNION ALLsẽ sửa lỗi này?
Steven V

4

Tôi đề nghị một chỉ số ghép trên (time, type, type_id, bytes_in, bytes_out).

Nếu sự (type_id, time, type)kết hợp là duy nhất (bằng cách nào thì khóa chính của bảng là gì?), Bạn chỉ có thể xác định khóa chính là (time, type, type_id). Sau đó, chỉ mục được nhóm của bảng sẽ là khóa chính này và bạn sẽ không cần chỉ mục ghép ở trên. Tùy thuộc vào những truy vấn phổ biến nhất của bạn là gì (nếu chúng có group by timevà / hoặc where time >=? and time <?giống như truy vấn này), chúng sẽ có sự cải thiện hiệu quả vì chúng sẽ có thể sử dụng chỉ mục được nhóm.

Bạn cũng có thể viết lại truy vấn như thế này

  • sử dụng =thay vì LIKE
  • kết hợp GROUP BYvới ORDER BY(cú pháp độc quyền của MySQL có thể cải thiện hiệu quả):

    SELECT t.time, SUM(t.bytesin), SUM(t.bytesout) 
    FROM traffic AS t 
    WHERE ( t.type = 'v4_assignment' AND t.type_id IN (231, between 20 to 100 ids,265)
         OR t.type = 'v4_host' AND t.type_id IN (131, ... a lot of ids... ,1506)
          ) 
        AND t.time >= 1343772000 AND t.time < 1346450399 
    GROUP BY t.time ASC ;

Cập nhật + chỉnh sửa

Khi bạn đã xác định không PRIMARYvà không có UNIQUEchỉ mục trên bảng (InnoDB), một cột 6 byte ẩn được tạo và được sử dụng làm chỉ mục được nhóm của bảng.

Vì vậy, có thể tốt hơn để xác định rõ ràng một cột số nguyên tăng tự động 4 byte và sử dụng nó kết hợp với timecột (hoặc tất cả 3 cột ở trên) làm một PRIMARYhoặc một UNIQUEkhóa. Không có mục đích nào khác ngoài việc có một chỉ mục được nhóm có ích cho các truy vấn của bạn:

ALTER TABLE traffic
  ADD COLUMN
    traffic_id INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
  ADD CONSTRAINT traffic_PK 
    PRIMARY KEY (time, type, type_id, traffic_id) 
  ADD INDEX traffic_id_IDX (traffic_id) ;

hoặc (để có khóa chính hẹp hơn):

ALTER TABLE traffic
  ADD COLUMN
    traffic_id INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
  ADD CONSTRAINT traffic_PK 
    PRIMARY KEY (time, traffic_id), 
  ADD INDEX traffic_id_IDX (traffic_id) ;

đề nghị khác :

Cả hai chỉ số cụm này gần tương đương với (time, type, type_id, bytes_in, bytes_out)đề xuất ở đầu.

Chỉ số khác có thể hoạt động tốt hơn là (type, type_id, time, bytes_in, bytes_out). Nhưng nó phụ thuộc vào mức độ có thể type_idcó trong các danh sách này và bao nhiêu phần trăm dữ liệu họ đề cập đến.


Cảm ơn bạn vì câu trả lời! Bảng không có khóa duy nhất, vì chúng tôi đang đo lưu lượng trên nhiều vị trí của mạng, nhiều bản ghi với kết hợp loại-type_id nhất định có thể được chèn vào mỗi khoảng thời gian. Đối với THÍCH, tôi đồng ý rằng = thuận tiện hơn, nhưng vì tôi không sử dụng bất kỳ ký tự đại diện nào, tôi cho rằng MySQL sẽ tối ưu hóa điều này.
Steven V

Bạn có thể chạy SHOW CREATE TABLE traffic ;và thêm đầu ra tại câu hỏi? (hoặc đó là những gì bạn đã làm?)
ypercubeᵀᴹ

Thật vậy, Show Tạo là những gì bạn thấy bên dưới "bảng Tạo:".
Steven V

Có đúng không, vì những gì tôi đã thấy cho đến nay, InnoDb không chấp nhận các khóa chính kết hợp với các cột tự động? Nếu vậy, tôi nghĩ chỉ số đề xuất của bạn sẽ chỉ hoạt động với MyISAM?
Steven V

Không, nó cũng hoạt động tốt với InnoDB.
ypercubeᵀᴹ
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.