【问题标题】:Is there anything else I can do to optimize this MySQL query?我还能做些什么来优化这个 MySQL 查询吗?
【发布时间】:2011-02-12 03:54:43
【问题描述】:

我有两个表,表 A 有 700,000 个条目,表 B 有 600,000 个条目。结构如下:

表 A:

+-----------+---------------------+------+-----+---------+----------------+
| Field     | Type                | Null | Key | Default | Extra          |
+-----------+---------------------+------+-----+---------+----------------+
| id        | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment | 
| number    | bigint(20) unsigned | YES  |     | NULL    |                | 
+-----------+---------------------+------+-----+---------+----------------+

表 B:

+-------------+---------------------+------+-----+---------+----------------+
| Field       | Type                | Null | Key | Default | Extra          |
+-------------+---------------------+------+-----+---------+----------------+
| id          | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment | 
| number_s    | bigint(20) unsigned | YES  | MUL | NULL    |                | 
| number_e    | bigint(20) unsigned | YES  | MUL | NULL    |                | 
| source      | varchar(50)         | YES  |     | NULL    |                |
+-------------+---------------------+------+-----+---------+----------------+

我正在尝试使用以下代码查找表 A 中的任何值是否存在于表 B 中:

$sql = "SELECT number from TableA";
$result = mysql_query($sql) or die(mysql_error());

while($row = mysql_fetch_assoc($result)) {
        $number = $row['number'];
        $sql = "SELECT source, count(source) FROM TableB WHERE number_s < $number AND number_e > $number GROUP BY source";
        $re = mysql_query($sql) or die(mysql_error);
        while($ro = mysql_fetch_array($re)) {
                echo $number."\t".$ro[0]."\t".$ro[1]."\n";
        }
}

我希望查询会很快,但由于某种原因,它并不快。我对选择的解释(具有特定的“数字”值)给了我以下信息:

mysql> explain SELECT source, count(source) FROM TableB WHERE number_s < 1812194440 AND number_e > 1812194440 GROUP BY source;
+----+-------------+------------+------+-------------------------+------+---------+------+--------+----------------------------------------------+
| id | select_type | table      | type | possible_keys           | key  | key_len | ref  | rows   | Extra                                        |
+----+-------------+------------+------+-------------------------+------+---------+------+--------+----------------------------------------------+
|  1 | SIMPLE      | TableB     | ALL  | number_s,number_e       | NULL | NULL    | NULL | 696325 | Using where; Using temporary; Using filesort | 
+----+-------------+------------+------+-------------------------+------+---------+------+--------+----------------------------------------------+
1 row in set (0.00 sec)

有什么我可以从中挤出的优化吗?

我尝试为同一任务编写一个存储过程,但它甚至似乎一开始就不起作用...它没有给出任何语法错误...我尝试运行它一天,它是仍在运行,感觉很奇怪。

CREATE PROCEDURE Filter() 
Begin 
  DECLARE number BIGINT UNSIGNED; 
  DECLARE x INT; 
  DECLARE done INT DEFAULT 0; 
  DECLARE cur1 CURSOR FOR SELECT number FROM TableA; 
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; 
  CREATE TEMPORARY TABLE IF NOT EXISTS Flags(number bigint unsigned, count int(11)); 
  OPEN cur1; 
  hist_loop: LOOP 
    FETCH cur1 INTO number; 
    SELECT count(*) from TableB WHERE number_s < number AND number_e > number INTO x; 
    IF done = 1 THEN 
      LEAVE hist_loop; 
    END IF; 
    IF x IS NOT NULL AND x>0 THEN 
      INSERT INTO Flags(number, count) VALUES(number, x); 
    END IF; 
  END LOOP hist_loop; 
  CLOSE cur1;
END

【问题讨论】:

  • 让我直截了当地说...您正在运行 700,001 个查询,但您对它不快感到惊讶吗?
  • 嗯..我不是说它不快..我只是想问是否有任何更多的优化可以让它更快...... :)
  • 使用$number BETWEEN number_s AND number_e会不会比较慢?
  • 并没有真正观察到性能差异。当然,我没有放时序说明,只是观察了 echo 命令的输出。
  • 如果你想知道是否有行,你可能需要一个 JOIN 查询。然后数据库可以优化 1 个查询,而不是处理 700k + 查询,这会带来所有开销。

标签: php stored-procedures mysql query-optimization


【解决方案1】:

您正在尝试查找包含一个点的区间。使用B-tree 索引(大多数数据库中的默认索引类型)并没有那么快,但是R-tree 索引对于这种查询很有效。 MySQL 不允许您直接更改索引的类型,但您可以通过 GEOMETRY 列类型强制 MySQL 使用 R-Tree。

Quassnoihis article on nested sets in MySQL 中对此进行了介绍。虽然不完全一样,但非常相似。引用文章:

还有某类任务 需要搜索所有范围 包含已知值:

* Searching for an IP address in the IP range ban list
* Searching for a given date within a date range

和其他几个。这些任务可以 通过使用 R-Tree 功能进行改进 MySQL的

【讨论】:

  • 虽然这个答案可能是问题的答案,但当有人使用像 for all records in table1 do select ... from table2 where condition based on something from table1 这样的代码时,我仍然会感到很有趣。恕我直言,加入将是一个更自然的思想方向。
  • @extraneon:确实,如果您阅读了这篇文章,您会发现这正是 Quassnoi 所建议的,例如JOIN t_hierarchy hrp ON MBRWithin(Point(0, hp.lft), hrp.sets)。虽然在这里你不想要所有的间隔 - 只知道有一个就足够了。
  • @Mark Byers:只想说一声“谢谢”。我在不到 5 秒的时间内获得了结果 :)
  • @Legend: 太好了 :) 之前花了多长时间?
  • @Mark Byers:我没有让它运行到完成。我运行了大约 2 个小时,它仍在继续。所以你的回复是救命稻草:) 实际上,我的问题完全映射到你指定的 IP 黑名单中。我试图找到包含给定 IP 的 IP 范围,但唯一的区别是我将 IP 地址存储为 BIGINT。
【解决方案2】:

在我看来,您在 number_enumber_s 列上有单独的索引,可能是使用单独的 ADD INDEX(number_e)ADD INDEX(number_s) 列创建的。

如果您添加包含这两个列的索引,您可能会获得更好的性能,因为它们都在您的查询中使用,并且 MySQL 显然没有选择使用任何一个单列索引,判断一个全表扫描会更快(如果您的查询跨越大范围的值,这并不罕见)。

ALTER TABLE tblB ADD INDEX(number_s,number_e);

之后您将不再需要单独的 number_s 索引,因为 MySQL 可以使用您刚刚创建的索引来仅针对 number_s 进行查询,因此您不妨删除该索引。

【讨论】:

  • +1 表示组合索引。没有观察到太大的差异,但我会继续尝试建议的 R-Trees。谢谢!
【解决方案3】:

首先,我假设所需的输出是将输入之间的所有“源”分组 number_e 和 number_s 及其计数。

我对语法很敏感,但您可以考虑在上面使用“BETWEEN”子句,而不是使用小于/大于运算符进行显式比较

编辑:Zombat 所说的也适用;索引也会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-19
    • 2012-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多