Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-34545

Nested Split Materialized causes Performance Loss with Version 11

Details

    Description

      Hello,

      Attached you will find a database dump that contains a part of my database.

      With MariaDB version 11, I am experiencing a significant performance loss with a specific query.

      With version 10.11, the following query takes only 0.036 seconds:

      SELECT 
          S.*, B.*
      FROM Teilnehmer_1 S
      INNER JOIN Buchholzpunkte B ON B.TerID = S.TerID AND B.TnNr = S.TnNr;
      

      134 rows in set (0.036 sec)

      With version 11.4.2, the same query takes over 11 seconds.

      134 rows in set (11.431 sec)

      I tested this in a virtual machine using Docker with the images: mariadb:11.4.2 and mariadb:10.11.

      If you need any further information, please let me know.

      Best regards,
      F.K

      Attachments

        1. analyze_json_2.txt
          51 kB
        2. analyze-format-json-alice-testcase.json
          10 kB
        3. dump.sql
          2.79 MB
        4. MDEV-MDEV-34545.test
          496 kB
        5. optimizer_trace_first_run.txt
          259 kB
        6. optimizer_trace_second_run.txt
          623 kB

        Activity

          We got the above results for InnoDB tables t1,t2,t3,t2c,t3c after having run ANALYZE TABLE ... PERSISTENT FOR ALL for each of these tables. If we convert these tables into MyISAM tables and run ANALYZE TABLE ... PERSISTENT FOR ALL for them we see the same problem as with InnoDB tables. The execution plans remain practically the same

          MariaDB [test]> set optimizer_switch='split_materialized=on';
          Query OK, 0 rows affected (0.000 sec)
           
          MariaDB [test]> EXPLAIN
              -> SELECT 1
              -> FROM
              -> t3
              -> JOIN 
              -> ( 
              ->   SELECT t3c.id, t3c.b, sum(dt_i.siege) 
              ->   FROM 
              ->   (
              ->     ( t3c JOIN t2c ON (t2c.id = t3c.id))
              ->     LEFT JOIN
              ->     ( 
              ->       SELECT t2.id, sum(CASE WHEN t2.a = t1.manr THEN 1 ELSE 0 END) AS siege
              ->       FROM
              ->       t1
              ->       JOIN
              ->       t2 on t1.paid = t2.paid
              ->       GROUP BY t2.id
              ->     ) dt_i
              ->     ON (dt_i.id = t3c.id)
              ->   )
              ->   GROUP BY t3c.id,t3c.b
              -> ) dt_o
              -> ON dt_o.id = t3.id AND dt_o.b = t3.b
              -> ;
          +------+-----------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+
          | id   | select_type     | table      | type   | possible_keys             | key     | key_len | ref                  | rows  | Extra                                        |
          +------+-----------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+
          |    1 | PRIMARY         | t3         | index  | id,id_2                   | id      | 8       | NULL                 | 550   | Using index                                  |
          |    1 | PRIMARY         | <derived2> | ref    | key0                      | key0    | 8       | test.t3.id,test.t3.b | 2     |                                              |
          |    2 | LATERAL DERIVED | t3c        | eq_ref | id,id_2                   | id      | 8       | test.t3.id,test.t3.b | 1     | Using index                                  |
          |    2 | LATERAL DERIVED | <derived3> | ref    | key0                      | key0    | 5       | test.t3c.id          | 3     |                                              |
          |    2 | LATERAL DERIVED | t2c        | ref    | id                        | id      | 4       | test.t3c.id          | 38    | Using index                                  |
          |    3 | DERIVED         | t1         | index  | paid,paid_2,paid_3,paid_4 | paid_4  | 5       | NULL                 | 22338 | Using index; Using temporary; Using filesort |
          |    3 | DERIVED         | t2         | eq_ref | PRIMARY,id                | PRIMARY | 4       | test.t1.paid         | 1     |                                              |
          +------+-----------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+
          7 rows in set (0.001 sec)
           
          MariaDB [test]> set optimizer_switch='split_materialized=off';
          Query OK, 0 rows affected (0.000 sec)
           
          MariaDB [test]> EXPLAIN
              -> SELECT 1
              -> FROM
              -> t3
              -> JOIN 
              -> ( 
              ->   SELECT t3c.id, t3c.b, sum(dt_i.siege) 
              ->   FROM 
              ->   (
              ->     ( t3c JOIN t2c ON (t2c.id = t3c.id))
              ->     LEFT JOIN
              ->     ( 
              ->       SELECT t2.id, sum(CASE WHEN t2.a = t1.manr THEN 1 ELSE 0 END) AS siege
              ->       FROM
              ->       t1
              ->       JOIN
              ->       t2 on t1.paid = t2.paid
              ->       GROUP BY t2.id
              ->     ) dt_i
              ->     ON (dt_i.id = t3c.id)
              ->   )
              ->   GROUP BY t3c.id,t3c.b
              -> ) dt_o
              -> ON dt_o.id = t3.id AND dt_o.b = t3.b
              -> ;
          +------+-------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+
          | id   | select_type | table      | type   | possible_keys             | key     | key_len | ref                  | rows  | Extra                                        |
          +------+-------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+
          |    1 | PRIMARY     | t3         | index  | id,id_2                   | id      | 8       | NULL                 | 550   | Using index                                  |
          |    1 | PRIMARY     | <derived2> | ref    | key0                      | key0    | 8       | test.t3.id,test.t3.b | 1577  |                                              |
          |    2 | DERIVED     | t3c        | index  | id,id_2                   | id      | 8       | NULL                 | 550   | Using index                                  |
          |    2 | DERIVED     | t2c        | ref    | id                        | id      | 4       | test.t3c.id          | 38    | Using index                                  |
          |    2 | DERIVED     | <derived3> | ref    | key0                      | key0    | 5       | test.t3c.id          | 40    |                                              |
          |    3 | DERIVED     | t1         | index  | paid,paid_2,paid_3,paid_4 | paid_4  | 5       | NULL                 | 22338 | Using index; Using temporary; Using filesort |
          |    3 | DERIVED     | t2         | eq_ref | PRIMARY                   | PRIMARY | 4       | test.t1.paid         | 1     |                                              |
          +------+-------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+
          7 rows in set (0.004 sec)
          

          The execution times become better : 48.7 sec and 0.3 sec correspondingly , but with the ratio between them is pretty close to what we had for InnoDB tables.

          igor Igor Babaev (Inactive) added a comment - We got the above results for InnoDB tables t1,t2,t3,t2c,t3c after having run ANALYZE TABLE ... PERSISTENT FOR ALL for each of these tables. If we convert these tables into MyISAM tables and run ANALYZE TABLE ... PERSISTENT FOR ALL for them we see the same problem as with InnoDB tables. The execution plans remain practically the same MariaDB [test]> set optimizer_switch='split_materialized=on'; Query OK, 0 rows affected (0.000 sec)   MariaDB [test]> EXPLAIN -> SELECT 1 -> FROM -> t3 -> JOIN -> ( -> SELECT t3c.id, t3c.b, sum(dt_i.siege) -> FROM -> ( -> ( t3c JOIN t2c ON (t2c.id = t3c.id)) -> LEFT JOIN -> ( -> SELECT t2.id, sum(CASE WHEN t2.a = t1.manr THEN 1 ELSE 0 END) AS siege -> FROM -> t1 -> JOIN -> t2 on t1.paid = t2.paid -> GROUP BY t2.id -> ) dt_i -> ON (dt_i.id = t3c.id) -> ) -> GROUP BY t3c.id,t3c.b -> ) dt_o -> ON dt_o.id = t3.id AND dt_o.b = t3.b -> ; +------+-----------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-----------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+ | 1 | PRIMARY | t3 | index | id,id_2 | id | 8 | NULL | 550 | Using index | | 1 | PRIMARY | <derived2> | ref | key0 | key0 | 8 | test.t3.id,test.t3.b | 2 | | | 2 | LATERAL DERIVED | t3c | eq_ref | id,id_2 | id | 8 | test.t3.id,test.t3.b | 1 | Using index | | 2 | LATERAL DERIVED | <derived3> | ref | key0 | key0 | 5 | test.t3c.id | 3 | | | 2 | LATERAL DERIVED | t2c | ref | id | id | 4 | test.t3c.id | 38 | Using index | | 3 | DERIVED | t1 | index | paid,paid_2,paid_3,paid_4 | paid_4 | 5 | NULL | 22338 | Using index; Using temporary; Using filesort | | 3 | DERIVED | t2 | eq_ref | PRIMARY,id | PRIMARY | 4 | test.t1.paid | 1 | | +------+-----------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+ 7 rows in set (0.001 sec)   MariaDB [test]> set optimizer_switch='split_materialized=off'; Query OK, 0 rows affected (0.000 sec)   MariaDB [test]> EXPLAIN -> SELECT 1 -> FROM -> t3 -> JOIN -> ( -> SELECT t3c.id, t3c.b, sum(dt_i.siege) -> FROM -> ( -> ( t3c JOIN t2c ON (t2c.id = t3c.id)) -> LEFT JOIN -> ( -> SELECT t2.id, sum(CASE WHEN t2.a = t1.manr THEN 1 ELSE 0 END) AS siege -> FROM -> t1 -> JOIN -> t2 on t1.paid = t2.paid -> GROUP BY t2.id -> ) dt_i -> ON (dt_i.id = t3c.id) -> ) -> GROUP BY t3c.id,t3c.b -> ) dt_o -> ON dt_o.id = t3.id AND dt_o.b = t3.b -> ; +------+-------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+ | 1 | PRIMARY | t3 | index | id,id_2 | id | 8 | NULL | 550 | Using index | | 1 | PRIMARY | <derived2> | ref | key0 | key0 | 8 | test.t3.id,test.t3.b | 1577 | | | 2 | DERIVED | t3c | index | id,id_2 | id | 8 | NULL | 550 | Using index | | 2 | DERIVED | t2c | ref | id | id | 4 | test.t3c.id | 38 | Using index | | 2 | DERIVED | <derived3> | ref | key0 | key0 | 5 | test.t3c.id | 40 | | | 3 | DERIVED | t1 | index | paid,paid_2,paid_3,paid_4 | paid_4 | 5 | NULL | 22338 | Using index; Using temporary; Using filesort | | 3 | DERIVED | t2 | eq_ref | PRIMARY | PRIMARY | 4 | test.t1.paid | 1 | | +------+-------------+------------+--------+---------------------------+---------+---------+----------------------+-------+----------------------------------------------+ 7 rows in set (0.004 sec) The execution times become better : 48.7 sec and 0.3 sec correspondingly , but with the ratio between them is pretty close to what we had for InnoDB tables.

          Now we are ready for an analysis of the bug. We are going to debug the execution of our query using InnoDB tables.
          When processing the query at first the optimizer proceeds like this:
          1. The best non splitting plan is chosen for the derived table dt_i and its cost C_i is calculated.
          2. The optimizer looks for the best plan non-splitting plan for the derived table dt_o evaluating costs of different possible join orders of the tables t3c,t2c,dt_i. As dt_i is joined by a left join operation with ON condition (dt_i.id=t3c.id) the optimizer considers only the following join orders: t3c->t2c->dt_i, t3c->dt_i->t2c, t2.c->t3c->dt_i.
          3. When evaluating a certain join order where dt_i is joined to the partial join PJ the optimizer considers two options: to join dt_i as it is specified or to join dt_i with pushed equalities depending on tables from PJ that are useful for split. In our case for join order t3c->dt_i->t2c the pushed equality is dt_i.id = t3c.id, for the other two orders the pushed equalities are dt_i.id = t3c.id and and an inferred equality dt_i.id = t2c.id. The optimizer chooses the cheapest option here.
          4. After the best non-splitting plan has been chosen for dt_o the optimizer fixes the the split choice for dt_i. It is done in the function JOIN_TAB::fix_splitting().

          Let's set a breakpoint in JOIN_TAB::fix_splitting(). When we come here for the first time we see

          (gdb) p table->alias
          $35 = {<Charset> = {m_charset = 0x5555579975e0 <my_charset_bin>}, <Binary_string> = {<Static_binary_string> = {<Sql_alloc> = {<No data fields>}, Ptr = 0x7fff6401b800 "dt_i", str_length = 4}, Alloced_length = 0, extra_alloc = 0, alloced = false, thread_specific = false}, <No data fields>}
          (gdb) p spl_plan
          $36 = (SplM_plan_info *) 0x0
          

          It means that no splitting has been chosen for dt_i in the best non-splitting planfor dt_o. This perfectly complies with the following:

          MariaDB [test]> EXPLAIN
              -> SELECT t3c.id, t3c.b, sum(dt_i.siege) 
              -> FROM 
              -> (
              ->   ( t3c JOIN t2c ON (t2c.id = t3c.id))
              ->   LEFT JOIN
              ->   ( 
              ->     SELECT t2.id, sum(CASE WHEN t2.a = t1.manr THEN 1 ELSE 0 END) AS siege
              ->     FROM
              ->     t1
              ->     JOIN
              ->     t2 ON t1.paid = t2.paid
              ->     GROUP BY t2.id
              ->   ) dt_i
              ->   ON (dt_i.id = t3c.id)
              -> )
              -> ;
          +------+-------------+------------+--------+---------------------------+---------+---------+--------------+-------+----------------------------------------------+
          | id   | select_type | table      | type   | possible_keys             | key     | key_len | ref          | rows  | Extra                                        |
          +------+-------------+------------+--------+---------------------------+---------+---------+--------------+-------+----------------------------------------------+
          |    1 | PRIMARY     | t3c        | index  | id,id_2                   | id      | 8       | NULL         | 550   | Using index                                  |
          |    1 | PRIMARY     | t2c        | ref    | id                        | id      | 4       | test.t3c.id  | 38    | Using index                                  |
          |    1 | PRIMARY     | <derived2> | ref    | key0                      | key0    | 5       | test.t3c.id  | 40    |                                              |
          |    2 | DERIVED     | t1         | index  | paid,paid_2,paid_3,paid_4 | paid_4  | 5       | NULL         | 22338 | Using index; Using temporary; Using filesort |
          |    2 | DERIVED     | t2         | eq_ref | PRIMARY,id                | PRIMARY | 4       | test.t1.paid | 1     |                                              |
          +------+-------------+------------+--------+---------------------------+---------+---------+--------------+-------+----------------------------------------------+
          

          igor Igor Babaev (Inactive) added a comment - Now we are ready for an analysis of the bug. We are going to debug the execution of our query using InnoDB tables. When processing the query at first the optimizer proceeds like this: 1. The best non splitting plan is chosen for the derived table dt_i and its cost C_i is calculated. 2. The optimizer looks for the best plan non-splitting plan for the derived table dt_o evaluating costs of different possible join orders of the tables t3c,t2c,dt_i. As dt_i is joined by a left join operation with ON condition (dt_i.id=t3c.id) the optimizer considers only the following join orders: t3c->t2c->dt_i, t3c->dt_i->t2c, t2.c->t3c->dt_i. 3. When evaluating a certain join order where dt_i is joined to the partial join PJ the optimizer considers two options: to join dt_i as it is specified or to join dt_i with pushed equalities depending on tables from PJ that are useful for split. In our case for join order t3c->dt_i->t2c the pushed equality is dt_i.id = t3c.id, for the other two orders the pushed equalities are dt_i.id = t3c.id and and an inferred equality dt_i.id = t2c.id. The optimizer chooses the cheapest option here. 4. After the best non-splitting plan has been chosen for dt_o the optimizer fixes the the split choice for dt_i. It is done in the function JOIN_TAB::fix_splitting(). Let's set a breakpoint in JOIN_TAB::fix_splitting(). When we come here for the first time we see (gdb) p table->alias $35 = {<Charset> = {m_charset = 0x5555579975e0 <my_charset_bin>}, <Binary_string> = {<Static_binary_string> = {<Sql_alloc> = {<No data fields>}, Ptr = 0x7fff6401b800 "dt_i", str_length = 4}, Alloced_length = 0, extra_alloc = 0, alloced = false, thread_specific = false}, <No data fields>} (gdb) p spl_plan $36 = (SplM_plan_info *) 0x0 It means that no splitting has been chosen for dt_i in the best non-splitting planfor dt_o. This perfectly complies with the following: MariaDB [test]> EXPLAIN -> SELECT t3c.id, t3c.b, sum(dt_i.siege) -> FROM -> ( -> ( t3c JOIN t2c ON (t2c.id = t3c.id)) -> LEFT JOIN -> ( -> SELECT t2.id, sum(CASE WHEN t2.a = t1.manr THEN 1 ELSE 0 END) AS siege -> FROM -> t1 -> JOIN -> t2 ON t1.paid = t2.paid -> GROUP BY t2.id -> ) dt_i -> ON (dt_i.id = t3c.id) -> ) -> ; +------+-------------+------------+--------+---------------------------+---------+---------+--------------+-------+----------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+------------+--------+---------------------------+---------+---------+--------------+-------+----------------------------------------------+ | 1 | PRIMARY | t3c | index | id,id_2 | id | 8 | NULL | 550 | Using index | | 1 | PRIMARY | t2c | ref | id | id | 4 | test.t3c.id | 38 | Using index | | 1 | PRIMARY | <derived2> | ref | key0 | key0 | 5 | test.t3c.id | 40 | | | 2 | DERIVED | t1 | index | paid,paid_2,paid_3,paid_4 | paid_4 | 5 | NULL | 22338 | Using index; Using temporary; Using filesort | | 2 | DERIVED | t2 | eq_ref | PRIMARY,id | PRIMARY | 4 | test.t1.paid | 1 | | +------+-------------+------------+--------+---------------------------+---------+---------+--------------+-------+----------------------------------------------+

          (Analysis of the bug continues).
          5. Next the optimizer searches for the best plan for the top select. The possible join orders are t3->dt_o and dt_o->t3.
          When the join order t3->dt_o is evaluated there are two options to join dt_o without split and with split of the derived table. The cost of the option without split C_o has been already calculated. Now the option with possible split is evaluated. The equalities that is useful for split of dt_o are dt_o.id = t3.id and dt_o.b = t3.b. Now the optimizer looks for the best splitting plan when these equalities are pushed into dt_o. Evaluating a certain join order of the tables from dt_o t3,t3,dt_i the optimizer calls JOIN_TAB::choose_best_splitting() for dt_i that leaves a pointer to the best split of dt_i for any split of dt_o. This pointer is set in the POSITION structure for dt_i in any splitting plan for dt_o. We see that finally the optimizer chooses a split for dt_o and JOIN_TAB::fix_splitting() called for dt_o preserves the pointer to a split of dt_i in the final chosen plan for dt_o.
          So we see an inconsistency here: the execution plan for dt_i isn't splitting, but the plan for dt_o in the position for dt_i points to some splitting plan for dt_i.
          As a consequence of this when at the invocation of JOIN::optimize_stage2() for dt_i the function JOIN::inject_best_splitting_cond() is called and it marks the select specifying dt_i as UNCACHEABLE_DEPENDENT_INJECTED. This causes refill of dt_i for every row of t3c that has been reached from a row of t3.

          igor Igor Babaev (Inactive) added a comment - (Analysis of the bug continues). 5. Next the optimizer searches for the best plan for the top select. The possible join orders are t3->dt_o and dt_o->t3. When the join order t3->dt_o is evaluated there are two options to join dt_o without split and with split of the derived table. The cost of the option without split C_o has been already calculated. Now the option with possible split is evaluated. The equalities that is useful for split of dt_o are dt_o.id = t3.id and dt_o.b = t3.b. Now the optimizer looks for the best splitting plan when these equalities are pushed into dt_o. Evaluating a certain join order of the tables from dt_o t3,t3,dt_i the optimizer calls JOIN_TAB::choose_best_splitting() for dt_i that leaves a pointer to the best split of dt_i for any split of dt_o. This pointer is set in the POSITION structure for dt_i in any splitting plan for dt_o. We see that finally the optimizer chooses a split for dt_o and JOIN_TAB::fix_splitting() called for dt_o preserves the pointer to a split of dt_i in the final chosen plan for dt_o. So we see an inconsistency here: the execution plan for dt_i isn't splitting, but the plan for dt_o in the position for dt_i points to some splitting plan for dt_i. As a consequence of this when at the invocation of JOIN::optimize_stage2() for dt_i the function JOIN::inject_best_splitting_cond() is called and it marks the select specifying dt_i as UNCACHEABLE_DEPENDENT_INJECTED. This causes refill of dt_i for every row of t3c that has been reached from a row of t3.
          igor Igor Babaev (Inactive) added a comment - - edited

          Here's how the functions

            JOIN::optimize(),
            mysql_derived_optimize(),
            choose_plan(),
            JOIN_TAB::choose_best_splitting(),
            JOIN_TAB::fix_splitting()
          

          interplay when the above query is being processed:

              JOIN::optimize is called for the select of the whole query Q.
              mysql_derived_optimize is called for the derived table dt_o
                JOIN::optimize is called for the select of the derived table dt_o
                  mysql_derived_optimize is called for the derived table dt_i
                    JOIN::optimize is called for the select of the derived table dt_i
                      choose_plan is called for the select of the derived table dt_i
                      (to choose the best non-splitting plan for the select of dt_i)
                  choose_plan is called for the select of the derived table dt_o
                  (to choose the best non-splitting plan for the select of dt_o)
                    JOIN_TAB::choose_best_splitting is called for the derived table dt_i
                    (to determine the best splitting plan for the select of dt_i
                     for the join order prefix t3c,t2c)          
                      choose_plan is called for the select of the derived table dt_i again
                      (to choose the best splitting plan for the select of dt_i
                       for the join order prefix t3c,t2c)
                    JOIN_TAB::choose_best_splitting is called for the derived table dt_i
                    (to determine the best splitting plan for the select of dt_i
                     for the join order prefix t3c)
                    JOIN_TAB::choose_best_splitting is called for the derived table dt_i
                    (to determine the best splitting plan for the select of dt_i
                     for the join order prefix t2c,t3c)                    
                  JOIN_TAB::fix_splitting is called for the derived table dt_i
                  (it determines that non-splitting plan of dt_i should be chosen
                   for non-splitting plan of dt_o)
              choose_plan is called for the select of the query Q
              (to choose choose the best plan for the select of Q)
                JOIN_TAB::choose_best_splitting is called for the derived table dt_o
                (to determine the best splitting plan for the select of dt_o
                 for the join order prefix t3)          
                  choose_plan is called for the select of the derived table dt_o again
                  (to choose the best splitting plan for the select of dt_o
                   for the join order prefix t3)
           ( * )   choose_plan is called for the derived table dt_i again
                    (to choose the best splitting plan for the select of dt_i
                     for the join order prefix t3c,t2c,
                     now only for one split partition of dt_o)
           ( ** )    JOIN_TAB::choose_best_splitting is called for the derived table dt_i
                      (to determine the best splitting plan for the select of dt_o
                       for the join order prefix t3c
                       now only for one split partition of dt_o)          
                      JOIN_TAB::choose_best_splitting is called for the derived table dt_i
                      (to determine the best splitting plan for the select of dt_i
                       for the join order prefix t2c,t3c,
                       now only for one split partition of dt_o)
                JOIN_TAB::choose_best_splitting is called for the derived table dt_o
              JOIN_TAB::fix_splitting is called for the derived table dt_o
              (it determines that a splitting plan of dt_o should be chosen
               in plan for the select of Q)
             mysql_derived_optimize is called for the derived table dt_i
               JOIN::optimize is called for the select of the derived table dt_i
               (to refine the chosen plan for the select of the derived table dt_i)
             mysql_derived_optimize is called for the derived table dt_o
               JOIN::optimize is called for the select of the derived table dt_o
               (to refine the chosen plan for the select of the derived table dt_o)
          

          We see that at the step ( * ) the optimizer tries to get the best plan fot dt_i when evaluating a split partition of dt_o. Yet when it comes to ( ** ) it compares the cost of a splitting plan for dt_i used for the evaluated split partition of dt_o with the cost C_u of non-split plan for dt_i. This is not correct because materialization of dt_i can be used by each split partition of dt_o. So the comparison should be done only with the portion of C_u, more exactly with C_u/P where P is the the number of partition in the evaluated split plan for dt_o. As a result of this incorrect comparison the optimizer now chooses a splitting plan for dt_i in the final chosen plan for dt_o that happens to be a splitting plan. However this time no JOIN_TAB::fix_splitting() is called for the dt_i and the final execution plan for dt_i remains the same as it was chosen before i.e. an non-splitting plan in spite of the fact that the plan for dt_o says that it uses a splitting plan for dt_i. This forces the second stage of the optimization (JOIN::optimize_stage2() to mark the select of dt_i as uncachedble and it causes re-materialization of dt_i for each partition of dt_o.

          igor Igor Babaev (Inactive) added a comment - - edited Here's how the functions JOIN::optimize(), mysql_derived_optimize(), choose_plan(), JOIN_TAB::choose_best_splitting(), JOIN_TAB::fix_splitting() interplay when the above query is being processed: JOIN::optimize is called for the select of the whole query Q. mysql_derived_optimize is called for the derived table dt_o JOIN::optimize is called for the select of the derived table dt_o mysql_derived_optimize is called for the derived table dt_i JOIN::optimize is called for the select of the derived table dt_i choose_plan is called for the select of the derived table dt_i (to choose the best non-splitting plan for the select of dt_i) choose_plan is called for the select of the derived table dt_o (to choose the best non-splitting plan for the select of dt_o) JOIN_TAB::choose_best_splitting is called for the derived table dt_i (to determine the best splitting plan for the select of dt_i for the join order prefix t3c,t2c) choose_plan is called for the select of the derived table dt_i again (to choose the best splitting plan for the select of dt_i for the join order prefix t3c,t2c) JOIN_TAB::choose_best_splitting is called for the derived table dt_i (to determine the best splitting plan for the select of dt_i for the join order prefix t3c) JOIN_TAB::choose_best_splitting is called for the derived table dt_i (to determine the best splitting plan for the select of dt_i for the join order prefix t2c,t3c) JOIN_TAB::fix_splitting is called for the derived table dt_i (it determines that non-splitting plan of dt_i should be chosen for non-splitting plan of dt_o) choose_plan is called for the select of the query Q (to choose choose the best plan for the select of Q) JOIN_TAB::choose_best_splitting is called for the derived table dt_o (to determine the best splitting plan for the select of dt_o for the join order prefix t3) choose_plan is called for the select of the derived table dt_o again (to choose the best splitting plan for the select of dt_o for the join order prefix t3) ( * ) choose_plan is called for the derived table dt_i again (to choose the best splitting plan for the select of dt_i for the join order prefix t3c,t2c, now only for one split partition of dt_o) ( ** ) JOIN_TAB::choose_best_splitting is called for the derived table dt_i (to determine the best splitting plan for the select of dt_o for the join order prefix t3c now only for one split partition of dt_o) JOIN_TAB::choose_best_splitting is called for the derived table dt_i (to determine the best splitting plan for the select of dt_i for the join order prefix t2c,t3c, now only for one split partition of dt_o) JOIN_TAB::choose_best_splitting is called for the derived table dt_o JOIN_TAB::fix_splitting is called for the derived table dt_o (it determines that a splitting plan of dt_o should be chosen in plan for the select of Q) mysql_derived_optimize is called for the derived table dt_i JOIN::optimize is called for the select of the derived table dt_i (to refine the chosen plan for the select of the derived table dt_i) mysql_derived_optimize is called for the derived table dt_o JOIN::optimize is called for the select of the derived table dt_o (to refine the chosen plan for the select of the derived table dt_o) We see that at the step ( * ) the optimizer tries to get the best plan fot dt_i when evaluating a split partition of dt_o. Yet when it comes to ( ** ) it compares the cost of a splitting plan for dt_i used for the evaluated split partition of dt_o with the cost C_u of non-split plan for dt_i. This is not correct because materialization of dt_i can be used by each split partition of dt_o. So the comparison should be done only with the portion of C_u, more exactly with C_u/P where P is the the number of partition in the evaluated split plan for dt_o. As a result of this incorrect comparison the optimizer now chooses a splitting plan for dt_i in the final chosen plan for dt_o that happens to be a splitting plan. However this time no JOIN_TAB::fix_splitting() is called for the dt_i and the final execution plan for dt_i remains the same as it was chosen before i.e. an non-splitting plan in spite of the fact that the plan for dt_o says that it uses a splitting plan for dt_i. This forces the second stage of the optimization (JOIN::optimize_stage2() to mark the select of dt_i as uncachedble and it causes re-materialization of dt_i for each partition of dt_o.
          mauerparker mauerparker added a comment -

          Hello,

          Unfortunately, there still doesn't seem to be a solution for this issue.

          Today, I tested again with mariadb:11.8.1-rc and observed an interesting behavior. Perhaps this will help resolve the problem.

          When I import the data using:

           
          mariadb -uroot -p test < dump.sql 
          

          and immediately execute the query:

           
          SELECT
              S.*, B.*
          FROM Teilnehmer_1 S
          INNER JOIN Buchholzpunkte B ON B.TerID = S.TerID AND B.TnNr = S.TnNr; 
          

          the result is fast:
          134 row(s) returned | 0.031 sec / 0.000 sec

          For the next 10 seconds, I can repeatedly execute the query, and it remains fast. After these 10 seconds, the query becomes slow again:
          134 row(s) returned | 10.625 sec / 0.000 sec

          It seems that after a certain period, an "optimization" occurs, triggering the issue.

          If I immediately run:

           
          EXPLAIN SELECT
              S.*, B.*
          FROM Teilnehmer_1 S
          INNER JOIN Buchholzpunkte B ON B.TerID = S.TerID AND B.TnNr = S.TnNr; 
          

          I get the following output:

          + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
          | id      | select_type      | table      | type      | possible_keys      | key      | key_len      | ref      | rows      | Extra      |
          + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
          | 1       | PRIMARY          | S          | ALL       | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 |          |              |          | 1700      |            |
          | 1       | PRIMARY          | <derived2> | ref       | key0               | key0     | 8            | test.S.TerID,test.S.TnNr | 10        |            |
          | 2       | DERIVED          | Te         | ALL       | PRIMARY            |          |              |          | 1         | Using where; Using temporary; Using filesort |
          | 2       | DERIVED          | P          | ref       | PRIMARY,TerID,TerRunde | TerID    | 4            | test.Te.TerID | 39        | Using where |
          | 2       | DERIVED          | M1         | ref       | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | idx_PaID_TnNr | 4            | test.P.PaID | 4         | Using index |
          | 2       | DERIVED          | Tn         | eq_ref    | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 | Key_TnNr_TerID_Teilnehmer_1 | 8            | test.Te.TerID,test.M1.TnNr | 1         |            |
          | 2       | DERIVED          | M2         | ref       | PaID_LiMaNr_MaNr_TnNr,PaID,idx_PaID_TnNr,idx_PaID_MaNr | idx_PaID_TnNr | 4            | test.P.PaID | 4         | Using where; Using index |
          | 2       | DERIVED          | <derived3> | ref       | key0               | key0     | 5            | test.Te.TerID | 10        | Using where |
          | 3       | DERIVED          | T          | index     | PRIMARY            | TuSID    | 4            |          | 1         | Using index; Using temporary; Using filesort |
          | 3       | DERIVED          | P          | ref       | PRIMARY,TerID,TerRunde | TerID    | 4            | test.T.TerID | 39        | Using where |
          | 3       | DERIVED          | M          | ref       | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | PaID_LiMaNr_MaNr_TnNr | 4            | test.P.PaID | 4         | Using index |
          + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
          11 rows
          

          After 10 seconds (without any changes), the EXPLAIN output becomes:

          + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
          | id      | select_type      | table      | type      | possible_keys      | key      | key_len      | ref      | rows      | Extra      |
          + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
          | 1       | PRIMARY          | S          | ALL       | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 |          |              |          | 1700      |            |
          | 1       | PRIMARY          | <derived2> | ref       | key0               | key0     | 8            | test.S.TerID,test.S.TnNr | 1         |            |
          | 2       | LATERAL DERIVED  | Tn         | eq_ref    | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 | Key_TnNr_TerID_Teilnehmer_1 | 8            | test.S.TerID,test.S.TnNr | 1         |            |
          | 2       | LATERAL DERIVED  | Te         | eq_ref    | PRIMARY            | PRIMARY  | 4            | test.Tn.TerID | 1         | Using where |
          | 2       | LATERAL DERIVED  | P          | ref       | PRIMARY,TerID,TerRunde | TerID    | 4            | test.Tn.TerID | 39        | Using where |
          | 2       | LATERAL DERIVED  | M1         | ref       | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | idx_PaID_TnNr | 8            | test.P.PaID,test.Tn.TnNr | 1         | Using index |
          | 2       | LATERAL DERIVED  | M2         | ref       | PaID_LiMaNr_MaNr_TnNr,PaID,idx_PaID_TnNr,idx_PaID_MaNr | PaID_LiMaNr_MaNr_TnNr | 4            | test.P.PaID | 4         | Using where; Using index |
          | 2       | LATERAL DERIVED  | <derived3> | ref       | key0               | key0     | 5            | test.Tn.TerID | 3         | Using where |
          | 3       | DERIVED          | P          | ALL       | PRIMARY,TerID,TerRunde |          |              |          | 10135     | Using where; Using temporary; Using filesort |
          | 3       | DERIVED          | T          | eq_ref    | PRIMARY            | PRIMARY  | 4            | test.P.TerID | 1         |            |
          | 3       | DERIVED          | M          | ref       | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | PaID_LiMaNr_MaNr_TnNr | 4            | test.P.PaID | 4         | Using index |
          + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- +
          11 rows
          

          Best regards,
          F.K.

          mauerparker mauerparker added a comment - Hello, Unfortunately, there still doesn't seem to be a solution for this issue. Today, I tested again with mariadb:11.8.1-rc and observed an interesting behavior. Perhaps this will help resolve the problem. When I import the data using: mariadb -uroot -p test < dump.sql and immediately execute the query: SELECT S.*, B.* FROM Teilnehmer_1 S INNER JOIN Buchholzpunkte B ON B.TerID = S.TerID AND B.TnNr = S.TnNr; the result is fast: 134 row(s) returned | 0.031 sec / 0.000 sec For the next 10 seconds, I can repeatedly execute the query, and it remains fast. After these 10 seconds, the query becomes slow again: 134 row(s) returned | 10.625 sec / 0.000 sec It seems that after a certain period, an "optimization" occurs, triggering the issue. If I immediately run: EXPLAIN SELECT S.*, B.* FROM Teilnehmer_1 S INNER JOIN Buchholzpunkte B ON B.TerID = S.TerID AND B.TnNr = S.TnNr; I get the following output: + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- + | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- + | 1 | PRIMARY | S | ALL | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 | | | | 1700 | | | 1 | PRIMARY | <derived2> | ref | key0 | key0 | 8 | test.S.TerID,test.S.TnNr | 10 | | | 2 | DERIVED | Te | ALL | PRIMARY | | | | 1 | Using where; Using temporary; Using filesort | | 2 | DERIVED | P | ref | PRIMARY,TerID,TerRunde | TerID | 4 | test.Te.TerID | 39 | Using where | | 2 | DERIVED | M1 | ref | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | idx_PaID_TnNr | 4 | test.P.PaID | 4 | Using index | | 2 | DERIVED | Tn | eq_ref | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 | Key_TnNr_TerID_Teilnehmer_1 | 8 | test.Te.TerID,test.M1.TnNr | 1 | | | 2 | DERIVED | M2 | ref | PaID_LiMaNr_MaNr_TnNr,PaID,idx_PaID_TnNr,idx_PaID_MaNr | idx_PaID_TnNr | 4 | test.P.PaID | 4 | Using where; Using index | | 2 | DERIVED | <derived3> | ref | key0 | key0 | 5 | test.Te.TerID | 10 | Using where | | 3 | DERIVED | T | index | PRIMARY | TuSID | 4 | | 1 | Using index; Using temporary; Using filesort | | 3 | DERIVED | P | ref | PRIMARY,TerID,TerRunde | TerID | 4 | test.T.TerID | 39 | Using where | | 3 | DERIVED | M | ref | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | PaID_LiMaNr_MaNr_TnNr | 4 | test.P.PaID | 4 | Using index | + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- + 11 rows After 10 seconds (without any changes), the EXPLAIN output becomes: + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- + | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- + | 1 | PRIMARY | S | ALL | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 | | | | 1700 | | | 1 | PRIMARY | <derived2> | ref | key0 | key0 | 8 | test.S.TerID,test.S.TnNr | 1 | | | 2 | LATERAL DERIVED | Tn | eq_ref | Key_TeaID_TerID_Teilnehmer_1,Key_TnNr_TerID_Teilnehmer_1,TerID_Teilnehmer_1,Ter_Tea_Teilnehmer_1 | Key_TnNr_TerID_Teilnehmer_1 | 8 | test.S.TerID,test.S.TnNr | 1 | | | 2 | LATERAL DERIVED | Te | eq_ref | PRIMARY | PRIMARY | 4 | test.Tn.TerID | 1 | Using where | | 2 | LATERAL DERIVED | P | ref | PRIMARY,TerID,TerRunde | TerID | 4 | test.Tn.TerID | 39 | Using where | | 2 | LATERAL DERIVED | M1 | ref | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | idx_PaID_TnNr | 8 | test.P.PaID,test.Tn.TnNr | 1 | Using index | | 2 | LATERAL DERIVED | M2 | ref | PaID_LiMaNr_MaNr_TnNr,PaID,idx_PaID_TnNr,idx_PaID_MaNr | PaID_LiMaNr_MaNr_TnNr | 4 | test.P.PaID | 4 | Using where; Using index | | 2 | LATERAL DERIVED | <derived3> | ref | key0 | key0 | 5 | test.Tn.TerID | 3 | Using where | | 3 | DERIVED | P | ALL | PRIMARY,TerID,TerRunde | | | | 10135 | Using where; Using temporary; Using filesort | | 3 | DERIVED | T | eq_ref | PRIMARY | PRIMARY | 4 | test.P.TerID | 1 | | | 3 | DERIVED | M | ref | PaID_LiMaNr_MaNr_TnNr,PaID,TnNr,idx_PaID_TnNr,idx_PaID_MaNr | PaID_LiMaNr_MaNr_TnNr | 4 | test.P.PaID | 4 | Using index | + ------- + ---------------- + ---------- + --------- + ------------------ + -------- + ------------ + -------- + --------- + ---------- + 11 rows Best regards, F.K.

          People

            psergei Sergei Petrunia
            mauerparker mauerparker
            Votes:
            2 Vote for this issue
            Watchers:
            10 Start watching this issue

            Dates

              Created:
              Updated:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.