MySQL 中通过复杂表达式获取 N 行顺序的有效方法
Efficient way to get N rows order by complex expression in MySQL
我有一个tableline_item { id: int, price: decimal, quantity: int, [other:...] }
。这个 table 非常大,大约。 2800 万行。现在我想获得前 1000 行 order by f(price, quantity, [other...])
,f
是一个任意函数。最好的方法是什么?
我想到了 2 个解决方案:
- 使用
order by
和 limit
。这种方式可能很慢,因为我认为 MySQL 计算每一行的 f
结果然后对它们进行排序。
- 创建新列来存储函数
f
的结果。这种方式不利于可扩展性,因为我可能想在不同的上下文中使用多个函数 f
(f1
, f2
...)。
我真的希望有比他们更好的第三种解决方案。
我在考虑另一个选择:
创建一个只有 id 和 f 列的临时 table。
创建第二个临时 table (temp_table2) 并在其中插入以下结果:
SELECT TOP 1000 id, f
FROM temp_table
ORDER BY f
这应该 运行 比您提到的其他 2 个选项更快,因为在这里您只需要使用 2 列。
最后,您可以 select 通过将此 seoncd 临时 table 加入到您的原始 table。
来获得最终结果
SELECT line_item.* --or just the columns you need
FROM temp_table2
INNER JOIN line_item
ON temp_table2.id = line_item.id
您也可以尝试执行您提到的第一个选项,看看如果使用我建议的临时 tables 是否有明显的性能提升。
在许多情况下,使用临时 tables 可以缩短执行时间,但并非总是如此 - 所以最好是尝试两者,看看哪个效果更好。
(对不起,这是一个否定的答案,但这就是生活。)
如果您接受 "best solution" 只是您体验过的速度的两倍,那么请接受@Zsuzsa 的。
我在这里告诉你,如果不对 f(...) 做一些事情就无法对其进行优化。原因如下:
优化器没有看到 WHERE 子句,但是看到了带有表达式的 ORDER BY。因此,它意识到评估查询的唯一方法是执行 "table scan"(即读取 all 行),为每一行评估函数,保存生成一个 tmp table(有 2800 万行),对该 tmp table 进行排序,并提供 1000 行。
能否将该函数的 any 复制到 WHERE 子句中以过滤掉某些行?如果是这样,tmp table 可能会更小。或者,如果幸运的话,也许可以设计一些 INDEX,这样它就不必进行完整的 table 扫描。
您要修改所有行吗?还是这种 "write only" table?也就是说,一行一旦写入就永远不会改变吗?在此基础上,可以为所有 'old' 行预先计算 f() 吗?如果是这样,将它存储在某个地方并添加一个索引——噗!即时结果。
f() 的公共部分是对某个日期范围的测试吗? (大 tables 通常有某种日期。大 tables 上的查询通常会询问 "recent" 项目。)如果是这样,是否可以将其从 f() 中提取出来。然后我们可以考虑按日期对 table 进行分区。这样,即使在 f 中没有其他东西可以优化,"partition pruning" 也可以限制要处理的行数。
请展示 CREATE TABLE 并讨论这里的一些想法是否可行。
我有一个tableline_item { id: int, price: decimal, quantity: int, [other:...] }
。这个 table 非常大,大约。 2800 万行。现在我想获得前 1000 行 order by f(price, quantity, [other...])
,f
是一个任意函数。最好的方法是什么?
我想到了 2 个解决方案:
- 使用
order by
和limit
。这种方式可能很慢,因为我认为 MySQL 计算每一行的f
结果然后对它们进行排序。 - 创建新列来存储函数
f
的结果。这种方式不利于可扩展性,因为我可能想在不同的上下文中使用多个函数f
(f1
,f2
...)。
我真的希望有比他们更好的第三种解决方案。
我在考虑另一个选择:
创建一个只有 id 和 f 列的临时 table。
创建第二个临时 table (temp_table2) 并在其中插入以下结果:
SELECT TOP 1000 id, f
FROM temp_table
ORDER BY f
这应该 运行 比您提到的其他 2 个选项更快,因为在这里您只需要使用 2 列。
最后,您可以 select 通过将此 seoncd 临时 table 加入到您的原始 table。
来获得最终结果SELECT line_item.* --or just the columns you need
FROM temp_table2
INNER JOIN line_item
ON temp_table2.id = line_item.id
您也可以尝试执行您提到的第一个选项,看看如果使用我建议的临时 tables 是否有明显的性能提升。 在许多情况下,使用临时 tables 可以缩短执行时间,但并非总是如此 - 所以最好是尝试两者,看看哪个效果更好。
(对不起,这是一个否定的答案,但这就是生活。)
如果您接受 "best solution" 只是您体验过的速度的两倍,那么请接受@Zsuzsa 的。
我在这里告诉你,如果不对 f(...) 做一些事情就无法对其进行优化。原因如下:
优化器没有看到 WHERE 子句,但是看到了带有表达式的 ORDER BY。因此,它意识到评估查询的唯一方法是执行 "table scan"(即读取 all 行),为每一行评估函数,保存生成一个 tmp table(有 2800 万行),对该 tmp table 进行排序,并提供 1000 行。
能否将该函数的 any 复制到 WHERE 子句中以过滤掉某些行?如果是这样,tmp table 可能会更小。或者,如果幸运的话,也许可以设计一些 INDEX,这样它就不必进行完整的 table 扫描。
您要修改所有行吗?还是这种 "write only" table?也就是说,一行一旦写入就永远不会改变吗?在此基础上,可以为所有 'old' 行预先计算 f() 吗?如果是这样,将它存储在某个地方并添加一个索引——噗!即时结果。
f() 的公共部分是对某个日期范围的测试吗? (大 tables 通常有某种日期。大 tables 上的查询通常会询问 "recent" 项目。)如果是这样,是否可以将其从 f() 中提取出来。然后我们可以考虑按日期对 table 进行分区。这样,即使在 f 中没有其他东西可以优化,"partition pruning" 也可以限制要处理的行数。
请展示 CREATE TABLE 并讨论这里的一些想法是否可行。