使用LIMIT选项进行MySQL RAND()优化

时间:2015-05-16 15:45:13

标签: mysql sql optimization random

我在表中有50,000行,我正在运行以下查询,但我听说这是一个坏主意,但我如何让它更好地工作?

mysql> SELECT t_dnis,account_id FROM mytable WHERE o_dnis = '15623157085' AND enabled = 1 ORDER BY RAND() LIMIT 1;
+------------+------------+
| t_dnis     | account_id |
+------------+------------+
| 5623157085 | 1127       |
+------------+------------+

我可以做的任何其他方式是查询更快或用户其他选项?

我不是DBA,如果以前问过这个问题,我很抱歉:(

注意:目前我们没有看到性能问题,但我们正在增长,因此可能会影响未来,所以只是想知道+-点以前是不合格的。

3 个答案:

答案 0 :(得分:1)

此查询:

SELECT t_dnis, account_id
FROM mytable
WHERE o_dnis = '15623157085' AND enabled = 1
ORDER BY RAND()
LIMIT 1;

排序50,000行。它正在排序与WHERE子句匹配的行数。正如您在评论中所述,这是低两位数。在少数几行中,使用ORDER BY rand()不会对性能产生太大影响。

想要一个索引。最佳指数为mytable(o_dnis, enabled, t_dnis, account_id)。这是查询的覆盖索引,因此不需要访问原始数据页。

在大多数情况下,我希望ORDER BY可以达到至少几百行,如果不是几千行。当然,这取决于很多因素,例如响应时间要求,运行的硬件以及运行的并发查询数。我的猜测是,您当前的数据/配置不会造成性能问题,并且数据增长有足够的空间而不会出现问题。

答案 1 :(得分:0)

ORDER BY RAND() LIMIT 1的问题在于MySQL将为每一行提供一个随机值并进行排序,执行全表扫描而不是丢弃所有结果。

这对于有很多行的表来说特别糟糕,执行像

这样的查询
SELECT * FROM foo ORDER BY RAND() LIMIT 1

但是,在您的情况下,查询已经在o_dnisenabled上进行了过滤。如果只有有限数量的行匹配(例如几百行),那么执行ORDER BY RAND()不会导致性能问题。

替代方案需要两个查询。一个要计数,另一个要取。

伪代码中的

count = query("SELECT COUNT(*) FROM mytable WHERE o_dnis = '15623157085' AND enabled = 1").value
offset = random(0, count - 1)
result = query("SELECT t_dnis, account_id FROM mytable WHERE o_dnis = '15623157085' AND enabled = 1 LIMIT 1 OFFSET " + offset).row

注意:为了使伪代码表现良好,o_dnis, enabled上需要有一个(多列)索引。

答案 2 :(得分:0)

除非您在非常慢的硬件上运行,否则您不应该在排序(多于少于50,000行)时遇到问题。所以,如果你仍然问这个问题,这让我怀疑你的问题不在于兰德()。

例如,一个可能导致缓慢的原因可能是没有合适的索引 - 在这种情况下,您可以选择覆盖索引:

CREATE INDEX mytable_ndx ON enabled, o_dnis, t_dnis, account_id;

或基本

CREATE INDEX mytable_ndx ON enabled, o_dnis;

此时你应该已经有了很好的表现。

否则,您可以通过计算行或仅启动缓存来运行查询两次。选择哪个取决于数据结构和返回的行数;通常,COUNT选项是最安全的选择。

SELECT COUNT(1) AS n FROM mytable WHERE ...

给你n,它允许你在与n相同的范围内生成一个随机数k,然后是

SELECT ... FROM mytable LIMIT k, 1

应该非常快。同样,索引将帮助您加快计数操作。

在某些情况下(仅限MySQL)你可以用

做得更好
SELECT SQL_CACHE SQL_CALC_FOUND_ROWS ... FROM mytable WHERE ...

使用calc_found_rows()函数恢复n,然后运行利用缓存的第二个查询。不过,如果你先试验一下,这是最好的。表格人口统计信息的变化可能会导致性能下降。