Applications often need to read data that lives outside Oracle. A common example is a MySQL database used by a separate application, while reports or PL/SQL code in Oracle need access to the same data. Instead of building an export/import process, Oracle can query MySQL directly through a heterogeneous database link.
This is done with Oracle Database Gateway for ODBC (DG4ODBC),
which ships with the Oracle Database home, together with the
MySQL Connector/ODBC driver and the
unixODBC driver manager. Once configured, MySQL tables can be
queried from Oracle using normal @dblink syntax.
(HS=OK)
→ listener static SID → dg4odbc process →
unixODBC → MySQL ODBC driver → MySQL server.
Environment Used in This Guide
The procedure below was performed on an Oracle 19c database running on Oracle Linux 8 with Grid Infrastructure (the listener runs from the Grid home). The target is a MySQL 8.4 server.
| Component | Source (Oracle) | Target (MySQL) |
|---|---|---|
| Host | oracle-vm |
mysql-vm |
| IP Address | 192.168.56.101 |
192.168.56.110 |
| OS | Oracle Linux 8.8 |
– |
| Version | Oracle 19c |
MySQL 8.4 |
| PDB / Database | PRODPDB |
salesdb |
| User / Schema | APPUSER |
app_reader |
| Port | 1521 |
3306 |
| Item | Value Used in This Guide |
|---|---|
| DB home (oracle user) | /u01/app/oracle/product/19.0.0.0/dbhome_1 |
| Grid home (grid user) | /u01/app/19.0.0.0/grid |
| ODBC DSN / HS SID / TNS alias | MYSQLDSN |
| Database link | MYSQL_LINK |
init<SID>.ora) and the TNS alias keeps the configuration
easy to follow and avoids mismatches between the layers.
Prerequisites
- Network access from the Oracle server to the MySQL server on port
3306. - A MySQL user allowed to connect from the Oracle server's IP address, with
the required privileges (normally
SELECT) on the target database. - Root access on the Oracle server to install packages and edit
/etc/odbc.ini. - Access to the
oracleOS user, and to thegridOS user if the listener runs from Grid Infrastructure. CREATE DATABASE LINKprivilege for the Oracle schema that will own the link.
[root@oracle-vm ~]# nc -zv 192.168.56.110 3306
listener.ora, tnsnames.ora or
/etc/odbc.ini, take a dated copy of each file. The listener file in
particular is shared by every database on the server.
Step 1: Check the OS Version
The MySQL repository package depends on the OS major version, so confirm it first.
[root@oracle-vm ~]# cat /etc/os-release | grep -E '^(NAME|VERSION_ID)'
NAME="Oracle Linux Server"
VERSION_ID="8.8"
Step 2: Install the MySQL Community Repository
The mysql-connector-odbc package is not available in the default
Oracle Linux repositories; it is published in MySQL's own yum repository.
Import the MySQL GPG key and install the repository package for EL8:
[root@oracle-vm ~]# rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
[root@oracle-vm ~]# dnf install -y https://dev.mysql.com/get/mysql84-community-release-el8-1.noarch.rpm
Installing : mysql84-community-release-el8-1.noarch
Running scriptlet: mysql84-community-release-el8-1.noarch
Warning: native mysql package from platform vendor seems to be enabled.
Please consider to disable this before installing packages from repo.mysql.com.
Run: yum module -y disable mysql
Installed:
mysql84-community-release-el8-1.noarch
Complete!
mysql module only matters when installing
MySQL Server from this repository. It can be ignored when installing
only the ODBC connector.
Step 3: Install MySQL Connector/ODBC
[root@oracle-vm ~]# dnf install -y mysql-connector-odbc
Installing:
mysql-connector-odbc x86_64 26.10.0-1.el8 mysql-connectors-community 12 M
Installing dependencies:
unixODBC x86_64 2.3.7-2.el8_10 ol8_appstream 453 k
Installed:
mysql-connector-odbc-26.10.0-1.el8.x86_64 unixODBC-2.3.7-2.el8_10.x86_64
Complete!
The unixODBC driver manager is installed automatically as a
dependency, and the driver is registered in /etc/odbcinst.ini.
Verify the registered drivers and libraries:
[root@oracle-vm ~]# odbcinst -q -d
[root@oracle-vm ~]# ls /usr/lib64/libmyodbc*
[PostgreSQL]
[MySQL]
[FreeTDS]
[MariaDB]
[MySQL ODBC 26.10 Unicode Driver]
[MySQL ODBC 26.10 ANSI Driver]
/usr/lib64/libmyodbc26a.so /usr/lib64/libmyodbc26w.so
libmyodbc26a.so is the ANSI driver and
libmyodbc26w.so is the Unicode driver. The
ANSI driver is the more reliable choice with DG4ODBC.
[MySQL] entry is a stock unixODBC template and may
point to a library that does not exist. Use the versioned
MySQL ODBC xx.xx ANSI Driver entry.
Step 4: Create the ODBC Data Source (DSN)
Add a DSN to /etc/odbc.ini as root. The password is deliberately
not stored here; the database link supplies the credentials.
[root@oracle-vm ~]# cat >> /etc/odbc.ini <<'EOF'
[MYSQLDSN]
Description = MySQL salesdb for Oracle DG4ODBC
Driver = MySQL ODBC 26.10 ANSI Driver
SERVER = 192.168.56.110
PORT = 3306
DATABASE = salesdb
CHARSET = utf8mb4
EOF
[root@oracle-vm ~]# chmod 644 /etc/odbc.ini
grid, not oracle.
Step 5: Test ODBC Connectivity with isql
Test the DSN on its own before involving Oracle. If this step fails, the database link will fail too.
[oracle@oracle-vm ~]$ isql -v MYSQLDSN app_reader '<password>'
+---------------------------------------+
| Connected! |
| |
| sql-statement |
| help [tablename] |
| quit |
| |
+---------------------------------------+
SQL> show tables;
SQL> quit
], = or $. Use quit to leave
isql.
Step 6: Create the Gateway Init File
As the oracle user, create
init<SID>.ora in $ORACLE_HOME/hs/admin. The file
name must match the SID used in the listener.
[oracle@oracle-vm ~]$ cat > $ORACLE_HOME/hs/admin/initMYSQLDSN.ora <<'EOF'
HS_FDS_CONNECT_INFO = MYSQLDSN
HS_FDS_SHAREABLE_NAME = /usr/lib64/libodbc.so.2
HS_FDS_TRACE_LEVEL = OFF
HS_LANGUAGE = AMERICAN_AMERICA.AL32UTF8
HS_NLS_NCHAR = UCS2
HS_FDS_SUPPORT_STATISTICS = FALSE
set ODBCINI = /etc/odbc.ini
EOF
[oracle@oracle-vm ~]$ chmod 644 $ORACLE_HOME/hs/admin/initMYSQLDSN.ora
| Parameter | Purpose |
|---|---|
HS_FDS_CONNECT_INFO |
The ODBC DSN name from /etc/odbc.ini. |
HS_FDS_SHAREABLE_NAME |
The unixODBC driver manager library (not the MySQL driver itself). |
HS_LANGUAGE |
Character set used between the gateway and the ODBC driver. |
HS_NLS_NCHAR |
National character set used for Unicode data. |
HS_FDS_SUPPORT_STATISTICS |
Disables remote statistics collection, which avoids errors with MySQL. |
set ODBCINI |
Points the gateway to the ODBC configuration file. |
/usr/lib64/libodbc.so.2. The base unixODBC
package does not provide the plain libodbc.so symlink; that link
only comes with unixODBC-devel.
Step 7: Add a Static SID Entry to the Listener
The gateway is started by the listener, so the listener needs a static
SID_DESC entry with PROGRAM=dg4odbc. First identify
which home the running listener belongs to:
[grid@oracle-vm ~]$ ps -ef | grep tnslsnr
grid 28621 1 0 Aug26 ? 07:47:58 /u01/app/19.0.0.0/grid/bin/tnslsnr LISTENER -no_crs_notify -inherit
In this environment, the listener runs from the Grid home as
the grid user. The entry therefore goes into the Grid home's
listener.ora, but its ORACLE_HOME must point to the
Database home, because that is where dg4odbc and
hs/admin live.
[grid@oracle-vm ~]$ cp /u01/app/19.0.0.0/grid/network/admin/listener.ora \
/u01/app/19.0.0.0/grid/network/admin/listener.ora.bak_$(date +%Y%m%d)
[grid@oracle-vm ~]$ vi /u01/app/19.0.0.0/grid/network/admin/listener.ora
Append the following block:
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(SID_NAME = MYSQLDSN)
(ORACLE_HOME = /u01/app/oracle/product/19.0.0.0/dbhome_1)
(PROGRAM = dg4odbc)
(ENVS = "LD_LIBRARY_PATH=/usr/lib64:/u01/app/oracle/product/19.0.0.0/dbhome_1/lib,ODBCINI=/etc/odbc.ini")
)
)
SID_LIST_LISTENER already exists, add only the new
SID_DESC inside it. Write the Oracle home path literally; do not use
$ORACLE_HOME inside listener.ora. Without Grid
Infrastructure, add the same block to the DB home's listener.ora
as the oracle user.
Step 8: Reload the Listener
A reload rereads listener.ora without stopping the
listener, so existing database connections are not affected.
[grid@oracle-vm ~]$ lsnrctl reload LISTENER
[grid@oracle-vm ~]$ lsnrctl status LISTENER | grep -A1 MYSQLDSN
Service "MYSQLDSN" has 1 instance(s).
Instance "MYSQLDSN", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully
UNKNOWN is normal for a static entry. The listener does not
check the gateway until a connection request arrives.
Step 9: Add the TNS Entry
As the oracle user, add an alias to the DB home's
tnsnames.ora. The (HS = OK) clause tells Oracle this
is a Heterogeneous Services connection.
[oracle@oracle-vm ~]$ vi $ORACLE_HOME/network/admin/tnsnames.ora
MYSQLDSN =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = oracle-vm)(PORT = 1521))
(CONNECT_DATA = (SID = MYSQLDSN))
(HS = OK)
)
[oracle@oracle-vm ~]$ tnsping MYSQLDSN
SID, not SERVICE_NAME, in
CONNECT_DATA. The gateway registers only through the static SID
entry.
Step 10: Create the Database Link
Connect to the PDB as the schema that will own the link and create it.
[oracle@oracle-vm ~]$ sqlplus APPUSER@PRODPDB
SQL> CREATE DATABASE LINK MYSQL_LINK
CONNECT TO "app_reader" IDENTIFIED BY "<password>"
USING 'MYSQLDSN';
Database link created.
Step 11: Test the Database Link
SQL> SELECT SYSDATE FROM DUAL@MYSQL_LINK;
SYSDATE
---------
28-OCT-26
Now query an actual MySQL table. MySQL object names must be written in lowercase inside double quotes:
SQL> SELECT COUNT(*) FROM "customers"@MYSQL_LINK;
SQL> SELECT "customer_id", "customer_name"
FROM "customers"@MYSQL_LINK
WHERE ROWNUM <= 10;
SELECT * FROM customers@MYSQL_LINK fails because Oracle uppercases
unquoted names to CUSTOMERS, which does not exist in MySQL on
Linux, where table names are case-sensitive.
Step 12: Verify the Link in the Data Dictionary
SQL> ALTER SESSION SET CONTAINER = PRODPDB;
SQL> SET LINES 200 PAGES 100
SQL> COLUMN owner FOR a15
SQL> COLUMN db_link FOR a40
SQL> COLUMN username FOR a15
SQL> COLUMN host FOR a12
SQL> SELECT owner, db_link, username, host, created, valid
FROM dba_db_links
WHERE host = 'MYSQLDSN';
OWNER DB_LINK USERNAME HOST CREATED VALID
--------------- ---------------------------------------- --------------- ------------ --------- -----
APPUSER MYSQL_LINK app_reader MYSQLDSN 28-OCT-26 YES
MYSQL_LINK using
standard SQL and the @dblink syntax.