By Bo Li (Boge)
The database query optimizer is like the brain of the entire system. The efficiency of executing an SQL statement can vary significantly based on different optimization decisions. Therefore, the optimizer is one of the core components and competitive advantages of a database system. Alibaba Cloud PolarDB for MySQL is a cloud-native database that can handles various user scenarios and loads, helping enterprises go online and increase their data value. To enhance its capabilities, we have focused on improving the query transformation functionality of PolarDB for MySQL.
This article series will focus on the query transformation capability of PolarDB for MySQL and introduce some of our accumulated work in this direction.
Correlated subqueries are commonly supported in modern relational databases. They offer strong semantic expression capabilities and allow users to directly reference columns from the outer query in order to calculate subquery results. Correlated subqueries are widely used in decision support systems and online analytics processing scenarios. For instance, one-third of the queries in the TPC-H decision support benchmark include correlated subqueries. Efficient execution of correlated subqueries is therefore a key criterion for determining the advancement of a database.
Decorrelation is a common method used to optimize queries with subqueries. Without decorrelation, the subquery is executed iteratively based on semantics. This means that the subquery is triggered and executed once for each piece of data queried by the outer query. Consequently, the correlated subquery is executed multiple times when the entire query is completed. If the outer query contains a large amount of data and the subquery cannot be efficiently executed, the overall query efficiency will be low.
This article introduces a method that utilizes the capabilities of window aggregate functions to decorrelate subqueries. It allows subqueries to be executed only once and avoids multiple accesses to the same common table in both the outer query and subquery. This greatly accelerates the entire query from two aspects.
The author has implemented this decorrelation method in MySQL, which significantly improves the performance of TPC-H Q2 and Q17 in the benchmark. By default, this method can be enabled online, providing stable and guaranteed performance improvement for eligible user queries.
Øystein, a MySQL industry expert, has also blogged Use Window Functions to Speed Up Correlated Subqueries to introduce this implementation, as he mentioned in his blog: At Alibaba, we have implemented the query transformation to window functions in our Innovative Version of POLARDB for MySQL. Unlike the subquery transformation introduced in MySQL 8.0.24, which is off by default since it may often significantly reduce the query performance, our transformation can be applied automatically. As long as the query satisfies the conditions listed above, it should not be any slower than the original query. In other words, with POLARDB you will no longer have to manually rewrite your query to get the benefits discussed in this blog post.
A common form of correlated subquery is when the subquery includes an aggregate function, and the outer query uses the aggregated results for further operations. A typical example is Query 2 in TPC-H:
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr,
s_address, s_phone, s_comment
FROM part, supplier, partsupp, nation, region
WHERE p_partkey = ps_partkey
AND s_suppkey = ps_suppkey
AND p_size = 30
AND p_type LIKE '%STEEL'
AND s_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name = 'ASIA'
AND ps_supplycost = (
SELECT MIN(ps_supplycost)
FROM partsupp, supplier, nation, region
WHERE p_partkey = ps_partkey
AND s_suppkey = ps_suppkey
AND s_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name = 'ASIA'
)
ORDER BY s_acctbal DESC, n_name, s_name, p_partkey
LIMIT 100;
The goal of this query is to find the supplier that can provide the minimum wholesale price for each part of a specific type and size in a particular area. In the query above, the main query first connects the eligible parts with the corresponding suppliers and related information. For each combination of parts and suppliers, the correlated subquery calculates the minimum wholesale price provided by all suppliers of the corresponding parts. Then, it compares whether the current combination is the same as the minimum wholesale price. Finally, it selects the supplier with the same wholesale price and the minimum value.
Using the above query as an example, in order to find the supplier that provides the minimum wholesale price for each part, in addition to the iterative method mentioned earlier, another approach is to calculate the minimum wholesale price of all parts corresponding to the supplier in advance, store the results, and then compare them with the combination of the main query part and the supplier. This avoids multiple executions of the subquery.
A window aggregate function is a way to calculate the aggregation result. Compared to common aggregate functions, window aggregate functions are more flexible and do not aggregate data. They allow you to add aggregation results to each row without affecting the original output columns. The expression form for window aggregate functions is as follows:
function(arg) OVER ([partition_clause] [order_clause] [frame_clause])
The OVER syntax is used to define a window for function computation. The partition_clause is similar to the commonly used GROUP BY syntax and specifies the columns that are aggregated by the aggregate function. This article only covers the usage of the partition_clause syntax.
Upon examining the main query body and subquery body of the previous query, we can observe that the tables, join conditions, and filter conditions involved in both bodies are quite similar. For instance, the tables present in the subquery are also included in the outer query, and the join conditions and filter conditions between these tables are also replicated in the outer query. Now, if we consider these tables and the join results obtained, we can conclude that the join results are identical in both the outer query and the subquery. Given that window aggregate functions can output additional aggregation results without affecting the original query results, could we perform the join operation only once in the subquery and output these join results? The main query would then only need to select the necessary columns from the output results of the subquery, eliminating the need for repetitive joins with these common tables.
Based on these ideas, we can rewrite the original query into the following form 1:
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr,
s_address, s_phone, s_comment
FROM part, (
SELECT MIN(ps_supplycost) OVER(PARTITION BY ps_partkey) as win_min,
ps_partkey, ps_supplycost, s_acctbal, n_name, s_name, s_address,
s_phone, s_comment
FROM partsupp, supplier, nation, region
WHERE s_suppkey = ps_suppkey
AND s_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name = 'ASIA') as derived
WHERE p_partkey = ps_partkey
AND p_size = 30
AND p_type LIKE '%STEEL'
AND ps_supplycost = derived.win_min
ORDER BY s_acctbal DESC, n_name, s_name, p_partkey
LIMIT 100;
By rewriting form 1, we have solved the issue of correlation in subqueries that requires iterative execution. Now, executing it once is sufficient. We have also avoided repeated access to common tables in both the main query and subqueries.
In addition to the rewriting mentioned above, it is important that the join results of the main query and the subquery are consistent for the common table. In fact, this consistency is not strictly required and can be expanded using other methods, as mentioned later. Another crucial condition is that the two tables involved in the correlated condition in the subquery must have a join condition that is semantically equivalent to the join condition between the two tables in the main query, and it must be an equivalent join.
How can we understand this? Let's consider the example of the original query mentioned above. The correlated condition in the subquery is p_partkey = ps_partkey. For each p_partkey in the main query, the subquery finds all records of ps_partkey that are identical to p_partkey and aggregates them. The subquery does not aggregate the ps_partkey, but instead aggregates the p_partkey value from the outer query. After decorrelating the subquery, only the ps_partkey column can be used for aggregation. Therefore, the correlated condition must be an equivalence condition. If p_partkey is equal to ps_partkey, the aggregation result of the ps_partkey column will be the same as that of the p_partkey column. If the correlated condition is non-equivalent, such as p_partkey being greater than ps_partkey, the aggregation result of ps_partkey cannot be correlated with the original subquery execution.
Why do we need a join condition that is equivalent to the correlated condition in the main query? This is because the output of the derived table needs to be joined with the associated table in the original main query. If the join condition is not equivalent to the correlated condition, the joined row will not correspond to the data that satisfies the associated condition. Using the aggregation result of the current row will result in an error.
Let's examine the query in the aforementioned form 1. In the original query, the join condition between the associated table "part" and the derived table is p_partkey = ps_partkey. The p_partkey is the primary key of the "part" table, and there is only one occurrence of the same p_partkey or ps_partkey value in the "part" table. However, there may be multiple occurrences in the "partsupp" table. Hence, the join relationship between the associated table and the derived table is 1:N. Joining them will not cause data expansion or affect the calculation of window aggregate values in the derived table. Therefore, the "part" table, join condition, and filter condition can be moved into the derived table together. The join operation is performed first, followed by the calculation of the window aggregate value. This is rewritten as form 2.
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr,
s_address, s_phone, s_comment
FROM (
SELECT MIN(ps_supplycost) OVER(PARTITION BY ps_partkey) as win_min,
ps_partkey, ps_supplycost, s_acctbal, n_name, s_name, s_address,
s_phone, s_comment
FROM part, partsupp, supplier, nation, region
WHERE p_partkey = ps_partkey
AND s_suppkey = ps_suppkey
AND s_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND p_size = 30
AND p_type LIKE '%STEEL'
AND r_name = 'ASIA') as derived
WHERE ps_supplycost = derived.win_min
ORDER BY s_acctbal DESC, n_name, s_name, p_partkey
LIMIT 100;
The benefit of this rewriting is that it allows more tables to be moved into the subquery that represents the derived table, providing the optimizer with more choices for the order of joins. Additionally, filter conditions and join conditions can be pushed down to the subquery, allowing for the pre-filtering of data that doesn't meet the conditions and reducing unnecessary calculations..
Now, we have learned the overall concept of decorrelation optimization by using a representative query example. Next, let's provide a general expression for decorrelation of subqueries using window aggregate functions. Assuming that the original query has the following form:
Original query correlated form
Among them:
Then under certain conditions, we can transform the query into the following two forms.
T2 is correlated with T3 in one or more common columns (not a primary key or unique key)
In this case, we can transform the original query into the following form, corresponding to form 1 in the above example:
The association columns of T2 and T3 form the primary key or unique key of T3 (forming a join relationship is 1:N ).
In this case, we can further optimize the original query and transform it to form 2:
In addition to the conditions mentioned above, there are some additional conditions that queries must meet in order to apply this optimization. These conditions are summarized below. Due to factors such as limitations in user scenarios, the risk of performance regression, and the difficulty of implementation, we support most usage scenarios, and a small number of expandable scenarios are also described in the following conditions.
To eliminate multiple accesses to common tables in both the main query and subquery, it is necessary to ensure that the join results of these common tables are consistent. One prerequisite is that the accessed tables are consistent. Therefore, the tables in the main query must include the tables in the subquery.
Expansion limits: Subqueries can include additional tables that are not in the main query. However, these additional tables must be connected to the common tables in a lossless manner. The purpose of a lossless join is also to ensure consistency in the output of the main query and the subquery on the common tables.
Similarly, in order to ensure consistency in the join results of the common tables in the main query and subquery, the join conditions and filter conditions of the common tables must include the conditions in the subquery. Additional conditions can exist in the main query, which can be further filtered at the outer layer after the output of the subquery representing the derived table.
Expansion limits: If a subquery contains additional conditions that are not in the main query, the conditions in the main query must be maintained in order to generate all the results required by the main query. However, the calculation result of the window aggregate function will be different from the original one due to the relaxed conditions. In this case, additional conditions need to be added to the window function computation using the CASE...WHEN statement. This transformation needs to be evaluated based on cost.
The specific reasons have been described in step 1 of optimization ideas, and will not be repeated here.
Window functions do not support the DISTINCT syntax. Therefore, aggregate functions cannot include the DISTINCT modifier. Additionally, aggregate functions must be compatible with window functions and can be transformed into window aggregate functions.
The main query and subquery can have multiple correlated conditions, but these conditions must be established between the same two tables. No other correlated conditions can exist except for those between these two tables.
Expansion limits: A subquery can be correlated with multiple different tables in the main query. Make sure that all correlated conditions are equivalent.
If a subquery is decorrelated using a window aggregate function, the execution times and execution order of the original query are changed. If the query contains user-defined functions, random functions, or other expressions with marginal effects or uncertainty, the execution result or the state of the database after execution may be inconsistent with the original query. Therefore, it is necessary to avoid transformation in these situations.
The process of implementing the preceding condition check and query decorrelation transformation in MySQL is as follows:
Decorrelation implementation process
For convenience, Outer Query is used to refer to the main query and Inner Query is used to refer to the subquery. The transformation process consists of the following steps:
In the TPC-H query, Query 2 and Query 17 meet the optimization conditions. Benchmark the optimization effect of these two queries.
The CPU specification is 2 Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz, 16 physical cores each.
Data is TPC-H 10G with index.
Parameters are innodb_adaptive_hash_index=off, innodb_buffer_pool_size=32G.
The results show that the acceleration ratio of Q2 is 1.54 and that of Q17 is 4.91.
Data is TPC-H 100G with index.
Parameters are innodb_adaptive_hash_index=off, innodb_buffer_pool_size=170G.
Check the Free Tier & Special Offers of Alibaba Cloud Database Products: https://www.alibabacloud.com/product/databases
Best Practice: Migrate AWS ElastiCache to Alibaba Cloud Tair via RIOT
About Database Kernel | PolarDB Optimizer Query Transformation - Join Elimination
ApsaraDB - October 20, 2023
ApsaraDB - October 27, 2023
ApsaraDB - October 30, 2023
ApsaraDB - October 19, 2023
ApsaraDB - August 7, 2023
ApsaraDB - March 26, 2024
Alibaba Cloud PolarDB for PostgreSQL is an in-house relational database service 100% compatible with PostgreSQL and highly compatible with the Oracle syntax.
Learn MoreAlibaba Cloud PolarDB for Xscale (PolarDB-X) is a cloud-native high-performance distributed database service independently developed by Alibaba Cloud.
Learn MoreAlibaba Cloud PolarDB for MySQL is a cloud-native relational database service 100% compatible with MySQL.
Learn MoreLeverage cloud-native database solutions dedicated for FinTech.
Learn MoreMore Posts by ApsaraDB