【问题标题】:Need advice optimizing SQL query (update on MySQL)需要优化 SQL 查询的建议(在 MySQL 上更新)
【发布时间】:2012-04-24 16:37:44
【问题描述】:

我使用慢查询日志对我的数据库进行了性能分析。原来这是第一大烦恼:

UPDATE
    t1
SET
  v1t1 =
  (
    SELECT
        t2.v3t2
    FROM
        t2
    WHERE
        t2.v2t2 = t1.v2t1
    AND t2.v1t2 <= '2012-04-24'
    ORDER BY
        t2.v1t2 DESC,
        t2.v3t2 DESC
    LIMIT 1
);

子查询本身已经很慢了。我尝试了 DISTINCT、GROUP BY 和更多子查询的变体,但在 4 秒内没有执行任何操作。例如下面的查询

SELECT v2t2, v3t2
FROM t2
WHERE t2.v1t2 <= '2012-04-24'
GROUP BY v2t2
ORDER BY v1t2 DESC    

需要:

mysql> SELECT ...
...    
69054 rows in set (5.61 sec)    

mysql> EXPLAIN SELECT ...
+----+-------------+-------------+------+---------------+------+---------+------+---------+----------------------------------------------+
| id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows    | Extra                                        |
+----+-------------+-------------+------+---------------+------+---------+------+---------+----------------------------------------------+
|  1 | SIMPLE      | t2          | ALL  | v1t2          | NULL | NULL    | NULL | 5203965 | Using where; Using temporary; Using filesort |
+----+-------------+-------------+------+---------------+------+---------+------+---------+----------------------------------------------+

mysql> SHOW CREATE TABLE t2;
...
  PRIMARY KEY (`v3t2`),
  KEY `v1t2_v3t2` (`v1t2`,`v3t2`),
  KEY `v1t2` (`v1t2`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8  

SELECT COUNT(*) FROM t1;
+----------+
| COUNT(*) |
+----------+
|    77070 |
+----------+

SELECT COUNT(*) FROM t2;
+----------+
| COUNT(*) |
+----------+
|  5203965 |
+----------+

我正在尝试获取最新条目 (v3t2) 及其父条目 (v2t2)。应该没什么大不了的吧?有没有人建议我应该转动哪些旋钮?非常感谢任何帮助或提示!

这应该是更合适的 SELECT 语句:

SELECT
    t1.v2t1,
  (
    SELECT
        t2.v3t2
    FROM
        t2
    WHERE
        t2.v2t2 = t1.v2t1
    AND t2.v1t2 <= '2012-04-24'
    ORDER BY
        t2.v1t2 DESC,
        t2.v3t2 DESC
    LIMIT 1
) AS latest   
FROM
    t1

【问题讨论】:

  • 您的子查询在PRIMARY KEY (v2t2) 上进行过滤。为什么ORDER BYLIMIT 在里面?您确定您发布的查询和表定义正确吗?
  • 您的第二个 SELECT 与您的问题 SELECT 完全不同(没有 LIMIT、没有 JOIN 等)。优化器别无选择,只能进行表扫描,因为您有效地要求了所有行。尝试对实际的问题查询进行 EXPLAIN。
  • 这些名字v1t1, v2t1, v1t3, ..., v7t3看得我眼花。
  • @MarkBrackett 在我的 MySQL 版本中,我无法对 UPDATE 语句执行 EXPLAIN。这就是为什么我尝试构建一个结果与我的子查询中的结果相似的查询。
  • @openHaus - 在您的 UPDATE 语句中解释 SELECT 子查询。您的“相似”查询缺少子查询的许多运算符,这是必须进行任何优化的地方。

标签: mysql sql optimization indexing


【解决方案1】:

您的ORDER BY ... LIMIT 1 正在强制数据库对表执行全面扫描以仅返回 1 行。它看起来非常适合索引。

在构建索引之前,通过运行检查文件的选择性:

SELECT count(*), count(v1t2), count(DISTINCT v1t2) FROM t2;

如果您的列中有大量非NULL 值,并且不同值的数量超过非NULLs 的40%,那么构建索引是一件好事。

如果索引没有帮助,您应该分析列中的数据。您正在使用t2.v1t2 &lt;= '2012-04-24' 条件,如果您的表中有一组历史记录,则不会给计划者任何帮助,因为所有行都应该是过去的,因此完全扫描是最佳选择反正。因此,indexe 是无用的。

您应该做的是考虑如何以某种方式重写您的查询,即只检查有限的记录子集。您的构造 ORDER BY ... DESC LIMIT 1 表明您可能想要最新的条目到 '2012-04-24'(包括)。为什么不尝试将查询重写为:

SELECT v2t2, v3t2
FROM t2
WHERE t2.v1t2 => date_add('2012-04-24' interval '-10' DAY)
GROUP BY v2t2
ORDER BY v1t2 DESC;

这只是一个示例,了解您的数据库的设计和数据的性质可以构建更精确的查询。

【讨论】:

  • 为什么要在 PRIMARY KEY 上分组?
  • @Quassnoi 我的错。主键是 v3t2
【解决方案2】:

我会看看为子选择 t2 构建的索引。由于排序,您应该有一个 v2t2 的索引,可能还有一个 v1t2 和 v3t2 的索引。索引应该减少子选择在更新查询中使用它们之前查找结果的时间。

【讨论】:

    【解决方案3】:

    这样效果更好吗?通过正在使用的键摆脱其中一种排序和组。

    UPDATE
        t1
    SET
      v1t1 =
      (
        SELECT
            MAX(t2.v3t2)
        FROM
            t2
        WHERE
            t2.v2t2 = t1.v2t1
        AND t2.v1t2 <= '2012-04-24'
        GROUP BY t2.v1t2
        ORDER BY t2.v1t2 DESC
        LIMIT 1
    );
    

    替代版本

    UPDATE `t1`
    SET `v1t1` = (
      SELECT MAX(`t2`.`v3t2`)
      FROM `t2`
      WHERE `t2`.`v2t2` = `t1`.`v2t1`
      AND `t2`.`v1t2` = (
        SELECT MAX(`t2`.`v1t2`)
        FROM `t2`
        WHERE `t2`.`v2t2` = `t1`.`v2t1
        AND `t2`.`v1t2` <= '2012-04-24'
        LIMIT 1
      )
      LIMIT 1
    );
    

    并将此索引添加到t2

    KEY `v2t2_v1t2` (`v2t2`, `v1t2`)
    

    【讨论】:

    • 我认为您在 GROUP BY 中有错字?不应该是t2.v2t2吗?
    • @OpenHaus,不,我输入正确(尽管它可能不起作用)。它背后的想法是您之前通过v1t2 订购,然后通过v3t2 订购。因此,您确实需要订购v1t2,即使您按它进行分组。 GROUP BY 是在排序发生之前缩小结果(应该更快)。然后,MAX() 用于获取最大v1t2 值组中最大的v3t2 值。
    • @OpenHaus,我还添加了另一个使用第二个SELECT 语句的查询版本。我建议尝试每一个并使用更快的:)
    • 查询正常,1594 行受影响(8 分 1.46 秒)匹配行:77076 更改:1594 警告:0
    • @OpenHaus,这是第一个查询还是备用查询?另外,请添加答案中列出的索引。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-02
    • 1970-01-01
    • 2013-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多