Iceberg external tables (XIHE SQL)
The XIHE engine of AnalyticDB for MySQL natively supports the Apache Iceberg lake table format. You can use standard SQL to create Iceberg tables and perform data writes, queries, and schema changes.
Prerequisites
-
The cluster edition is Enterprise Edition, Basic Edition, or Data Lakehouse Edition.
-
The kernel version of the cluster is 3.2.3.0 or later.
-
An external database is created. For more information, see CREATE EXTERNAL DATABASE.
-
To use the internal lake mode (AnalyticDB-managed lake storage), you must submit a ticket to enable the lake storage feature. For more information, see Lake storage.
Background
Apache Iceberg is an open data lake table format that supports features such as ACID transactions, schema evolution, and partition transforms. Data is stored in Parquet format on OSS, and any Iceberg-compatible compute engine can directly read the data.
AnalyticDB supports two storage modes. The storage mode is determined when you create a table and cannot be changed afterward:
|
Dimension |
Internal lake (managed lake storage) |
External lake (user-owned OSS) |
|
Storage management |
Fully managed by AnalyticDB |
User-managed OSS bucket |
|
Key table creation parameter |
|
|
|
Activation method |
Submit a ticket to apply for activation |
No additional activation required. Only authorization is needed |
|
Applicable scenario |
New projects that require simplified O&M |
Existing OSS data or self-managed storage required |
Create a table
Syntax
CREATE TABLE [IF NOT EXISTS] <db>.<table> (
<col1> <type1> [COMMENT '<comment>'],
<col2> <type2> [COMMENT '<comment>'],
...
)
[COMMENT '<table_comment>']
[PARTITIONED BY (<partition_expr1>[, <partition_expr2>, ...])]
STORED AS ICEBERG
[LOCATION '<oss_path>']
[TBLPROPERTIES (
'<key1>' = '<value1>',
...
)];
|
Clause |
Required |
Description |
|
|
Yes |
Declares the table format as Iceberg. |
|
|
No |
Partition expression. Identity partitioning (using column values directly) and transform function partitioning ( |
|
|
Required for external lake |
Points to a user-owned OSS path. The format is |
|
|
Required for internal lake |
For the internal lake mode, you must include |
The following table properties can be configured in TBLPROPERTIES:
|
Property |
Default value |
Description |
|
|
N/A |
Required for the internal lake mode. Set the value to |
|
|
N/A |
Required for the internal lake mode. Set the value to the name of the OSS bucket allocated by AnalyticDB. |
|
|
|
The Iceberg format version. Both |
|
|
N/A |
In external lake mode, if you want to point to existing Iceberg data, specify the OSS path of the |
|
|
N/A |
Identifier columns (similar to primary keys) for row-level DELETE. Format: |
External lake table example
-- Create a database
CREATE DATABASE IF NOT EXISTS lake_db;
-- Create an external lake Iceberg table
CREATE TABLE lake_db.orders (
order_id BIGINT COMMENT 'Order ID',
user_id BIGINT COMMENT 'User ID',
status STRING COMMENT 'Order status',
total_amount DECIMAL(18, 2) COMMENT 'Order amount',
created_at TIMESTAMP COMMENT 'Order time'
)
COMMENT 'Orders table'
PARTITIONED BY (dt DATE)
STORED AS ICEBERG
LOCATION 'oss://<your-bucket>/warehouse/lake_db/orders/';
In external lake mode, the engine automatically creates directories and metadata files in the specified OSS path. You do not need to create the OSS directory in advance.
Internal lake table example
CREATE TABLE lake_db.orders (
order_id BIGINT COMMENT 'Order ID',
user_id BIGINT COMMENT 'User ID',
status STRING COMMENT 'Order status',
total_amount DECIMAL(18, 2) COMMENT 'Order amount',
created_at TIMESTAMP COMMENT 'Order time'
)
COMMENT 'Orders table'
PARTITIONED BY (dt DATE)
STORED AS ICEBERG
TBLPROPERTIES (
'catalog_type' = 'ADB',
'adb_lake_bucket' = 'adb-lake-cn-<region>-xxxx'
);
When you create an internal lake table, you do not need to specify LOCATION. The storage path is automatically allocated by AnalyticDB. You can find the value of adb_lake_bucket on the lake storage page in the console.
CTAS (CREATE TABLE AS SELECT)
CTAS creates a table and writes the query results at the same time. The column names and types are inferred from the SELECT statement.
CREATE TABLE lake_db.orders_copy
PARTITIONED BY (dt DATE)
STORED AS ICEBERG
LOCATION 'oss://<your-bucket>/warehouse/lake_db/orders_copy/'
AS SELECT * FROM lake_db.orders;
Partition transform functions
In addition to identity partitioning (using column values directly), Iceberg supports transform function partitioning based on existing columns. You do not need to specify partition values during writes. The engine automatically routes data based on column values.
|
Function |
Applicable types |
Example |
|
identity |
Any |
|
|
|
DATE / TIMESTAMP |
|
|
|
DATE / TIMESTAMP |
|
|
|
DATE / TIMESTAMP |
|
|
|
TIMESTAMP |
|
When you use day(created_at) for partitioning, you do not need to maintain a separate dt column. Iceberg automatically extracts the date from created_at for partitioning:
CREATE TABLE lake_db.orders_by_day (
order_id BIGINT,
user_id BIGINT,
status STRING,
total_amount DECIMAL(18, 2),
created_at TIMESTAMP
)
PARTITIONED BY (day(created_at))
STORED AS ICEBERG
LOCATION 'oss://<your-bucket>/warehouse/lake_db/orders_by_day/';
Other DDL operations
-- View the complete CREATE TABLE statement
SHOW CREATE TABLE lake_db.orders;
-- View the column structure
DESCRIBE lake_db.orders;
-- Drop a table
DROP TABLE IF EXISTS lake_db.orders;
Executing DROP TABLE on an internal lake table permanently deletes both the data files and metadata on OSS. This operation is irreversible. The behavior of DROP TABLE on an external lake table depends on the ownership settings of the storage path.
Write data
INSERT INTO (append)
INSERT INTO lake_db.orders
SELECT * FROM VALUES
(1001, 501, 'paid', 299.90, TIMESTAMP '2026-06-11 10:00:00', DATE '2026-06-11'),
(1002, 502, 'pending', 158.00, TIMESTAMP '2026-06-11 10:05:00', DATE '2026-06-11'),
(1003, 503, 'shipped', 450.00, TIMESTAMP '2026-06-11 10:10:00', DATE '2026-06-12')
AS t(order_id, user_id, status, total_amount, created_at, dt);
The INSERT syntax must use the INSERT INTO t SELECT * FROM VALUES (...) form. Using INSERT INTO t VALUES (...) directly causes an error.
INSERT OVERWRITE (overwrite)
On a partitioned table, INSERT OVERWRITE uses a dynamic partition overwrite strategy: only partitions present in the SELECT result are replaced, leaving other partitions unchanged.
-- Only overwrite the data in the dt='2026-06-11' partition. Other partitions are not affected.
INSERT OVERWRITE lake_db.orders
SELECT * FROM VALUES
(2001, 601, 'paid', 999.00, TIMESTAMP '2026-06-11 12:00:00', DATE '2026-06-11')
AS t(order_id, user_id, status, total_amount, created_at, dt);
DELETE (row-level delete)
Iceberg tables support row-level deletion. To use DELETE, the following requirements must be met:
-
format_versionis set to'3'when the table is created. -
identifier-fields(identifier columns, similar to primary keys) is specified in TBLPROPERTIES when the table is created.
-- Create a table: specify format_version=3 and identifier-fields
CREATE TABLE lake_db.orders_v3 (
order_id BIGINT,
user_id BIGINT,
status STRING,
total_amount DECIMAL(18, 2),
created_at TIMESTAMP
)
PARTITIONED BY (day(created_at))
STORED AS ICEBERG
LOCATION 'oss://<your-bucket>/warehouse/lake_db/orders_v3/'
TBLPROPERTIES (
'format-version' = '3',
'identifier-fields' = '[order_id]'
);
-- Delete rows that meet the condition
DELETE FROM lake_db.orders_v3 WHERE status = 'cancelled';
Query data
Basic queries
SELECT * FROM lake_db.orders WHERE dt = DATE '2026-06-11';
SELECT dt, COUNT(*) AS cnt, SUM(total_amount) AS total
FROM lake_db.orders
GROUP BY dt;
Partition pruning
When the WHERE clause references partition columns or transform functions, the engine automatically prunes irrelevant partitions and scans only matching data files.
-- Identity partitioning: match the partition column directly
SELECT * FROM lake_db.orders WHERE dt = DATE '2026-06-11';
-- day(col) partitioning: match the time range
SELECT * FROM lake_db.orders_by_day
WHERE created_at >= TIMESTAMP '2026-06-11 00:00:00'
AND created_at < TIMESTAMP '2026-06-12 00:00:00';
Predicate pushdown
Iceberg pushes WHERE predicates down to the data file level and uses Parquet file statistics (min/max/null count) to skip row groups that do not match. This reduces the amount of data read. Supported predicates include equality (=), range (<, >, BETWEEN), and IN.
Modify table schema
Add columns
ALTER TABLE lake_db.orders ADD COLUMNS (
region STRING COMMENT 'Order region'
);
-- Verify
DESCRIBE lake_db.orders;
The new column is appended to the end of the column list. The value of the new column is NULL for existing rows.
New columns must be nullable. Adding NOT NULL columns is not supported. Schema changes only modify the schema definition in metadata.json and do not rewrite existing data files.
Type promotion
Safe upward type promotion is supported. For example, INT to BIGINT:
ALTER TABLE lake_db.orders CHANGE COLUMN order_id order_id BIGINT;
Configuration parameters
You can set these parameters by using Config or Hint to adjust the write and query behavior of Iceberg tables.
|
Parameter |
Description |
Restart required |
|
|
The maximum number of partitions allowed per writer during writes. Default value: 100. |
No |
|
|
Enables or disables the metadata cache. Default value: true. |
No |
|
|
The manifest cache strategy for queries. Valid values: |
No |
The following instance-level parameters require an instance restart to take effect:
|
Parameter |
Description |
Default value |
|
|
The master switch for manifest file caching. |
false |
|
|
The maximum total bytes for the manifest cache. |
104857600 (100 MB) |
|
|
The expiration time for cache entries, in milliseconds. |
0 (never expires) |
|
|
The maximum bytes of a single manifest file that can be cached. Files exceeding this limit are not cached. |
8388608 (8 MB) |
Limits
-
Use
STRINGfor string types.VARCHARandCHAR(N)are not supported. -
STORED AS ICEBERGis a required clause. The table is not created as an Iceberg table if this clause is missing. -
Both
format_version='2'(default) and'3'are supported. Row-level DELETE requires'3'. -
The INSERT syntax must use the
INSERT INTO t SELECT * FROM VALUES (...)form. TheINSERT INTO t VALUES (...)form is not supported. -
INSERT OVERWRITE uses a dynamic partition overwrite strategy for partitioned tables. Only the partitions involved in the SELECT result are replaced.
-
Row-level DELETE requires
format_version='3'andidentifier-fields. -
Columns added by ALTER TABLE ADD COLUMNS must be nullable.
-
The storage mode (internal lake or external lake) cannot be changed after the table is created.
-
Too many partitions can degrade performance. For high-cardinality columns, use
day()ormonth()to limit the partition count.