【问题标题】:MySQL RAND() optimization with LIMIT optionMySQL RAND() 优化与 LIMIT 选项
【发布时间】:2015-07-28 10:46:27
【问题描述】:

我在表中有 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 很抱歉:(

注意:目前我们没有看到性能问题,但我们正在成长,因此可能会在未来产生影响,所以只想知道 +- 之前的点已经过时了。

【问题讨论】:

  • 在查询前加上解释,然后运行它。编辑您的问题并输入结果,否则我们只能猜测。
  • 可能没什么大不了的。有多少行匹配where 子句?
  • @GordonLinoff 目前我们没有超过 1020 但它会在未来增长

标签: mysql sql optimization random


【解决方案1】:

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 上需要有一个(多列)索引。

【讨论】:

  • 伪代码可能不会更快。它必须完全扫描“计数”行(不为以后存储任何内容),然后必须再次读取这些行的一部分以获得所需的行。
  • @RickJames 为了使伪代码更好地执行,(o_dnis, enabled) 上需要有一个(多列)索引。感谢您提请注意。
  • COUNT(*) 选择得益于 INDEX(o_dnis, enabled) 能够完全在索引中执行选择。
【解决方案2】:

这个查询:

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

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

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

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

【讨论】:

    【解决方案3】:

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

    例如,速度慢的一个可能原因可能是没有适当的索引 - 在这种情况下,您可以使用覆盖索引:

    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,然后运行 ​​应该利用缓存的第二个查询。不过,最好先进行实验。并且表格人口统计数据的变化可能会导致性能下降。

    【讨论】:

      猜你喜欢
      • 2013-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-08
      相关资源
      最近更新 更多