Bug #116773 Performance of TPC-DS Query 7
Submitted: 25 Nov 2024 17:40 Modified: 28 Nov 2024 8:01
Reporter: JINSHENG BA Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S5 (Performance)
Version:596f0d23 (9.0.0), 8.0.40 OS:Any
Assigned to: CPU Architecture:Any

[25 Nov 2024 17:40] JINSHENG BA
Description:
For TPC-DS Query 7:

select  i_item_id, 
        avg(ss_quantity) agg1,
        avg(ss_list_price) agg2,
        avg(ss_coupon_amt) agg3,
        avg(ss_sales_price) agg4 
 from store_sales, customer_demographics, date_dim, item, promotion
 where ss_sold_date_sk = d_date_sk and
       ss_item_sk = i_item_sk and
       ss_cdemo_sk = cd_demo_sk and
       ss_promo_sk = p_promo_sk and
       cd_gender = 'F' and 
       cd_marital_status = 'W' and
       cd_education_status = 'Primary' and
       (p_channel_email = 'N' or p_channel_event = 'N') and
       d_year = 1998 
 group by i_item_id
 order by i_item_id
 limit 100;

Its execution time and query plan:
EXPLAIN
-> Limit: 100 row(s)  (actual time=66095..66095 rows=100 loops=1)
    -> Sort: item.i_item_id, limit input to 100 row(s) per chunk  (actual time=66095..66095 rows=100 loops=1)
        -> Table scan on <temporary>  (actual time=66093..66094 rows=5181 loops=1)
            -> Aggregate using temporary table  (actual time=66093..66093 rows=5181 loops=1)
                -> Nested loop inner join  (cost=1.41e+6 rows=1967) (actual time=3.6..66070 rows=7702 loops=1)
                    -> Nested loop inner join  (cost=1.38e+6 rows=39337) (actual time=1.23..20864 rows=535961 loops=1)
                        -> Nested loop inner join  (cost=958290 rows=393367) (actual time=1.17..14766 rows=2.74e+6 loops=1)
                            -> Nested loop inner join  (cost=233667 rows=2.07e+6) (actual time=1.15..8512 rows=2.88e+6 loops=1)
                                -> Table scan on item  (cost=2417 rows=17565) (actual time=0.92..1760 rows=18000 loops=1)
                                -> Filter: ((store_sales.ss_promo_sk is not null) and (store_sales.ss_sold_date_sk is not null) and (store_sales.ss_cdemo_sk is not null))  (cost=1.38 rows=118) (actual time=0.195..0.366 rows=160 loops=18000)
                                    -> Index lookup on store_sales using PRIMARY (ss_item_sk=item.i_item_sk)  (cost=1.38 rows=118) (actual time=0.195..0.348 rows=160 loops=18000)
                            -> Filter: ((promotion.p_channel_email = 'N') or (promotion.p_channel_event = 'N'))  (cost=0.25 rows=0.19) (actual time=0.00194..0.00202 rows=0.952 loops=2.88e+6)
                                -> Single-row index lookup on promotion using PRIMARY (p_promo_sk=store_sales.ss_promo_sk)  (cost=0.25 rows=1) (actual time=0.00171..0.00174 rows=0.955 loops=2.88e+6)
                        -> Filter: (date_dim.d_year = 1998)  (cost=0.979 rows=0.1) (actual time=0.00209..0.00211 rows=0.195 loops=2.74e+6)
                            -> Single-row index lookup on date_dim using PRIMARY (d_date_sk=store_sales.ss_sold_date_sk)  (cost=0.979 rows=1) (actual time=0.0019..0.00193 rows=0.976 loops=2.74e+6)
                    -> Filter: ((customer_demographics.cd_education_status = 'Primary') and (customer_demographics.cd_marital_status = 'W') and (customer_demographics.cd_gender = 'F'))  (cost=0.526 rows=0.05) (actual time=0.0842..0.0842 rows=0.0144 loops=535961)
                        -> Single-row index lookup on customer_demographics using PRIMARY (cd_demo_sk=store_sales.ss_cdemo_sk)  (cost=0.526 rows=1) (actual time=0.0839..0.0839 rows=0.988 loops=535961)

If we apply this patch:

diff --git a/sql/sql_planner.cc b/sql/sql_planner.cc
index ad0c639abca..73d9e7cf9b1 100644
--- a/sql/sql_planner.cc
+++ b/sql/sql_planner.cc
@@ -1210,7 +1210,7 @@ void Optimize_table_order::best_access_path(JOIN_TAB *tab,
     chosen. The filtering effect for all the scan types of access
     (range/index scan/table scan) has already been calculated.
   */
-  if (best_ref)
+  if (!best_ref)
     filter_effect = calculate_condition_filter(
         tab, best_ref, ~remaining_tables & ~excluded_tables, rows_fetched,
         false, false, trace_access_scan);

The execution time is significantly reduced:

EXPLAIN
-> Limit: 100 row(s)  (actual time=5479..5479 rows=100 loops=1)
    -> Sort: item.i_item_id, limit input to 100 row(s) per chunk  (actual time=5479..5479 rows=100 loops=1)
        -> Table scan on <temporary>  (actual time=5477..5478 rows=5181 loops=1)
            -> Aggregate using temporary table  (actual time=5477..5477 rows=5181 loops=1)
                -> Nested loop inner join  (cost=1.13e+6 rows=53277) (actual time=896..5466 rows=7702 loops=1)
                    -> Nested loop inner join  (cost=843429 rows=53277) (actual time=894..5374 rows=39339 loops=1)
                        -> Nested loop inner join  (cost=559367 rows=53277) (actual time=894..5283 rows=39339 loops=1)
                            -> Inner hash join (store_sales.ss_cdemo_sk = customer_demographics.cd_demo_sk)  (cost=275305 rows=53277) (actual time=893..5193 rows=40389 loops=1)
                                -> Filter: ((store_sales.ss_promo_sk is not null) and (store_sales.ss_sold_date_sk is not null))  (cost=25873 rows=278735) (actual time=0.721..1944 rows=2.88e+6 loops=1)
                                    -> Table scan on store_sales  (cost=25873 rows=2.79e+6) (actual time=0.72..1760 rows=2.88e+6 loops=1)
                                -> Hash
                                    -> Filter: ((customer_demographics.cd_education_status = 'Primary') and (customer_demographics.cd_marital_status = 'W') and (customer_demographics.cd_gender = 'F'))  (cost=200448 rows=1.91) (actual time=0.412..885 rows=27440 loops=1)
                                        -> Table scan on customer_demographics  (cost=200448 rows=1.91e+6) (actual time=0.403..722 rows=1.92e+6 loops=1)
                            -> Filter: ((promotion.p_channel_email = 'N') or (promotion.p_channel_event = 'N'))  (cost=0.523 rows=1) (actual time=0.00202..0.00209 rows=0.974 loops=40389)
                                -> Single-row index lookup on promotion using PRIMARY (p_promo_sk=store_sales.ss_promo_sk)  (cost=0.523 rows=1) (actual time=0.00179..0.00181 rows=0.977 loops=40389)
                        -> Single-row index lookup on item using PRIMARY (i_item_sk=store_sales.ss_item_sk)  (cost=0.523 rows=1) (actual time=0.00214..0.00216 rows=1 loops=39339)
                    -> Filter: (date_dim.d_year = 1998)  (cost=0.523 rows=1) (actual time=0.00221..0.00223 rows=0.196 loops=39339)
                        -> Single-row index lookup on date_dim using PRIMARY (d_date_sk=store_sales.ss_sold_date_sk)  (cost=0.523 rows=1) (actual time=0.00204..0.00206 rows=0.989 loops=39339)

I am not proposing a patch, but it seems the performance can be significantly improved by changing the usage of `calculate_condition_filter`. I am wondering whether we can optimize any logic to enable the second query plan, which seems much more efficient?

How to repeat:
I dumped the entire database: https://drive.google.com/file/d/1mCwATIQtNwYftxVvMCNz6xgjvRDCrhtq/view?usp=sharing

Compile MySQL in two versions, one is original and the other is with the changed IF condition. Then compare the execution of above query on both versions.
[27 Nov 2024 7:04] MySQL Verification Team
Hello JINSHENG BA,

Thank you for the report and feedback.
I'm trying to reproduce the issue with provided details and get back to you if anything further needed.
I assume you have tested against the default server settings(if this is not the case then please attach the configuration details to the report).

regards,
Umesh
[28 Nov 2024 8:01] MySQL Verification Team
Hello JINSHENG BA,

Thank you for the report and details.
Observed this with 8.0.40 build.

regards,
Umesh