MySQL 5.7 版与 5.6 版的性能对比

MySQL performance for version 5.7 vs. 5.6

我注意到一个我不确定如何处理的特定性能问题。

我正在将 Web 应用程序从一台服务器迁移到另一台具有非常相似规格的服务器。显然,新服务器通常优于旧服务器。

旧服务器是运行MySQL5.6.35
新服务器是 运行 MySQL 5.7.17

新旧服务器的配置几乎完全相同MySQL。 新旧服务器都是 运行 完全相同的数据库,完全复制。

有问题的 Web 应用程序是 Magento 1.9.3.2。

在Magento中,以下函数 Mage_Catalog_Model_Category::getChildrenCategories() 旨在列出给定特定类别的所有直接子类别。

在我的例子中,这个函数最终会冒泡到这个查询:

SELECT    `main_table`.`entity_id`
        , main_table.`name`
        , main_table.`path`
        , `main_table`.`is_active`
        , `main_table`.`is_anchor`
        , `url_rewrite`.`request_path`

FROM `catalog_category_flat_store_1` AS `main_table`

LEFT JOIN `core_url_rewrite` AS `url_rewrite`
ON url_rewrite.category_id=main_table.entity_id
AND url_rewrite.is_system=1
AND url_rewrite.store_id = 1
AND url_rewrite.id_path LIKE 'category/%'

WHERE (main_table.include_in_menu = '1')
AND (main_table.is_active = '1')
AND (main_table.path LIKE '1/494/%')
AND (`level` <= 2)
ORDER BY `main_table`.`position` ASC;

虽然此查询的结构对于任何 Magento 安装都是相同的,但在 Magento 安装到 Magento 安装之间的值和函数正在查看的类别之间显然会存在细微差异。

我的 catalog_category_flat_store_1 table 有 214 行。
我的 url_rewrite table 有 1,734,316 行。

这个查询,当它自己直接执行到 MySQL 时,在 MySQL 版本之间的表现非常不同。

我正在使用 SQLyog 分析此查询。

在MySQL 5.6 中,上述查询在 0.04 秒内执行。此查询的配置文件如下所示:https://codepen.io/Petce/full/JNKEpy/

在MySQL 5.7 中,上述查询在 1.952 秒内执行。此查询的配置文件如下所示:https://codepen.io/Petce/full/gWMgKZ/

如您所见,在几乎完全相同的设置上执行相同的查询实际上慢了 2 秒,我不确定为什么。

出于某种原因,MySQL 5.7 不想使用 table 索引来帮助生成结果集。

有更多人 experience/knowledge 可以解释这里发生了什么以及如何修复它吗?

我认为这个问题与 MYSQL 5.7 优化器的工作方式有关。出于某种原因,似乎认为完整的 table 扫描是可行的方法。我可以通过将 max_seeks_for_key 设置得非常低(如 100)或将 range_optimizer_max_mem_size 设置得非常低以强制它发出警告来显着提高查询性能。

执行这些操作中的任何一个都会将查询速度提高近 10 倍,降至 0.2 秒,但是,这仍然比 MYSQL 5.6 慢很多,它在 0.04 秒内执行,我不认为这些是个好主意,因为我不确定是否会有其他影响。

修改查询也非常困难,因为它是由 Magento 框架生成的,并且需要自定义我想避免的 Magento 代码库。我什至不确定它是否是唯一受到影响的查询。

我已经包含了我的 MySQL 安装的次要版本。我现在正在尝试将 MySQL 5.7.17 更新到 5.7.18(最新版本)以查看性能是否有任何更新。

升级到 MySQL 5.7.18 后,我没有看到任何改进。为了使系统恢复到 stable 高性能状态,我们决定降级回 MySQL 5.6.30。降级后,我们看到了立竿见影的改善。

上述查询在 MySQL 5.6.30 中执行,在新服务器上执行时间为 0.036 秒。

这不是仅供@Nigel Ren 评论的答案

这里可以看到LIKE也用到了索引

mysql> SELECT *
    -> FROM testdb
    -> WHERE
    -> vals LIKE 'text%';
+----+---------------------------------------+
| id | vals                                  |
+----+---------------------------------------+
|  3 | text for line number 3                |
|  1 | textline 1 we rqwe rq wer qwer q wer  |
|  2 | textline 2 asdf asd fas f asf  wer 3  |
+----+---------------------------------------+
3 rows in set (0,00 sec)

mysql> EXPLAIN
    -> SELECT *
    -> FROM testdb
    -> WHERE
    -> vals LIKE 'text%';
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | testdb | NULL       | range | vals          | vals | 515     | NULL |    3 |   100.00 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0,01 sec)

mysql>

样本与 LEFT()

mysql> SELECT *
    -> FROM testdb
    -> WHERE
    -> LEFT(vals,4) = 'text';
+----+---------------------------------------+
| id | vals                                  |
+----+---------------------------------------+
|  3 | text for line number 3                |
|  1 | textline 1 we rqwe rq wer qwer q wer  |
|  2 | textline 2 asdf asd fas f asf  wer 3  |
+----+---------------------------------------+
3 rows in set (0,01 sec)

mysql> EXPLAIN
    -> SELECT *
    -> FROM testdb
    -> WHERE
    -> LEFT(vals,4) = 'text';
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | testdb | NULL       | index | NULL          | vals | 515     | NULL |    5 |   100.00 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0,01 sec)

mysql>

哇!这是我第一次从 Profiling 中看到有用的东西。动态创建索引是 Oracle 的一项新优化功能。但看起来这不是本案的最佳方案。

首先,我建议您在 http://bugs.mysql.com 提交一个错误——他们不喜欢回归,尤其是这种令人震惊的错误。如果可能,请提供 EXPLAIN FORMAT=JSON SELECT... 和 "Optimizer trace"。 (我不接受调整晦涩的可调参数作为 acceptable 的答案,但感谢您发现它们。)

回来帮助你...

  • 如果您不需要LEFT,请不要使用它。它 returns NULLs 当 'right' table 中没有匹配的行时;你的情况会发生这种情况吗?
  • 请提供SHOW CREATE TABLE。同时,我猜你没有 INDEX(include_in_menu, is_active, path)。前两个可以是任意顺序; path 需要放在最后。
  • INDEX(category_id, is_system, store_id, id_path) 最后 id_path
  • 您的查询似乎有一种模式可以很好地转换为子查询:

(注意:这甚至保留了 LEFT 的语义。)

SELECT  `main_table`.`entity_id` , main_table.`name` , main_table.`path` ,
        `main_table`.`is_active` , `main_table`.`is_anchor` ,
        ( SELECT  `request_path`
            FROM  url_rewrite
            WHERE  url_rewrite.category_id=main_table.entity_id
              AND  url_rewrite.is_system = 1
              AND  url_rewrite.store_id  = 1
              AND  url_rewrite.id_path LIKE 'category/%' 
        ) as request_path
    FROM  `catalog_category_flat_store_1` AS `main_table`
    WHERE  (main_table.include_in_menu = '1')
      AND  (main_table.is_active = '1')
      AND  (main_table.path like '1/494/%')
      AND  (`level` <= 2)
    ORDER BY  `main_table`.`position` ASC
    LIMIT  0, 1000 

(建议的索引也适用于此。)