【问题标题】:MySQL query performance: individuate and rewrite faster queryMySQL 查询性能:个性化和重写更快的查询
【发布时间】:2016-12-12 15:49:15
【问题描述】:

今天我偶然发现了同一个查询的两种不同形式(返回完全相同的结果),但执行时间却大不相同:

ORIGINAL QUERY:

select count(distinct unit.ID)
from UNIT unit 
    left outer join AUTHORIZATION auth on unit.ID=auth.UNIT_ID 
    left outer join WORKFLOW_EXECUTION exec on unit.WORKFLOW_EXECUTION_ID=exec.ID 
where 
(
    unit.RESPONSIBLE_ID=2
    and 
    (
        (
            unit.STATUS<>'CLOSED' 
            and 
            unit.EXPECTEDRELEASEDATE is not null
        ) 
        or 
        exec.ACTIVE=1
    )
)
or 
(
    exec.ACTIVE=1 
    and 
    auth.INTERVENTION=1 
    and 
    auth.SUBJECT_ID=2
);

计划:

+----+-------------+-------+------------+--------+-------------------------------------------------------------------+-------------------------------------+---------+----------------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys                                                     | key                                 | key_len | ref                              | rows   | filtered | Extra       |
+----+-------------+-------+------------+--------+-------------------------------------------------------------------+-------------------------------------+---------+----------------------------------+--------+----------+-------------+
|  1 | SIMPLE      | unit  | NULL       | ALL    | FK_UNIT_RESPONSIBLE_ID,IX_UNIT_STATUS,IX_UNIT_EXPECTEDRELEASEDATE | NULL                                | NULL    | NULL                             | 451486 |   100.00 | NULL        |
|  1 | SIMPLE      | auth  | NULL       | ref    | UK_AUTHORIZATION_UNIT_ID_SUBJECT_ID,FK_AUTHORIZATION_UNIT_ID      | UK_AUTHORIZATION_UNIT_ID_SUBJECT_ID | 9       | edea2.unit.ID                    |      1 |   100.00 | Using where |
|  1 | SIMPLE      | exec  | NULL       | eq_ref | PRIMARY                                                           | PRIMARY                             | 8       | edea2.unit.WORKFLOW_EXECUTION_ID |      1 |   100.00 | Using where |
+----+-------------+-------+------------+--------+-------------------------------------------------------------------+-------------------------------------+---------+----------------------------------+--------+----------+-------------+

持续时间:

+-------------------------+
| count(distinct unit.ID) |
+-------------------------+
|                     538 |
+-------------------------+
1 row in set (2.46 sec) 

然后,我观察到,当仅使用一个 where 谓词执行此查询时,持续时间明显缩短。

所以我有了用新风格重写它的想法:

select count(distinct unit_root.ID)
from UNIT unit_root 
where 
    unit_root.ID in 
    (
        select unit.ID
        from UNIT unit 
            left outer join WORKFLOW_EXECUTION exec on unit.WORKFLOW_EXECUTION_ID=exec.ID 
        where 
        (
            unit.RESPONSIBLE_ID=2
            and 
            (
                (
                    unit.STATUS<>'CLOSED' 
                    and 
                    unit.EXPECTEDRELEASEDATE is not null
                ) 
                or 
                exec.ACTIVE=1
            )
        )
    )
    or  
    unit_root.ID in 
    (
        select unit.ID
        from UNIT unit 
            left outer join WORKFLOW_EXECUTION exec on unit.WORKFLOW_EXECUTION_ID=exec.ID 
            left outer join AUTHORIZATION auth on unit.ID=auth.UNIT_ID 
        where 
        (
            exec.ACTIVE=1 
            and 
            auth.INTERVENTION=1 
            and 
            auth.SUBJECT_ID=2
        )
    );

计划:

+----+-------------+-----------+------------+--------+---------------------------------------------------------------------------------------------------------------------------+-----------------------------+---------+----------------------------------+--------+----------+--------------------------+
| id | select_type | table     | partitions | type   | possible_keys                                                                                                             | key                         | key_len | ref                              | rows   | filtered | Extra                    |
+----+-------------+-----------+------------+--------+---------------------------------------------------------------------------------------------------------------------------+-----------------------------+---------+----------------------------------+--------+----------+--------------------------+
|  1 | PRIMARY     | unit_root | NULL       | index  | PRIMARY,FK_UNIT_RESPONSIBLE_ID,FK_UNIT_WORKFLOW_EXECUTION_ID,IX_UNIT_EXPECTEDRELEASEDATE                                  | IX_UNIT_EXPECTEDRELEASEDATE | 6       | NULL                             | 451486 |   100.00 | Using where; Using index |
|  3 | SUBQUERY    | auth      | NULL       | ref    | UK_AUTHORIZATION_UNIT_ID_SUBJECT_ID,FK_AUTHORIZATION_UNIT_ID,FK_AUTHORIZATION_SUBJECT_ID,IX_AUTHORIZATION_INTERVENTION    | FK_AUTHORIZATION_SUBJECT_ID | 8       | const                            |      1 |    50.00 | Using where              |
|  3 | SUBQUERY    | unit      | NULL       | eq_ref | PRIMARY,FK_UNIT_WORKFLOW_EXECUTION_ID                                                                                     | PRIMARY                     | 8       | edea2.auth.UNIT_ID               |      1 |   100.00 | NULL                     |
|  3 | SUBQUERY    | exec      | NULL       | eq_ref | PRIMARY,IX_WORKFLOW_EXECUTION_ACTIVE                                                                                      | PRIMARY                     | 8       | edea2.unit.WORKFLOW_EXECUTION_ID |      1 |    26.47 | Using where              |
|  2 | SUBQUERY    | unit      | NULL       | ref    | PRIMARY,FK_UNIT_RESPONSIBLE_ID,IX_UNIT_STATUS,IX_UNIT_EXPECTEDRELEASEDATE                                                 | FK_UNIT_RESPONSIBLE_ID      | 8       | const                            | 225743 |   100.00 | NULL                     |
|  2 | SUBQUERY    | exec      | NULL       | eq_ref | PRIMARY                                                                                                                   | PRIMARY                     | 8       | edea2.unit.WORKFLOW_EXECUTION_ID |      1 |   100.00 | Using where              |
+----+-------------+-----------+------------+--------+---------------------------------------------------------------------------------------------------------------------------+-----------------------------+---------+----------------------------------+--------+----------+--------------------------+

持续时间:

+------------------------------+
| count(distinct unit_root.ID) |
+------------------------------+
|                          538 |
+------------------------------+
1 row in set (0.51 sec)

最后,问题:

  1. 为什么会有这样的差异?优化器不应该能够优化这种查询吗?
  2. 是否有无需测量执行时间或调查查询计划即可快速区分此类查询的技巧?
  3. 关于如何更快地改写风格的任何提示?

请注意,我使用的是 MySQL 5.7.10,这些查询是由 Hibernate 生成的。

谢谢

【问题讨论】:

    标签: mysql sql performance join


    【解决方案1】:

    您有太多问题,但这里有一些背景。

    首先,您对优化器的要求太多了。我没有查看您查询的详细信息,但它们在逻辑上可能并不完全相同。例如,NULL 值或表中的重复值可能会导致差异。

    其次,尽管(我和其他许多人)经常说 SQL 是一种描述性语言,而不是一种过程性语言。但是,这不会扩展到查询重写。这通常意味着优化器可以重新安排连接、使用索引、为连接和聚合选择合适的算法、决定进行过滤的最佳位置以及其他一些事情。但是,处理的基本结构已经到位。

    在提示方面。使用GROUP BYDISTINCT 会降低查询速度。不要避开他们!它们是语言的必要部分。但正如您在这两个查询中发现的那样,可以节省一些费用。另一个危险是OR,因为它可以阻止相关索引的使用。

    从子查询的逻辑复杂度来看,有可能进一步优化查询。

    最后,如果你想编写高效的查询,你不能依赖查询优化器。您需要投入工作以了解查询优化器的工作原理,以及用于查询不同组件的算法以及索引和分区等基本优化技术。

    【讨论】:

    • 谢谢,但这只是一般性建议,并不能真正回答任何具体问题。不要误会,我知道什么是关系代数以及它是如何工作的,但这里只是实现的问题。如果您能查看原始查询的详细信息(这是一个简单的查询)以及我如何以 unpacked 样式重写它,获得速度提升(同时牺牲可读性),我将不胜感激OR 子句从完整行到单个索引列。继续...
    • 我可以学习每一个 MySQL 特定优化器的细节,但这并不便宜,这就是我在这里问的原因。无论如何,我发现它在这种特定情况下“如何”工作Optimizing Subqueries with Semi-Join Transformations。不过,感谢您的建议。
    猜你喜欢
    • 2012-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-26
    • 2016-03-09
    • 1970-01-01
    • 2014-11-30
    • 1970-01-01
    相关资源
    最近更新 更多