返回介绍

Max/Min 函数消除规则

发布于 2020-10-26 07:01:23 字数 5278 浏览 1134 评论 0 收藏 0

在 SQL 中包含了 max/min 函数时,查询优化器会尝试使用 max/min 消除优化规则来将 max/min 聚合函数转换为 TopN 算子,从而能够有效地利用索引进行查询。

根据 select 语句中 max/min 函数的个数,这一优化规则有以下两种表现形式:

  • 只有一个 max/min 函数时的优化规则

    当一个 SQL 满足以下条件时,就会应用这个规则:

    • 只有一个聚合函数,且为 max 或者 min 函数。
    • 聚合函数没有相应的 group by 语句。

    例如:

    select max(a) from t

    这时 max/min 消除优化规则会将其重写为:

    select max(a) from (select a from t where a is not null order by a desc limit 1) t

    这个新的 SQL 语句在 a 列存在索引(或 a 列是某个联合索引的前缀)时,能够利用索引只扫描一行数据来得到最大或者最小值,从而避免对整个表的扫描。

    上述例子最终得到的执行计划如下:

    mysql> explain select max(a) from t;
    +------------------------------+---------+-----------+-------------------------+-------------------------------------+
    | id                           | estRows | task      | access object           | operator info                       |
    +------------------------------+---------+-----------+-------------------------+-------------------------------------+
    | StreamAgg_13                 | 1.00    | root      |                         | funcs:max(test.t.a)->Column#4       |
    | └─Limit_17                   | 1.00    | root      |                         | offset:0, count:1                   |
    |   └─IndexReader_27           | 1.00    | root      |                         | index:Limit_26                      |
    |     └─Limit_26               | 1.00    | cop[tikv] |                         | offset:0, count:1                   |
    |       └─IndexFullScan_25     | 1.00    | cop[tikv] | table:t, index:idx_a(a) | keep order:true, desc, stats:pseudo |
    +------------------------------+---------+-----------+-------------------------+-------------------------------------+
    5 rows in set (0.00 sec)

    存在多个 max/min 函数时的优化规则

    当一个 SQL 满足以下条件时,就会应用这个规则:

    • 有多个聚合函数,且所有的聚合函数都是 max/min
    • 聚合函数没有相应的 group by 语句。
    • 每个 max/min 聚合函数参数中的列都有索引能够保序。

    下面是一个简单的例子:

    select max(a) - min(a) from t

    优化规则会先检查 a 列是否存在索引能够为其保序,如果存在,这个 SQL 会先被重写为两个子查询的笛卡尔积:

    select max_a - min_a
    from
        (select max(a) as max_a from t) t1,
        (select min(a) as min_a from t) t2

    这样,两个子句中的 max/min 函数就可以使用上述“只有一个 max/min 函数时的优化规则”分别进行优化,最终重写为:

    select max_a - min_a
    from
        (select max(a) as max_a from (select a from t where a is not null order by a desc limit 1) t) t1,
        (select min(a) as min_a from (select a from t where a is not null order by a asc limit 1) t) t2

    同样的,如果 a 列能够使用索引保序,那这个优化只会扫描两行数据,避免了对整个表的扫描。但如果 a 列没有可以保序的索引,这个变换会使原本只需一次的全表扫描变成两次,因此这个规则就不会被应用。

    最后得到的执行计划:

    mysql> explain select max(a)-min(a) from t;
    +------------------------------------+---------+-----------+-------------------------+-------------------------------------+
    | id                                 | estRows | task      | access object           | operator info                       |
    +------------------------------------+---------+-----------+-------------------------+-------------------------------------+
    | Projection_17                      | 1.00    | root      |                         | minus(Column#4, Column#5)->Column#6 |
    | └─HashJoin_18                      | 1.00    | root      |                         | CARTESIAN inner join                |
    |   ├─StreamAgg_45(Build)            | 1.00    | root      |                         | funcs:min(test.t.a)->Column#5       |
    |   │ └─Limit_49                     | 1.00    | root      |                         | offset:0, count:1                   |
    |   │   └─IndexReader_59             | 1.00    | root      |                         | index:Limit_58                      |
    |   │     └─Limit_58                 | 1.00    | cop[tikv] |                         | offset:0, count:1                   |
    |   │       └─IndexFullScan_57       | 1.00    | cop[tikv] | table:t, index:idx_a(a) | keep order:true, stats:pseudo       |
    |   └─StreamAgg_24(Probe)            | 1.00    | root      |                         | funcs:max(test.t.a)->Column#4       |
    |     └─Limit_28                     | 1.00    | root      |                         | offset:0, count:1                   |
    |       └─IndexReader_38             | 1.00    | root      |                         | index:Limit_37                      |
    |         └─Limit_37                 | 1.00    | cop[tikv] |                         | offset:0, count:1                   |
    |           └─IndexFullScan_36       | 1.00    | cop[tikv] | table:t, index:idx_a(a) | keep order:true, desc, stats:pseudo |
    +------------------------------------+---------+-----------+-------------------------+-------------------------------------+
    12 rows in set (0.01 sec)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文