The mysql_fdw extension lets you query, insert, update, and delete data in a remote MySQL database directly from AnalyticDB for PostgreSQL — without copying data into AnalyticDB for PostgreSQL first.
How it works
mysql_fdw implements the PostgreSQL foreign data wrapper (FDW) interface. You define a foreign server pointing to your MySQL instance, map a database user to MySQL credentials, and then create foreign tables that mirror MySQL tables. Queries on those foreign tables are forwarded to MySQL, and several clause types (WHERE, JOIN, aggregate, ORDER BY, LIMIT/OFFSET) are pushed down to MySQL for execution to minimize data transfer.
Before you begin
-
Install the mysql_fdw extension. See Install, update, and uninstall extensions.
-
Create a database account for your AnalyticDB for PostgreSQL instance. See Create a database account.
-
Have your MySQL host address, port, database name, and credentials ready.
Connect to a MySQL database
The setup requires three SQL statements run in sequence: CREATE SERVER, CREATE USER MAPPING, and CREATE FOREIGN TABLE (or IMPORT FOREIGN SCHEMA).
Step 1: Log in to your AnalyticDB for PostgreSQL instance
-
Go to the AnalyticDB for PostgreSQL console and click the instance ID.
-
In the upper-right corner, click Log On to Database.
-
In the Log On to Database Instance dialog box, enter your Database Account and Database Password, then click Login.
Step 2: Create a foreign server
Run CREATE SERVER to define the remote MySQL database as a foreign server:
-- Create a foreign server named mysql_svr
CREATE SERVER mysql_svr
FOREIGN DATA WRAPPER mysql_fdw
OPTIONS (host '127.0.0.1', port '3306');
Expected output:
CREATE SERVER
Replace 127.0.0.1 and 3306 with your MySQL host and port. For all available options, see CREATE SERVER.
Step 3: Create a user mapping
Run CREATE USER MAPPING to associate an AnalyticDB for PostgreSQL user with MySQL credentials:
-- Map the public role to MySQL credentials
CREATE USER MAPPING FOR public
SERVER mysql_svr
OPTIONS (username '$MYSQL_USER_NAME', password '$MYSQL_PASS');
Expected output:
CREATE USER MAPPING
Replace $MYSQL_USER_NAME and $MYSQL_PASS with actual MySQL credentials.
Step 4: Create a foreign table
Run CREATE FOREIGN TABLE to define a local table that maps to a MySQL table. Column names must match the MySQL table's column names exactly.
-- Create a foreign table mirroring the MySQL table test_foreign_table
CREATE FOREIGN TABLE test_foreign_table (
c1 INT,
c2 INT,
c3 TEXT,
c4 TEXT
)
SERVER mysql_svr
OPTIONS (dbname 'mysql_fdw_database', table_name 'test_foreign_table');
Expected output:
CREATE FOREIGN TABLE
Alternatively, use IMPORT FOREIGN SCHEMA to create foreign tables automatically for all tables in a MySQL schema:
IMPORT FOREIGN SCHEMA mysql_fdw_database
FROM SERVER mysql_svr
INTO public;
Use LIMIT TO (table1, table2) to import only specific tables, or EXCEPT (table1, table2) to import all tables except the listed ones. For all available options, see IMPORT FOREIGN SCHEMA.
Step 5: Query and modify data
After the foreign table is created, run SELECT, INSERT, UPDATE, and DELETE on it as you would on any local table:
-- Query data
SELECT * FROM test_foreign_table;
-- Insert a row
INSERT INTO test_foreign_table (c1, c2, c3, c4) VALUES (1, 2, 'foo', 'bar');
-- Update a row
UPDATE test_foreign_table SET c3 = 'updated' WHERE c1 = 1;
-- Delete a row
DELETE FROM test_foreign_table WHERE c1 = 1;
For INSERT, UPDATE, and DELETE to work, the MySQL source table must use its first column as the unique primary key column or as part of a composite primary key. This constraint does not apply to SELECT.
Usage notes
-
DML primary key requirement: For UPDATE, DELETE, and INSERT operations, the MySQL source table must use its first column as the unique primary key or as part of a composite primary key. SELECT is not subject to this constraint.
-
Connection pool: All queries in the same session reuse the same MySQL connection rather than creating a new one per query.
-
NULL sort order: MySQL sorts NULL values differently from AnalyticDB for PostgreSQL. When using ORDER BY pushdown, add an IS NULL predicate at the start of each ORDER BY clause so that MySQL places NULLs at the end of ascending result sets, matching AnalyticDB for PostgreSQL behavior.
Syntax reference
CREATE SERVER
CREATE SERVER [IF NOT EXISTS] server_name [TYPE 'server_type'] [VERSION 'server_version']
FOREIGN DATA WRAPPER fdw_name
[OPTIONS ([mpp_execute 'any | master | all segments'], option 'value' [, ...])]
Options
| Option | Type | Description | Default |
|---|---|---|---|
host |
string | IP address or hostname of the MySQL database | 127.0.0.1 |
port |
integer | Port number of the MySQL database | 3306 |
init_command |
string | SQL statement run when the connection is established | Empty |
secure_auth |
boolean | Whether to use MySQL's pre-V5.7.5 password() encryption method |
true |
use_remote_estimate |
boolean | Whether to run EXPLAIN on MySQL to collect table statistics for query planning | false |
reconnect |
boolean | Whether to allow automatic reconnection | false |
character_set |
string | Character set for the connection. auto detects the character set from the MySQL client OS |
auto |
sql_mode |
string | SQL mode in MySQL, which affects SQL syntax and data validation. See Server SQL Modes | ANSI_QUOTES |
ssl_key |
string | Path to the private key file on the MySQL client | Empty |
ssl_cert |
string | Path to the public key certificate on the MySQL client | Empty |
ssl_ca |
string | Path to the CA certificate. If specified, the foreign server uses the same certificate | Empty |
ssl_capath |
string | Path to a directory containing trusted SSL CA certificates | Empty |
ssl_cipher |
string | SSL cipher suites allowed for encryption | <none> |
fetch_size |
integer | Number of rows fetched per execution. A value set on the foreign table overrides this server-level value | 100 |
CREATE USER MAPPING
CREATE USER MAPPING [IF NOT EXISTS] FOR { user_name | USER | CURRENT_USER | PUBLIC }
SERVER server_name
[OPTIONS (option 'value' [, ...])]
Options
| Option | Type | Description | Default |
|---|---|---|---|
username |
string | Username for the MySQL database | Empty |
password |
string | Password for the MySQL database | Empty |
CREATE FOREIGN TABLE
CREATE FOREIGN TABLE [IF NOT EXISTS] table_name ([
{ column_name data_type [OPTIONS (option 'value' [, ...])] [COLLATE collation] [column_constraint [...]]
| table_constraint }
[, ...]
])
[INHERITS (parent_table [, ...])]
SERVER server_name
[OPTIONS ([mpp_execute 'any | master | all segments'], option 'value' [, ...])]
Options
| Option | Type | Description | Default |
|---|---|---|---|
dbname |
string | Name of the MySQL database containing the table | Empty |
table_name |
string | Name of the MySQL table to map to | A custom name in AnalyticDB for PostgreSQL |
fetch_size |
integer | Number of rows fetched per execution. Overrides the server-level fetch_size |
100 |
IMPORT FOREIGN SCHEMA
Use IMPORT FOREIGN SCHEMA to create foreign tables automatically for MySQL tables, instead of defining each with CREATE FOREIGN TABLE.
IMPORT FOREIGN SCHEMA remote_schema
[{ LIMIT TO | EXCEPT } (table_name [, ...])]
FROM SERVER server_name
INTO local_schema
[OPTIONS (option 'value' [, ...])]
-
LIMIT TO (table_name, ...): import only the listed tables. -
EXCEPT (table_name, ...): import all tables except the listed ones.
Options
| Option | Type | Description | Default |
|---|---|---|---|
import_default |
boolean | Whether to include column DEFAULT expressions when creating foreign tables | FALSE |
import_not_null |
boolean | Whether to include column NOT NULL constraints when creating foreign tables | TRUE |
import_enum_as_text |
boolean | Whether to map MySQL ENUM columns to TEXT. If FALSE, the system prompts you to create the ENUM type manually |
FALSE |
Performance features
mysql_fdw pushes several clause types down to MySQL for execution, reducing data transferred to AnalyticDB for PostgreSQL.
| Feature | Description |
|---|---|
| Connection pool | All queries in the same session share one MySQL connection. |
| WHERE pushdown | WHERE conditions on foreign tables are evaluated in MySQL, so only matching rows are returned. |
| Projection pushdown | Only the columns referenced in SELECT are fetched from MySQL, not the full row. |
| Prepared statement | SELECT queries use prepared statements for better performance. |
| JOIN pushdown | JOIN operations between two foreign tables in the same MySQL database run in MySQL. Supported join types: INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN. Not supported: FULL OUTER JOIN, SEMI JOIN, ANTI JOIN. Only JOINs with relational and arithmetic operators are pushed down. |
| Aggregate pushdown | Aggregate functions (MIN, MAX, SUM, AVG, COUNT) run in MySQL. |
| ORDER BY pushdown | ORDER BY clauses run in MySQL to return an ordered result set. Add an IS NULL predicate before each ORDER BY column to handle NULL sort order differences between MySQL and AnalyticDB for PostgreSQL. |
| LIMIT and OFFSET pushdown | LIMIT and OFFSET clauses run in MySQL, reducing data transferred. |
Compatibility
ApsaraDB RDS for MySQL
| Version | Compatible |
|---|---|
| 5.5 | Yes |
| 5.6 | Yes |
| 5.7 | Yes |
| 8.0 | Yes |
Self-managed MySQL database
MySQL versions earlier than 5.7 are no longer maintained. MySQL 5.7 and 8.0 are in common use.
| Version | Compatible |
|---|---|
| 5.7 | Yes |
| 8.0 (mainly used) | Yes |
PolarDB for MySQL
| Version | Compatible |
|---|---|
| 5.6 | Yes |
| 5.7 | Yes |
| 8.0.1 | Yes |
| 8.0.2 | Yes |
Data type mapping
| No. | MySQL data type | AnalyticDB for PostgreSQL data type |
|---|---|---|
| 1 | BIT(N) | BIT(N) |
| 2 | TINYINT | SMALLINT |
| 3 | TINYINT UNSIGNED | SMALLINT |
| 4 | SMALLINT | SMALLINT |
| 5 | SMALLINT UNSIGNED | BIGINT |
| 6 | MEDIUMINT | INT |
| 7 | MEDIUMINT UNSIGNED | INT |
| 8 | INT | INT |
| 9 | INT UNSIGNED | BIGINT |
| 10 | BIGINT | BIGINT |
| 11 | BIGINT UNSIGNED | NUMERIC |
| 12 | DECIMAL(M,N) | NUMERIC |
| 13 | FLOAT | REAL |
| 14 | DOUBLE(M,N) | DOUBLE PRECISION |
| 15 | DATE | DATE |
| 16 | DATETIME | DATETIME without TIME zone |
| 17 | TIMESTAMP | DATETIME without TIME zone |
| 18 | TIME | TIME without TIME zone |
| 19 | YEAR[4] | Not supported |
| 20 | CHAR[N] | CHAR[N] |
| 21 | VARCHAR[N] | CHARACTER VARYING(N) |
| 22 | BINARY[N] | BYTEA |
| 23 | VARBINARY[N] | BYTEA |
| 24 | TINYBLOB | Not supported |
| 25 | TINYTEXT | TEXT |
| 26 | BLOB | BYTEA |
| 27 | TEXT | TEXT |
| 28 | MEDIUMBLOB | BYTEA |
| 29 | MEDIUMTEXT | TEXT |
| 30 | LONGBLOB | BYTEA |
| 31 | LONGTEXT | TEXT |
| 32 | ENUM(",") | Prompted to manually create data of the ENUM type |
| 33 | SET('','') | Not supported |
| 34 | GEOMETRY | Not supported |
| 35 | POINT | POINT |
| 36 | LINESTRING | Not supported |
| 37 | POLYGON | POLYGON |
| 38 | MULTIPOINT | Not supported |
| 39 | MULTILINESTRING | Not supported |
| 40 | MULTIPOLYGON | Not supported |
| 41 | GEOMETRCOLLECTION | Not supported |
| 42 | JSON | JSON |