Return MySQL 每组前 N 行,但效率很高
Return top N rows per group in MySQL, but efficiently
我在 MySQL 5.7.30 中有一个非常简单的 table,我将其归结为以下三列。我正在尝试为某些组 (WHERE groupable IN (3, 4, 5)
) 确定每组的前 N 个元素。但是即使对于单个组,我也无法有效地做到这一点(请参阅下面的 WHERE groupable = 3
)。
DROP TABLE IF EXISTS test;
CREATE TABLE test (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
groupable BIGINT NOT NULL,
orderable BIGINT NOT NULL,
UNIQUE INDEX test_index_1 (groupable, orderable),
UNIQUE INDEX test_index_2 (orderable, groupable),
INDEX test_index_3 (orderable),
INDEX test_index_4 (groupable)
);
INSERT INTO test(groupable, orderable) VALUES
(1, 100), (1, 101), (1, 102), (1, 103), (1, 104), (1, 105), (1, 106), (1, 107),
(2, 200), (2, 201), (2, 202), (2, 203), (2, 204), (2, 205), (2, 206), (2, 207),
(3, 300), (3, 301), (3, 302), (3, 303), (3, 304), (3, 305), (3, 306), (3, 307),
(4, 400);
EXPLAIN SELECT id FROM test
WHERE groupable = 3
ORDER BY orderable LIMIT 2;
最终EXPLAIN
returnsrows
值为8。根据documentation,“rows列表示行数MySQL 认为它必须检查才能执行查询。" 我希望有一个 (groupable, orderable)
索引可以减少使用 groupable = 3
检查每一行的需要并允许引擎直接访问最大的。不是这样吗?有解决办法吗?
我看到人们一直在问这个问题,但到目前为止我看到的所有答案似乎都有相同的缺点:检查每组的每一行。或者对于那些没有 WHERE/IN
子句的,检查整个 table.
感谢您的帮助!
注意:虽然这个例子很小,但我已经在 table 上复制了相同的内容,其中有数千个可分组项,每个可分组项有数百行。
注意 #2:为了以防万一,我添加了额外的索引,以确保我没有遗漏一些隐藏的优化。
希望您有一个维度 table,其中可分组 ID 是唯一的?
然后,我将使用连接和相关子查询。
SELECT
dim.id,
fact.*
FROM
dim_groupable AS dim
LEFT JOIN
fact_groupable AS fact
ON fact.id IN (
SELECT id
FROM fact_groupable
WHERE groupable = dim.id
ORDER BY orderable
LIMIT 2
)
然后做索引覆盖groupable, orderable, id
,这样相关的子查询就可以单独用索引来回答
如果您没有维度 table,只需使用 (SELECT DISTINCT groupable AS id FROM fact_groupable) AS dim
。但是,你真的应该有一个维度 table.
包含分组和排序列的复合索引将完全覆盖此查询。此外,mysql 将在找到 LIMIT 中指定的结果数后立即停止读取索引。
这样,查询在实际运行时不会检查所有行。 EXPLAIN 子句是一个近似值,在其对检查的 ROWS 的估计中不包括此短路 LIMIT 优化。
来自文档...
https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html
MySQL stops sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
Using index -
The column information is retrieved from the table using only information in the index tree without having to do an additional seek to read the actual row. This strategy can be used when the query uses only columns that are part of a single index.
我在 MySQL 5.7.30 中有一个非常简单的 table,我将其归结为以下三列。我正在尝试为某些组 (WHERE groupable IN (3, 4, 5)
) 确定每组的前 N 个元素。但是即使对于单个组,我也无法有效地做到这一点(请参阅下面的 WHERE groupable = 3
)。
DROP TABLE IF EXISTS test;
CREATE TABLE test (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
groupable BIGINT NOT NULL,
orderable BIGINT NOT NULL,
UNIQUE INDEX test_index_1 (groupable, orderable),
UNIQUE INDEX test_index_2 (orderable, groupable),
INDEX test_index_3 (orderable),
INDEX test_index_4 (groupable)
);
INSERT INTO test(groupable, orderable) VALUES
(1, 100), (1, 101), (1, 102), (1, 103), (1, 104), (1, 105), (1, 106), (1, 107),
(2, 200), (2, 201), (2, 202), (2, 203), (2, 204), (2, 205), (2, 206), (2, 207),
(3, 300), (3, 301), (3, 302), (3, 303), (3, 304), (3, 305), (3, 306), (3, 307),
(4, 400);
EXPLAIN SELECT id FROM test
WHERE groupable = 3
ORDER BY orderable LIMIT 2;
最终EXPLAIN
returnsrows
值为8。根据documentation,“rows列表示行数MySQL 认为它必须检查才能执行查询。" 我希望有一个 (groupable, orderable)
索引可以减少使用 groupable = 3
检查每一行的需要并允许引擎直接访问最大的。不是这样吗?有解决办法吗?
我看到人们一直在问这个问题,但到目前为止我看到的所有答案似乎都有相同的缺点:检查每组的每一行。或者对于那些没有 WHERE/IN
子句的,检查整个 table.
感谢您的帮助!
注意:虽然这个例子很小,但我已经在 table 上复制了相同的内容,其中有数千个可分组项,每个可分组项有数百行。
注意 #2:为了以防万一,我添加了额外的索引,以确保我没有遗漏一些隐藏的优化。
希望您有一个维度 table,其中可分组 ID 是唯一的?
然后,我将使用连接和相关子查询。
SELECT
dim.id,
fact.*
FROM
dim_groupable AS dim
LEFT JOIN
fact_groupable AS fact
ON fact.id IN (
SELECT id
FROM fact_groupable
WHERE groupable = dim.id
ORDER BY orderable
LIMIT 2
)
然后做索引覆盖groupable, orderable, id
,这样相关的子查询就可以单独用索引来回答
如果您没有维度 table,只需使用 (SELECT DISTINCT groupable AS id FROM fact_groupable) AS dim
。但是,你真的应该有一个维度 table.
包含分组和排序列的复合索引将完全覆盖此查询。此外,mysql 将在找到 LIMIT 中指定的结果数后立即停止读取索引。
这样,查询在实际运行时不会检查所有行。 EXPLAIN 子句是一个近似值,在其对检查的 ROWS 的估计中不包括此短路 LIMIT 优化。
来自文档... https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html
MySQL stops sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
Using index - The column information is retrieved from the table using only information in the index tree without having to do an additional seek to read the actual row. This strategy can be used when the query uses only columns that are part of a single index.