Tôi đang làm việc trên một truy vấn mà tôi cần phải sử dụng IN
, BETWEEN
, GROUP BY
, JOIN
, ORDER BY
tất cả trong một truy vấn. Tôi đang vật lộn với hiệu năng cho truy vấn đó, vì vậy tôi cần trợ giúp để chọn chỉ mục hoặc thực hiện thay đổi cấu trúc bảng nếu chỉ mục không giúp được.
Một số cân nhắc
- Số lượng hàng cho cả hai bảng dưới đây là trong
millions
. - Có chức năng nơi người dùng có thể lọc danh sách bằng cách
name
,age
,gender
, vv - Có chức năng nơi người dùng có thể sắp xếp danh sách theo một số số liệu như
age
,visits_count
v.v. - Cần phân trang cho danh sách.
Cấu trúc bảng
Bảng 1
CREATE TABLE `table_1` (
`visitor_id` varchar(32) CHARACTER SET ascii NOT NULL,
`name` varchar(200) NOT NULL,
`gender` varchar(1) NOT NULL DEFAULT 'M',
`mobile_number` int(10) unsigned DEFAULT NULL,
`age` tinyint(1) unsigned NOT NULL DEFAULT '1',
`visits_count` mediumint(5) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`visitor_id`),
KEY `indx_t1_test` (`visitor_id`,`visits_count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
ban 2
CREATE TABLE `table_2` (
`company_id` bigint(20) unsigned NOT NULL,
`visitor_id` varchar(32) CHARACTER SET ascii NOT NULL,
`time_duration` mediumint(5) unsigned NOT NULL DEFAULT '0',
`visited_on` date NOT NULL,
PRIMARY KEY (`company_id`,`visitor_id`,`visited_on`),
KEY `indx_t2_test` (`visited_on`,`company_id`,`visitor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Dữ liệu cơ bản nhất tôi muốn truy xuất
Muốn có 20 khách truy cập (Phân trang ) duy nhất ( GROUP BY / DISTINCT
) đã truy cập nhóm công ty cụ thể ( IN
một phần) giữa thời gian ( BETWEEN
một phần) thời gian đã chọn theo tuổi ( ORDER BY
phần) của họ.
Truy vấn 1
Truy vấn đầu tiên nếu tôi viết ra cho điều này thì nó sẽ là:
SELECT
t1.visitor_id
FROM table_1 AS t1
INNER JOIN table_2 AS t2 ON t2.visitor_id = t1.visitor_id
WHERE
t2.company_id IN (528,211,1275,521,1299,493,492,852,868,869,1235,486,485,1238,855,1237,651,538,1241,1240,548,543,1247,1253,490,468,582,583,569,477,488,802,1294,518,1274,476,545,1267,556,479,1266,1265,541,1189,1263,1152,1260,478,1257,885,1139,1256,804,708,547,561,1239,1142,1226,1148,1230,529,1223,1192,1191,874,830,822,818,817,794,718,487,709,706,705,669,513,455) AND
t2.visited_on BETWEEN '2015-01-01' AND '2017-01-31'
GROUP BY t1.visitor_id
ORDER BY t1.`visits_count` DESC
LIMIT 20;
Khi tôi chạy truy vấn này cho bất kỳ công ty nào, nó sẽ trả về dữ liệu đủ nhanh (khi số lượng hàng khớp với số lượng nhỏ, hiệu suất truy vấn tốt).
Các vấn đề là khi số lượng các công ty tăng IN
một phần của truy vấn (Tôi cần phải hỗ trợ 100 công ty cho phần này của truy vấn), phải mất thời gian về 36 seconds
với kết quả lợi nhuận.
Explain
đầu ra của truy vấn này là:
Truy vấn 2
Truy vấn thứ hai tôi có thể nghĩ cho trường hợp tương tự, sau đó sẽ là một cái gì đó như thế này:
SELECT
(
SELECT
t2.visitor_id
FROM table_2 AS t2
WHERE
t2.company_id IN (528,211,1275,521,1299,493,492,852,868,869,1235,486,485,1238,855,1237,651,538,1241,1240,548,543,1247,1253,490,468,582,583,569,477,488,802,1294,518,1274,476,545,1267,556,479,1266,1265,541,1189,1263,1152,1260,478,1257,885,1139,1256,804,708,547,561,1239,1142,1226,1148,1230,529,1223,1192,1191,874,830,822,818,817,794,718,487,709,706,705,669,513,455)
AND t2.visitor_id = t1.`visitor_id`
AND t2.visited_on BETWEEN '2015-01-01' AND '2017-01-31'
LIMIT 1
) AS visitor_id
FROM `table_1` AS t1
HAVING visitor_id IS NOT NULL
ORDER BY t1.`visits_count` DESC
LIMIT 0, 20
Hành vi của truy vấn này ngược lại với truy vấn đầu tiên. Nếu tôi chạy truy vấn cho công ty có ít khách truy cập, hiệu suất của truy vấn này rất thấp (mất khoảng một phần 38 seconds
) (chỉ một công ty IN
một phần và công ty đó chỉ có 3-4 khách truy cập). Khi số lượng công ty IN
một phần cao, nó sẽ trả về kết quả nhanh hơn so với một công ty (mất khoảng 13 seconds
), nhưng hiệu quả vẫn không thể sử dụng được.
Explain
đầu ra của truy vấn này là:
Truy vấn 3
Để loại bỏ việc sử dụng IN
một phần truy vấn, tôi đã tạo bảng tạm thời và thêm id công ty vào bảng đó và sau đó sử dụng JOIN
:
SELECT
DISTINCT
t1.visitor_id
FROM `table_1` AS t1
INNER JOIN `table_2` AS t2 ON t1.`visitor_id` = t2.visitor_id
INNER JOIN temp_table AS t3 ON t3.company_id = t2.company_id
ORDER BY t1.`visits_count` DESC
LIMIT 0, 20;
Truy vấn này cũng mất thời gian lên tới 22 giây . Tôi cần hiệu suất lên đến 2-3 seconds
danh sách này.
Thông tin thêm
innodb_buffer_pool_size
là 12 GB- RAM là 30 GB
- Tôi đang sử dụng
db.r3.xlarge
ví dụ AWS RDS SHOW TABLE STATUS
đầu ra như sau:
Truy vấn
SELECT COUNT(*) FROM table_2 WHERE company_id IN (...) AND visited_on BETWEEN '2015-01-01' AND '2017-01-31'
trả về2660123
Lần đầu tiên chỉ có nó là mất thời gian. Nếu tôi chạy lại cùng một truy vấn thì nó sẽ nhanh hơn rất nhiều (0,2 giây). Nhưng, nếu tôi thay đổi giới hạn thành
LIMIT 20, 20
lần nữa thì nó sẽ lặp lại24 seconds
lần đầu tiên và cùng lần truy vấn thứ hai sẽ nhanh hơn. Nó có thể là vìinnodb_buffer_pool_size
.Đầu ra của
EXPLAIN FORMAT=JSON SELECT ...;
như sau.
{ "query_block": { "select_id": 1, "ordering_operation": { "using_filesort": true, "grouping_operation": { "using_temporary_table": true, "using_filesort": false, "nested_loop": [ { "table": { "table_name": "t2", "access_type": "range", "possible_keys": [ "PRIMARY", "indx_t2_test" ], "key": "PRIMARY", "used_key_parts": [ "company_id" ], "key_length": "8", "rows": 17301, "filtered": 100, "using_index": true, "attached_condition": "((`db`.`t2`.`company_id` in (528,211,1275,521,1299,493,492,852,868,869,1235,486,485,1238,855,1237,651,538,1241,1240,548,543,1247,1253,490,468,582,583,569,477,488,802,1294,518,1274,476,545,1267,556,479,1266,1265,541,1189,1263,1152,1260,478,1257,885,1139,1256,804,708,547,561,1239,1142,1226,1148,1230,529,1223,1192,1191,874,830,822,818,817,794,718,487,709,706,705,669,513,455)) and (`db`.`t2`.`visited_on` between '2015-01-01' and '2017-01-31'))" } }, { "table": { "table_name": "t1", "access_type": "eq_ref", "possible_keys": [ "PRIMARY", "indx_t1_test" ], "key": "PRIMARY", "used_key_parts": [ "visitor_id" ], "key_length": "34", "ref": [ "db.t2.visitor_id" ], "rows": 1, "filtered": 100 } } ] } } } }
Đầu ra của truy vấn được đề xuất bởi Rick James:
SELECT
t2.visitor_id
FROM (
SELECT
DISTINCT visitor_id
FROM table_2
WHERE
company_id IN (528,211,1275,521,1299,493,492,852, 868,
869,1235,486,485,1238,855,1237,651,538,1241,1240, 548,
543,1247,1253,490,468,582,583,569,477,488,802,1294, 518,
1274,476,545,1267,556,479,1266,1265,541,1189,1263, 1152,
1260,478,1257,885,1139,1256,804,708,547,561,1239, 1142,
1226,1148,1230,529,1223,1192,1191,874,830,822,818, 817,
794,718,487,709,706,705,669,513,455)
AND visited_on BETWEEN '2015-01-01' AND '2017-01-31'
) AS t2
INNER JOIN table_1 AS t1 ON t2.visitor_id = t1.visitor_id
ORDER BY t1.`visits_count` DESC
LIMIT 20;
Explain
đầu ra của truy vấn như sau:
Truy vấn này mất 58 giây
Explain
đầu ra của truy vấn con bên trong như sau
Truy vấn:
SELECT
COUNT(DISTINCT company_id, visited_on, visitor_id),
COUNT(DISTINCT company_id, LEFT(visited_on, 7), visitor_id),
COUNT(*)
FROM table_2;
trả về:
COUNT(DISTINCT company_id, visited_on, visitor_id)
= 7607938.COUNT(DISTINCT company_id, LEFT(visited_on, 7), visitor_id)
= 5194480COUNT(*)
= 7607938
Lưu ý đầu ra này là với dữ liệu mới nhất, vì vậy số lượng hàng trong count(*)
có thể đã tăng lên.
limit
khóa học)? Nếu đây là một phần không thể thiếu trong mô hình kinh doanh của bạn, bạn có thể muốn nghĩ về kết quả được tính toán trước. Để kiểm tra: bạn có thể thử thêm một chỉ mục(visits_count)
trên bảng 1 và(visitor_id, company, visited_on)
trên bảng 2, thử truy vấn 1 mà không có (sai)group by
,select distinct t1.visitor_id
thay vào đó sử dụng và buộc sử dụng các chỉ mục này?