PolarDB lets you query CSV-formatted cold data directly in Object Storage Service (OSS) without importing it into your database. This reduces storage costs for infrequently accessed data while keeping the data queryable through standard SQL.
Quick start
The following example shows the complete workflow: create a server, create a foreign table, and query data.
-- Step 1: Create an OSS server (newer cluster syntax)
CREATE SERVER my_oss_server
FOREIGN DATA WRAPPER oss OPTIONS
(
EXTRA_SERVER_INFO '{"oss_endpoint": "oss-cn-hangzhou-internal.aliyuncs.com",
"oss_bucket": "my-bucket",
"oss_access_key_id": "LTAI5t...",
"oss_access_key_secret": "...",
"oss_prefix": "csv-data"}'
);
-- Step 2: Create an OSS foreign table (maps to csv-data/orders.CSV in OSS)
CREATE TABLE orders (
id INT,
amount DECIMAL(10,2),
created_at DATETIME
) ENGINE=CSV CONNECTION="my_oss_server";
-- Step 3: Query the data
SELECT id, amount FROM orders WHERE amount > 100;Verify the table was created with the CSV engine:
SHOW CREATE TABLE orders;The output must contain ENGINE=CSV. If it does not, your cluster version does not support OSS foreign tables. See Prerequisites.
For details on each step, see Set up OSS foreign tables.
Prerequisites
Before you begin, ensure that you have:
A PolarDB cluster running MySQL 8.0.1 (revision 8.0.1.1.25.4 or later) or MySQL 8.0.2 (revision 8.0.2.2.1 or later). To check your version, see Query the engine version
SERVERS_ADMINpermissions on your PolarDB cluster. See PermissionsAn OSS bucket in the same zone as your PolarDB cluster. See Upload data
An AccessKey ID and AccessKey secret for your RAM user or Alibaba Cloud account
How it works
OSS foreign tables map directly to CSV files stored in an OSS bucket. Queries run against the foreign table read from OSS in real time — no data is copied into PolarDB.
The workflow has four steps:
Create an OSS server — registers the OSS connection (bucket, endpoint, credentials).
Upload data — place your CSV files in the OSS directory configured on the server.
Create an OSS foreign table — binds a PolarDB table to a specific CSV file.
Query data — use standard SQL
SELECTstatements against the foreign table.

Limitations
Only CSV-formatted data is supported. Compressed CSV files and geospatial data types are not supported.
Supported operations:
CREATE,SELECT, andDROP.DROPremoves the table definition from PolarDB but does not delete the CSV file in OSS.OSS foreign tables do not support indexes, partitioning, or transactions.
NULL values require MySQL 8.0.1 revision 8.0.1.1.28 or later, or MySQL 8.0.2 revision 8.0.2.2.5 or later.
The .CSV file extension must be uppercase. A file named t1.csv (lowercase) will not be found. Name files t1.CSV.
Supported data types
Numeric types
| Data type | Size | Signed range | Unsigned range | Description |
|---|---|---|---|---|
| TINYINT | 1 byte | -128 to 127 | 0 to 255 | Small integer |
| SMALLINT | 2 bytes | -32768 to 32767 | 0 to 65535 | Integer |
| MEDIUMINT | 3 bytes | -8388608 to 8388607 | 0 to 16777215 | Integer |
| INT or INTEGER | 4 bytes | -2147483648 to 2147483647 | 0 to 4294967295 | Integer |
| BIGINT | 8 bytes | -9,223,372,036,854,775,808 to 9223372036854775807 | 0 to 18446744073709551615 | Big integer |
| FLOAT | 4 bytes | -3.402823466E+38 to -1.175494351E-38; 0; 1.175494351E-38 to 3.402823466351E+38 | 0; 1.175494351E-38 to 3.402823466E+38 | Single-precision float |
| DOUBLE | 8 bytes | -2.2250738585072014E-308 to -1.7976931348623157E+308; 0; 1.7976931348623157E+308 to 2.2250738585072014E-308 | 0; 1.7976931348623157E+308 to 2.2250738585072014E-308 | Double-precision float |
| DECIMAL | M+2 if M>D, else D+2 | Depends on M and D | Depends on M and D | Decimal |
Date and time types
| Data type | Size | Range | Format | Description |
|---|---|---|---|---|
| DATE | 3 bytes | 1000-01-01 to 9999-12-31 | YYYY-MM-DD | Date |
| TIME | 3 bytes | -838:59:59 to 838:59:59 | HH:MM:SS | Time or duration |
| YEAR | 1 byte | 1901 to 2155 | YYYY | Year |
| DATETIME | 8 bytes | 1000-01-01 00:00:00 to 9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | Combined date and time |
| TIMESTAMP | 4 bytes | 1970-01-01 00:00:00 to 2038-01-19 03:14:07 | YYYY-MM-DD HH:MM:SS | Timestamp |
For DATETIME and TIMESTAMP, month and day must always be two digits (for example, 2020-01-01, not 2020-1-1). Single-digit values cause pushdown queries to fail silently.
String types
| Data type | Size | Description |
|---|---|---|
| CHAR | 0–255 bytes | Fixed-length string |
| VARCHAR | 0–65535 bytes | Variable-length string |
| TINYBLOB | 0–255 bytes | Binary object up to 255 bytes |
| TINYTEXT | 0–255 bytes | Short string |
| BLOB | 0–65535 bytes | Binary object |
| TEXT | 0–65535 bytes | String |
| MEDIUMBLOB | 0–16777215 bytes | Medium binary object |
| MEDIUMTEXT | 0–16777215 bytes | Medium string |
| LONGBLOB | 0–4294967295 bytes | Long binary object |
| LONGTEXT | 0–4294967295 bytes | Long string |
Set up OSS foreign tables
Step 1: Create an OSS server
An OSS server stores the connection configuration for your OSS bucket. All other methods of connecting to OSS have been disabled for security reasons.
Permissions
SERVERS_ADMIN permission is required to create an OSS server. Check your current permissions:
SHOW GRANTS FOR username;If you do not have SERVERS_ADMIN permission, follow the steps for your account type:
Standard account without SERVERS_ADMIN: Ask a privileged account to run:
GRANT SERVERS_ADMIN ON *.* TO 'users'@'%' WITH GRANT OPTION;Privileged account without SERVERS_ADMIN: In the PolarDB console, go to the cluster details page, choose Settings and Management > Accounts, find your privileged account on the User Account tab, and click Reset Permissions.
After creating an OSS server, a privileged account can view server details (with access keys encrypted):
SELECT Server_name, Extra_server_info FROM mysql.servers;Choose the correct syntax for your cluster version
Two syntax variants exist for CREATE SERVER. Check your revision version first (see Query the engine version) and use the matching syntax.
Statements for clusters of later versions
Newer clusters (MySQL 8.0.1 revision 8.0.1.1.28 or later, or MySQL 8.0.2 revision 8.0.2.2.5 or later):
CREATE SERVER <server_name>
FOREIGN DATA WRAPPER oss OPTIONS
(
[DATABASE '<my_database_name>',]
EXTRA_SERVER_INFO '{"oss_endpoint": "<my_oss_endpoint>","oss_bucket": "<my_oss_bucket>","oss_access_key_id": "<my_oss_access_key_id>","oss_access_key_secret": "<my_oss_access_key_secret>","oss_prefix":"<my_oss_prefix>","oss_sts_token":"<my_oss_sts_token>"}'
);Statements for clusters of earlier versions
Older clusters (MySQL 8.0.1 revision 8.0.1.1.25.4 to 8.0.1.1.28, or MySQL 8.0.2 revision 8.0.2.2.1 to 8.0.2.2.5):
oss_prefix and oss_sts_token are not supported in this version.
CREATE SERVER <server_name>
FOREIGN DATA WRAPPER oss OPTIONS
(
[DATABASE '<my_database_name>',]
EXTRA_SERVER_INFO '{"oss_endpoint": "<my_oss_endpoint>","oss_bucket": "<my_oss_bucket>","oss_access_key_id":"<my_oss_access_key_id>","oss_access_key_secret":"<my_oss_access_key_secret>"}'
);Parameters
| Parameter | Required | Description |
|---|---|---|
server_name | Yes | A globally unique name for the OSS server. Up to 64 characters, case-insensitive. Names longer than 64 characters are truncated automatically. |
my_database_name | No | An OSS directory path for CSV files. Has the same effect as oss_prefix. Use oss_prefix instead when available. If both are set, the final path is oss_prefix/DATABASE. |
my_oss_endpoint | Yes | The OSS endpoint. Use an internal endpoint (containing internal) when connecting from an Alibaba Cloud server to avoid internet traffic charges — for example, oss-cn-xxx-internal.aliyuncs.com. |
my_oss_bucket | Yes | The OSS bucket where your CSV files are stored. Deploy the bucket in the same zone as your PolarDB cluster to reduce latency. |
my_oss_access_key_id | Yes | The AccessKey ID of your RAM user or Alibaba Cloud account. |
my_oss_access_key_secret | Yes | The AccessKey secret of your RAM user or Alibaba Cloud account. |
my_oss_prefix | No | The OSS directory path for CSV files. (Newer clusters only.) |
my_oss_sts_token | No | A temporary credential from Security Token Service (STS). Required if you use STS temporary access credentials. Supported on MySQL 8.0.1 revision 8.0.1.1.29 or later, or MySQL 8.0.2 revision 8.0.2.2.6 or later. |
oss_sts_tokenhas a default expiration time. When it expires, reset allEXTRA_SERVER_INFOvalues usingALTER SERVER:
ALTER SERVER server_name OPTIONS(EXTRA_SERVER_INFO '{"oss_endpoint": "<my_oss_endpoint>",
"oss_bucket": "<my_oss_bucket>", "oss_access_key_id": "<my_oss_access_key_id>",
"oss_access_key_secret": "<my_oss_access_key_secret>", "oss_prefix":"<my_oss_prefix>", "oss_sts_token": "<my_oss_sts_token>"}');Step 2: Upload data
Use ossutil to upload local CSV files to your OSS bucket. Follow these naming rules:
Place CSV files in the
DATABASEoross_prefixdirectory configured on your OSS server.Name each file
<foreign_table_name>.CSV. The.CSVextension must be uppercase. For example, if the table is namedt1, the file must bet1.CSV.Make sure the CSV columns match the columns defined in the OSS foreign table.
For existing MySQL data, upload the data file directly and create the OSS foreign table based on the same table definition.
Step 3: Create an OSS foreign table
After creating an OSS server, create a foreign table to bind it to a specific CSV file:
CREATE TABLE <table_name> (create_definition,...) ENGINE=CSV CONNECTION="<connection_string>";The connection_string is a /-separated path with up to three components:
server_name[/oss_path][/data_file_name]| Component | Required | Description |
|---|---|---|
server_name | Yes | The OSS server name from CREATE SERVER. |
oss_path | No | A subdirectory path within the oss_prefix in OSS. Requires MySQL 8.0.1 revision 8.0.1.1.28 or later, or MySQL 8.0.2 revision 8.0.2.2.5 or later. If specified, data_file_name is also required. |
data_file_name | No | The name of the data file, without the .CSV extension. If omitted, the table name is used. |
Example — connect to a file in a subdirectory:
CREATE TABLE t1 (id INT) ENGINE=CSV CONNECTION="server_name/a/b/c/d/t1";This connects t1 to the file at oss_prefix/a/b/c/d/t1.CSV in OSS.
Example — point a table to a file with a different name:
-- Queries t2.CSV in the oss_prefix path
CREATE TABLE t1 (id INT) ENGINE=CSV CONNECTION="server_name/t2";After creation, verify the table was created with the CSV engine:
SHOW CREATE TABLE t1;Check that the output contains ENGINE=CSV. If it does not, your cluster version is too old to support OSS foreign tables. See Prerequisites.
Step 4: Query data
Run standard SQL statements against OSS foreign tables:
-- Count rows
SELECT count(*) FROM t1;
-- Filter by range
SELECT id FROM t1 WHERE id < 10 AND id > 1;
-- Point lookup
SELECT id FROM t1 WHERE id = 3;
-- Join with another table
SELECT id FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name LIKE "%er%";If a query produces a warning instead of an error, run SHOW WARNINGS; to view the details.
Common errors
| Error | Cause | Fix |
|---|---|---|
OSS error: No corresponding data file on the OSS engine. | The CSV file was not found in OSS. | Verify the file exists at the correct path and that the name matches <table_name>.CSV with an uppercase .CSV extension. |
There is not enough memory space for OSS transmission. Currently requested memory %d. | Insufficient memory for the OSS query. | Increase loose_csv_max_oss_threads on the Parameters page, or run FLUSH TABLE to release threads held by other OSS tables. |
ERROR 8054 (HY000): OSS error: ... Couldn't connect to server ... aliyuncs.com:80 | The cluster cannot reach the OSS server. | Confirm the cluster and OSS bucket are in the same zone. If not, move them to the same zone. If they are already in the same zone, switch to a public endpoint and contact Alibaba Cloud support if the error persists. |
Work with NULL values
NULL value support requires MySQL 8.0.1 revision 8.0.1.1.28 or later, or MySQL 8.0.2 revision 8.0.2.2.5 or later.
Insert NULL values
Set NULL_MARKER when creating the table to tell PolarDB which string in the CSV represents a NULL value. The default is NULL.
CREATE TABLE `t1` (
`id` int(11) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`time` timestamp NULL DEFAULT NULL
) ENGINE=CSV NULL_MARKER='NULL' CONNECTION='server_name';In the CSV file, any field whose value matches NULL_MARKER (without enclosing double quotation marks) is treated as NULL. If the value is enclosed in double quotation marks, it is treated as a string.
NULL_MARKER restrictions:
Cannot be a number-only string or empty
Cannot contain
",\n,\r, or,
Example:
CSV contents:
1,"xiaohong","2022-01-01 00:00:00"
NULL,"xiaoming","2022-02-01 00:00:00"
3,NULL,"2022-03-01 00:00:00"
4,"xiaowang",NULLQuery result:
SELECT * FROM t1;
+------+----------+---------------------+
| id | name | time |
+------+----------+---------------------+
| 1 | xiaohong | 2022-01-01 00:00:00 |
| NULL | xiaoming | 2022-02-01 00:00:00 |
| 3 | NULL | 2022-03-01 00:00:00 |
| 4 | xiaowang | NULL |
+------+----------+---------------------+To check the current NULL_MARKER value for a table:
SHOW CREATE TABLE t1;Read NULL values
When PolarDB reads a NULL value from a CSV file:
Column allows NULL: the field is set to NULL.
Column is NOT NULL: behavior depends on
sql_mode.STRICT_TRANS_TABLES: an error is reported.Other modes: the field is set to the column's default value, or the MySQL type default if no default is defined. Run
SHOW WARNINGS;to view the warning details.
To check the current sql_mode:
SHOW VARIABLES LIKE "sql_mode";To change sql_mode, go to Settings and Management > Parameters in the PolarDB console. For details, see Modify parameters.
Example — NOT NULL column with a NULL value in the CSV:
Table:
CREATE TABLE `t` (
`id` int(11) NOT NULL
) ENGINE=CSV
CONNECTION="server_name";CSV (t.csv):
NULL
2With STRICT_TRANS_TABLES:
SELECT * FROM t;
ERROR 1364 (HY000): Field 'id' doesn't have a default valueWithout STRICT_TRANS_TABLES:
SELECT * FROM t;
+----+
| id |
+----+
| 0 |
| 2 |
+----+
2 rows in set, 1 warning (0.00 sec)The value 0 is the MySQL default for INT. To see the warning:
SHOW WARNINGS;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1364 | Field 'id' doesn't have a default value |
+---------+------+-----------------------------------------+Query optimization
Engine condition pushdown sends eligible WHERE conditions to OSS for evaluation, reducing the data transferred to PolarDB. It is disabled by default.
Enable pushdown for the current session:
SET SESSION optimizer_switch='engine_condition_pushdown=on';Pushdown requirements
For a condition to be pushed down, all of the following must be true:
The CSV file is UTF-8 encoded.
Only a single file is queried (no JOIN, ORDER BY, GROUP BY, or HAVING).
The WHERE clause uses only supported operators:
>,<,>=,<=,==,LIKE,IN,AND,OR,+,-,*,/.The WHERE clause contains no aggregation (for example,
WHERE max(age) > 100is not allowed).The query has at most 1,000 columns, each column name at most 1,024 bytes.
The LIKE clause has at most 5 wildcards (
%).The IN clause has at most 1,024 constants.
Each column and row in the CSV is at most 256 KB.
The SQL statement is at most 16 KB, with at most 20 expressions after WHERE and at most 100 aggregation operations.
View the execution plan
Use EXPLAIN to see which conditions are pushed down.
Default format:
EXPLAIN SELECT count(*) FROM `t1` WHERE `id` > 5 AND `id` < 100 AND `name` LIKE "%1%%%%%" GROUP BY `id` ORDER BY `id` DESC;Conditions listed after With pushed engine condition are evaluated in OSS. All other conditions are evaluated locally.
Tree format (requires PolarDB for MySQL 8.0.2):
EXPLAIN FORMAT=tree SELECT count(*) FROM `t1` WHERE `id` > 5 AND `id` < 100 AND `name` LIKE "%1%%%%%" GROUP BY `id` ORDER BY `id` DESC;Conditions listed after engine conditions: are evaluated in OSS.
JSON format:
EXPLAIN FORMAT=json SELECT count(*) FROM `t1` WHERE `id` > 5 AND `id` < 100 AND `name` LIKE "%1%%%%%" GROUP BY `id` ORDER BY `id` DESC;Conditions in the engine_condition field are evaluated in OSS.
Disable pushdown
If your data contains characters incompatible with pushdown, you will see:
OSS error: The current query does not support engine condition pushdown. You need to use NO_ECP() hint or set optimizer_switch = 'engine_condition_pushdown=OFF' to turn off the condition push down function.Disable pushdown per query with a hint:
SELECT /*+ NO_ECP(t1) */ `j` FROM `t1` WHERE `j` LIKE "%c%" LIMIT 10;Disable pushdown for the current session:
SET SESSION optimizer_switch='engine_condition_pushdown=off';Check the current status:
SELECT @@optimizer_switch;Parameters
Configure these parameters in the PolarDB console under Settings and Management > Parameters.
| Parameter | Level | Default | Valid range | Description |
|---|---|---|---|---|
loose_csv_oss_buff_size | Session | 134217728 bytes | 4096–134217728 | Memory allocated per OSS thread. |
loose_csv_max_oss_threads | Global | 1 | 1–100 | Maximum number of concurrent OSS threads. |
Maximum OSS memory usage = loose_csv_max_oss_threads × loose_csv_oss_buff_size.
Keep total OSS memory usage below 5% of the node's memory capacity to avoid out-of-memory issues.
Synchronize OSS server information between nodes
The primary node and all read-only nodes in a PolarDB cluster share the same OSS server configuration. Changes to the OSS server are synchronized to read-only nodes in a lock-free manner, so operations on each node remain independent.
If a read-only node thread holds the OSS server lock, synchronization may be delayed. To manually trigger synchronization for a specific node, run either of the following (replace pi-bpxxxxxxxx with the actual node ID):
/*force_node='pi-bpxxxxxxxx'*/ FLUSH PRIVILEGES;
/*force_node='pi-bpxxxxxxxx'*/ FLUSH TABLE oss_foreign_table;