By Fangwu
From the perspective of users, IN queries hold the following advantages:
However, when using IN queries, we often encounter query performance issues. Once the parameter list of the IN expression exceeds a certain threshold, the processing efficiency of the database tends to drop sharply.
From the database perspective, the biggest challenge with IN queries is the instability caused by the dynamic parameter list. For example, for a MySQL instance, an excessive number of IN parameters may lead to a full table scan based on the range_optimizer_max_mem_size. Once the threshold is exceeded, the performance will decrease radically.
For PolarDB-X and other distributed online business databases, IN queries also pose the following challenges:
• When the optimizer calculates the cost, a large number of parameters bring additional cost calculation overhead.
• The dynamic parameter list of IN generates various SQL templates, which can easily fill up the execution plan cache.
• In distributed scenarios, vast quantities of IN parameters need to be pruned through pre-calculation to avoid unnecessary calculation overheads on the DN.
Note: In this article, the IN query does not contain IN subqueries.
Limited by space, this article focuses on the optimization of PolarDB-X in IN query execution plan management and execution.
Different databases have various execution plan management strategies. The types of PolarDB-X execution plans are similar to those in PostgreSQL, including a custom plan and a generic plan.
A prepared statement can be executed with either a generic plan or a custom plan. A generic plan is the same across all executions, while a custom plan is generated for a specific execution using the parameter values given in that call. Use of a generic plan avoids planning overhead, but in some situations a custom plan will be much more efficient to execute because the planner can make use of knowledge of the parameter values. (Of course, if the prepared statement has no parameters, then this is moot and a generic plan is always used.)
Quoted from PostgreSQL documentation: https://www.postgresql.org/docs/current/sql-prepare.html
However, regarding the cache strategy of execution plans, many PolarDB-X customers use more complex SQL templates for their online businesses. On the other hand, data is split into different partitions of the cluster according to various strategies, which greatly increases the challenges faced by the optimizer. Therefore, unlike PostgreSQL's more conservative execution plan management strategy, PolarDB-X tends to reuse cache execution plans as much as possible to avoid high optimizer overhead.
The parameter list of the IN expression poses a great challenge. Different parameter lists point to different execution plans, so the plan cache is easily filled. Especially in ad hoc query scenarios, different IN query parameter lists can easily generate massive SQL templates.
SELECT order_info FROM orders WHERE item_id IN(?) AND seller_id IN(?) ORDERY BY gmt;
Consider the simple order query request above, if both item_id and seller_id have 10 items, then the SQL can generate up to 100 SQL templates, whose execution plans are completely similar.
To reduce the number of these execution plans, the PolarDB-X parser encapsulates the parameter list of the IN expression. No matter how many dynamic parameters there are in the IN expression, they are all encapsulated in RawString objects. The optimizer is adapted to perform cost calculations for the RawString parameter lists. In the executor, the IN expression parameter list is packaged and sent to the DN for calculation.
The benefit is that the SQL templates of IN queries can be converged into one execution plan, which greatly alleviates the cache pressure of the execution plan. PolarDB-X can now support dynamic random access for high-concurrency IN queries.
In PolarDB-X, you can observe the cache status of the execution plan through PlanCache and SPM views. Here we demonstrate the cache status through PlanCache.
First, purchase a PolarDB-X 2.0 instance and create an environment by using the following SQL statements:
// Create a database and a table
CREATE DATABASE test MODE=AUTO;
use test
CREATE TABLE tb_h(
id bigint not null auto_increment,
bid int,
name varchar(30),
birthday datetime not null,
primary key(id)
)
PARTITION BY HASH(id)
PARTITIONS 8;
Execute the following SQL statement: select * from tb_h where id in(1,2,3,4,5);
Observe the cache status of the plan through the plan_cache view:
mysql> select * from information_schema.plan_cache where `sql` like '%tb_h%' limit 10\G
*************************** 1. row ***************************
COMPUTE_NODE: 10.2.100.161:3021
SCHEMA_NAME: test
TABLE_NAMES: tb_h
ID: 1d4daf02
HIT_COUNT: 0
SQL: SELECT *
FROM tb_h
WHERE id IN (?)
TYPE_DIGEST: 1926227933676693664
PLAN:
Gather
LogicalView(tableNames=[[tb_h]])
PARAMETER: [[1,2,3,4,5]]
1 row in set (0.01 sec)
As you can see from the view, the dynamic parameter list of the IN expression is reduced to one.
If the SQL template does not change elsewhere, the same execution plan will be reused no matter how many times queries with different IN parameter lists are executed.
mysql> explain select * from tb_h where id in(4566);
+----------------------------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN |
+----------------------------------------------------------------------------------------------------------------------------------------------+
| Gather(concurrent=true) |
| LogicalView(tables="tb_h[p8]", sql="SELECT `id`, `bid`, `name`, `birthday` FROM `tb_h` AS `tb_h` FORCE INDEX(PRIMARY) WHERE (`id` IN(?))") |
| HitCache:true |
| Source:PLAN_CACHE |
| TemplateId: 1d4daf02 |
+----------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
mysql> explain select * from tb_h where id in(45, 66);
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Gather(concurrent=true) |
| LogicalView(tables="tb_h[p2,p7]", shardCount=2, sql="SELECT `id`, `bid`, `name`, `birthday` FROM `tb_h` AS `tb_h` FORCE INDEX(PRIMARY) WHERE (`id` IN(?))", pruningInfo="all size:2*2(part), pruning size:2") |
| HitCache:true |
| Source:PLAN_CACHE |
| TemplateId: 1d4daf02 |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
You can see that 1d4daf02 is always the Template ID that hits the plan cache.
You may find that for more than one IN parameter request, the EXPLAIN information will contain pruningInfo information, and different parameters of the same plan will display different pruningInfo information.
This is a pre-pruning optimization performed by PolarDB-X on the CN to reduce the calculation overhead caused by an excessive number of parameters in IN queries.
In distributed databases, data is partitioned based on certain characteristics. If an excessive number of IN expression lists can be pre-pruned based on these partitioning characteristics, the number of parameters processed on each DN will be significantly reduced.
Let's first establish the legitimacy of pruning based on relational algebra.
When the partitioning expression contains an IN expression, if the IN expression is first treated as a whole and not expanded, all expressions can be converted to the disjunctive normal form (DNF) based on the Boolean conversion law.
Based on this, each conjunction clause will contain !, AND, IN, and other expressions. The effect of each parameter in the IN expression on the entire expression based on this structure will be described below.
For each conjunction clause, IN must be in an AND relationship with other expressions. Then parameters in the IN expression can be expanded separately according to the distribution law:
E AND Col IN(P1, P2, P3...,Pn)
converts to
(E AND Col = P1) OR
(E AND Col = P2) OR
(E AND Col = P3) OR
...
(E AND Col = Pn) OR
E represents other expressions related to partitioning.
Distribution law p∧(q∨r)≡(p∧q)∨(p∧r)
Assuming that the shard set calculated by the overall expression is K and the shard set calculated by each Pn sub-clause is Kn, then according to the OR relationship, we can get
K=K1 ∪ K2 ∪ K3 ∪ ... ∪ Kn
The union set K is taken by calculating each Kn respectively. In this process, if the shards corresponding to each Pn clause are recorded, you can get each shard and its corresponding parameter list under the current conjunction clause.
For each conjunction clause, we can get a set of relations between shards and the IN parameter list. As the conjunction clauses are in an OR relationship, we can merge these sets based on each shard to obtain the final relationship between shards and the parameter list. In this case, the parameter list is pruned.
! and IN are combined into NOT IN in the conjunction clause, and NOT IN is calculated as a full table scan.
If multiple IN expressions are involved, we need to consider every combination resulting from the Cartesian product. An excessively large Cartesian product can lead to an overly lengthy calculation process. Therefore, the following optimizations are made:
• Set the maximum number of calculations. IN expressions are included in the Cartesian product calculation from the longest one. If the maximum number is exceeded, the remaining IN expressions are considered as a whole and are no longer pruned.
• If all shards are covered in a single calculation, it is deemed that the expression does not have the shard pruning capability. You can exit the pruning directly.
// Create a table
CREATE TABLE `hash_tbl_todays_AutoPruning` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`bid` int(11) DEFAULT NULL,
`name` varchar(30) DEFAULT NULL,
`birthday` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `auto_shard_key_birthday` USING BTREE (`birthday`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4
PARTITION BY HASH(TO_DAYS(`birthday`))
PARTITIONS 8 ;
// Enable the EXPLAIN_PRUNING_DETAIL to observe the effect of IN pruning.
SET GLOBAL EXPLAIN_PRUNING_DETAIL=TRUE;
mysql> explain select * from hash_tbl_todays_AutoPruning where birthday in ('2024-09-08', '2024-09-10', '2024-10-08', '2023-12-30') and bid>100;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Gather(concurrent=true) |
| LogicalView(tables="hash_tbl_todays_AutoPruning[p1,p4,p7]", shardCount=3, sql="SELECT `id`, `bid`, `name`, `birthday` FROM `hash_tbl_todays_AutoPruning` AS `hash_tbl_todays_AutoPruning` WHERE ((`birthday` IN(?)) AND (`bid` > ?))", pruningInfo="all size:4*3(part), pruning size:8, pruning time:0ms, pruning detail:(TEST_P00000_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00006])->(PruneRaw('2024-09-10'));(TEST_P00000_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00000])->(PruneRaw('2023-12-30'));(TEST_P00001_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00003])->(PruneRaw('2024-09-08','2024-10-08'))") |
| HitCache:true |
| Source:PLAN_CACHE |
| TemplateId: f36182f0 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
You can see the details of IN pruning in pruningInfo:
all size:4*3(part),
pruning size:8,
pruning time:0ms,
pruning detail:(TEST_P00000_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00006])->(PruneRaw('2024-09-10'));(TEST_P00000_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00000])->(PruneRaw('2023-12-30'));(TEST_P00001_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00003])->(PruneRaw('2024-09-08','2024-10-08')
• all size: This represents the total number of IN parameter lists that will be pushed to the DN compute node if it is not pruned. In the current case, the IN expression has 4 parameters, and 3 out of 8 shards are involved in this request, so the total number of push-downs is 4*3=12.
• pruning size: This represents the number of parameters pruned by the IN expression. After pruning, each parameter is sent only to its related shard. Therefore, the pruned number is consistent with the number of IN expression parameters.
• pruning time: This represents the time consumed for pruning.
• pruning detail: This represents the specific pruning information, including the shard name, the sharded table name, and the pruned IN parameter list.
Multi-column IN expressions such as (a, b) in ((xx, xx), (yy, yy)) also support pruning.
mysql> explain select * from hash_tbl_todays_AutoPruning where (name, birthday) in (('xiaoli', '2024-09-08'), ('ming', '2024-09-10'), ('xiaoT', '2024-10-08'), ('yi', '2023-12-30')) and bid>100;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Gather(concurrent=true) |
| LogicalView(tables="hash_tbl_todays_AutoPruning[p1,p4,p7]", shardCount=3, sql="SELECT `id`, `bid`, `name`, `birthday` FROM `hash_tbl_todays_AutoPruning` AS `hash_tbl_todays_AutoPruning` WHERE ((((`name`, `birthday`)) IN(?)) AND (`bid` > ?))", pruningInfo="all size:4*3(part), pruning size:8, pruning time:0ms, pruning detail:(TEST_P00000_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00006])->(PruneRaw(('ming','2024-09-10')));(TEST_P00000_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00000])->(PruneRaw(('yi','2023-12-30')));(TEST_P00001_GROUP, [hash_tbl_todays_AutoPruning_dn1M_00003])->(PruneRaw(('xiaoli','2024-09-08'),('xiaoT','2024-10-08')))") |
| HitCache:true |
| Source:PLAN_CACHE |
| TemplateId: ce4cba38 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
For multi-level sharded tables, different columns are used for different levels. If the business needs to perform IN queries on multiple columns at the same time, pruning can also be applied.
If there are multiple IN expressions, PolarDB-X will perform Cartesian traversal to prune all IN parameters. The following figure shows an example.
On the PolarDB-X instance, we first try to create a level-2 partitioned table, and then check the EXPLAIN output for an SQL query with multiple IN expressions:
CREATE TABLE tb_k_k_tp(
id bigint not null auto_increment,
bid int,
name varchar(30),
birthday datetime not null,
primary key(id)
)
PARTITION BY KEY(bid)
PARTITIONS 2
SUBPARTITION BY KEY(id)
SUBPARTITIONS 4;
mysql> explain select * from tb_k_k_tp where bid in (1,2,3,4,5,6,7,8,9,10) and id in (11,12,13,14,15,16);
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Gather(concurrent=true) |
| LogicalView(tables="tb_k_k_tp[p1sp1,p1sp2,p1sp3,p1sp4,p2sp1,p2sp2,p2sp3,p2sp4]", shardCount=8, sql="SELECT `id`, `bid`, `name`, `birthday` FROM `tb_k_k_tp` AS `tb_k_k_tp` FORCE INDEX(PRIMARY) WHERE((`id` IN(?)) AND (`bid` IN(?)))", pruningInfo="all size:16*8(part), pruning size:76, pruning time:1ms, pruning detail:(TEST_P00000_GROUP, [tb_k_k_tp_UyWm_00004])->(PruneRaw(1,4,7,8,9,10),PruneRaw(11,12));(TEST_P00001_GROUP, [tb_k_k_tp_UyWm_00005])->(PruneRaw(1,4,7,8,9,10),PruneRaw(13,16));(TEST_P00000_GROUP, [tb_k_k_tp_UyWm_00006])->(PruneRaw(1,4,7,8,9,10),PruneRaw(14));(TEST_P00001_GROUP, [tb_k_k_tp_UyWm_00007])->(PruneRaw(1,4,7,8,9,10),PruneRaw(15));(TEST_P00000_GROUP, [tb_k_k_tp_UyWm_00000])->(PruneRaw(2,3,5,6),PruneRaw(11,12));(TEST_P00000_GROUP, [tb_k_k_tp_UyWm_00002])->(PruneRaw(2,3,5,6),PruneRaw(14));(TEST_P00001_GROUP, [tb_k_k_tp_UyWm_00003])->(PruneRaw(2,3,5,6),PruneRaw(15));(TEST_P00001_GROUP, [tb_k_k_tp_UyWm_00001])->(PruneRaw(2,3,5,6),PruneRaw(13,16))") |
| HitCache:true |
| Source:PLAN_CACHE |
| TemplateId: f70bd95a |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
From the pruning detail information in the EXPLAIN output, it can be seen that originally 16 parameters were to be sent to each of the 8 shards, meaning the DN would have to process 128 ( 16*8=128) parameter values. After pruning, only 52 (128 - 76 = 52) parameters remain to be processed.
Environment:
PolarDB-X 8C32G
Sysbench IN value tests
SELECT c FROM sbtest1 WHERE id in (?)
5 IN values, AUTO table, 400 concurrent, the primary key and shard key as the ID, and 64 sharded tables.
QPS | CN CPU | DN CPU | DN IOPS | |
---|---|---|---|---|
Pruned | 20031.74 | 100% | 53% | 10% |
Not pruned | 21322.80 | 100% | 85% | 47% |
QPS | CN CPU | DN CPU | DN IOPS | |
---|---|---|---|---|
Pruned | 7424.55 | 100% | 100% | 36% |
Not pruned | 639.39 | 20% | 74% | 100% |
During queries, IN pruning is essentially to avoid redundant calculation overhead on the DN by precalculating partitions on the CN. According to the sysbench IN query stress tests, IN pruning can greatly reduce the CPU overhead of the DN, thus providing a larger cluster capacity.
• When the number of IN parameters is small (about 5), the CN CPU becomes the bottleneck first, so the QPS without pruning is slightly higher than that with pruning. The CPU and I/O pressure on the DN will be much higher than that without pruning.
• If IN parameters are not pruned as they increase, CPU and I/O pressure on the DN will significantly increase, resulting in a QPS that falls far short of what can be achieved with pruning.
Consider a level-2 partitioned table T, where each level can only have one partition key. Set the level-1 partition key to sk1 and the level-2 partition key to sk2.
A filter condition F contains constant IN conditions on partition keys, which allows for IN pruning in linear time.
Lemma 1
For the level-2 partitioned table T, all constant IN conditions on the partition keys in the filter condition K belong to three sets X, Y, and Z, X={(...,sk1,...) in A} Y={(...,sk2,...) in B} Z={(...,sk1,sk2...) in C}, where A, B, and C are all constant vector sets.
Proof
Replace m not in M in the filter condition with not(m in M). Note that when m is null, both m not in M and not(m in M) are undefined.
Lemma 2
For a partitioned table A with n partitions and partition key sk, if there is a condition (...,sk,...) in S in the filter condition K, then S can be split into n sets S1, S2,..., Sn according to the partitioning method of sk (the sets can be empty). This condition is logically equivalent to (...,sk,...) in S1, (...,sk,...) in S2,...(...,sk,...) in Sn in different partitioned tables.
Proof
Filtering out null does not affect the result.
Consider the i-th shard Ai. As ∀sk ∈ Ai, there must be partition(sk) = i. If (...,sk,...) in S, then ∃s∈ S, sk=s.
partition(s)=i, so s∈Si is (...,sk,...) in Si and (...,sk,...) in S=>(...,sk,...) in Si. Intuitively, not((...,sk,...) in S)=>not((...,sk,...) in Si).
So ∀sk ∈ Ai, (...,sk,...) in S and (...,sk,...) in Si are consistently true or false.
∀sk ∈ Ai, the rest of K remains unchanged. Replacing (...,sk,...) in S with (...,sk,...) in Si does not change the result of any expression, so the result of K remains constant.
Lemma 3
For a partitioned table A with n partitions and partition key sk, the filter condition K has multiple IN conditions (...,sk,...) in S (...,sk,...) in T (...,sk,...) in U. S is split into n sets S1, S2, ..., Sn according to the sk partition, T is split into n sets T1, T2, ..., Tn according to the sk partitioning mode, and U is split into n sets U1, U2, ..., Un according to the sk partitioning mode. Replace it with (...,sk,...) in Si (...,sk,...) in Ti (...,sk,...) in Ui at the i-th shard.
Proof
First, apply Lemma 2 to decompose K into n conditions K1, K2, ..., Kn based on S.
For the condition Ki on shard i, substitute lemma 2, and sk in T and sk in Ti are both true or false, so you can replace them and repeatedly apply them.
Theorem
For a level-2 partitioned table T, all IN conditions that contain partition keys and only constants in the filter condition F can be pruned in partition mode.
Proof
Divide all IN conditions that contain partition keys and only constants into three sets X={(...,sk1,...) in A} Y={(...,sk2,...) in B} Z={(...,sk1,sk2...) in C} by using Lemma 1.
Consider only the level-1 partitions of T and conditions X, Z. Through Lemma 3, parameters in X, Z can be pruned to obtain the Ti of each level-1 partition and the corresponding IN condition sets Xi, Y, Zi. For each i, with partitioned table Ti, and conditions Y, Zi, by Lemma 3 again, parameters in Y, Zi can be pruned.
The above lemmas prove that IN parameters in all expressions can be pruned based on the sharding rules. Even in the case of A IN(1,2,3) OR bid>100, the parameters in A can also be pruned without affecting the correctness.
However, for the sake of costs and customer benefits, PolarDB-X does not yet support IN pruning under the OR condition.
In scenarios involving a large number of condition values or in complex distributed environments, IN queries in SQL statements are favored for their simplicity and potential efficiency, but they also have some inherent limitations. Through continuous technical optimization practices, PolarDB-X explores the following main technical means to effectively optimize IN queries. They include but not limited to:
• Optimized cost calculation mechanism: For IN queries with a large number of parameters, the cost evaluation algorithm is adjusted to more accurately predict resource consumption and select a more reasonable execution path.
• Intelligent management of the execution plan cache: By comprehensively transforming the parser, optimizer, and executor, the query engine can control the number of redundant SQL templates generated by IN parameters. This ensures that the overall performance is not affected due to plan cache overflow.
• Enhanced distributed processing capabilities: Efficient pre-processing technology is used to identify and remove unnecessary IN parameters. This reduces the workload of each DN and accelerates the query process and the overall performance of the database.
In the future, we will continue to explore and share valuable database engine optimization techniques.
Core Technology of PolarDB-X Storage Engine | Lizard XA Two-phase Commit Algorithm
Performance Improvement Tool | In-depth Analysis of PolarDB-X Columnar Query Technology
ApsaraDB - June 3, 2024
Apache Flink Community - June 14, 2024
ApsaraDB - August 23, 2024
ApsaraDB - April 10, 2024
ApsaraDB - June 4, 2024
ApsaraDB - March 19, 2025
Alibaba 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 PostgreSQL is an in-house relational database service 100% compatible with PostgreSQL and highly compatible with the Oracle syntax.
Learn MoreAlibaba Cloud PolarDB for MySQL is a cloud-native relational database service 100% compatible with MySQL.
Learn MoreA ledger database that provides powerful data audit capabilities.
Learn MoreMore Posts by ApsaraDB