[AWS] MariaDBからOracleにCONNECTエンジンでDBリンクを張る方法

AWS EC2上に作成されたAmazon Linux 2023 + MariaDB 10.5の環境から、同じネットワーク内にある他DBMSを参照する(OracleでいうところのDBLINK、SQL Serverでいうところのリンクサーバー)のが思ったより大変だったので備忘録として残しておく。

 

FEDERATEDエンジンを当たるも空振り

まず適当に「MariaDB DBLINK」などとWeb検索すると、こういう記事が見つかった。

mysql/mariadb外部表(DBLINK,FEDERATED)の利用方法 #MySQL8.0 - Qiita

詳しくはよくわからないが、

MariaDB > create table dummyTable (id int ,name varchar(255))
-> ENGINE=FEDERATED
-> default charset=utf8
-> CONNECTION='mysql://xxxx:xxxx@xxxxx/xxxxxx/dummyTable';

という書き方をすればDBLINKに相当することが実現できるらしい・・・?

が、何度やっても上手く行かない。というか「mysql://」スキーマにOracleのIPアドレスを入れたところで動く気がしない。

「ENGINE=FEDERATED」と書かれているとおり、これは「Federatedエンジン」を使用して外部テーブルにアクセスする仕組みのようだ。

 

 

ストレージエンジンの話

MariaDBは様々な方式でデータを持つことができ、そこにアクセスするための仕組みとして「ストレージエンジン」を搭載している。

例えば「MariaDB」と聞いて一般的にイメージする、RDBMSとしてデータを持つ形式は「InnoDB」ストレージエンジンが使われている。

他にもメモリ上にテーブルを保持する「Memory」や水平分散が可能な「Spider」など、様々なエンジンがある。

001.gif

MySQLのストレージエンジンについて #MySQL - Qiita

その中でも「Federated」は、ネットワーク上にある他のMariaDB/MySQLに接続するためのエンジンであり、残念ながらOracleやSQL Server等の他DBMSには対応していないようだ。

 

CONNECTエンジンに舵を切る

どうしたものかとさらに検索してみたところ、「CONNECT」エンジンというものが求めているものらしい。

002.jpeg

CONNECT STORAGE ENGINEを利用した異なるDBMSへの透過的接続 - Database JUNKY

CONNECTは汎用性の高いODBC経由で接続できるため、大抵のDBMSには対応しているとのこと。これだ。

(ちなみに、FEDERATEDはMySQL/MariaDB双方に存在するが、CONNECTはMariaDB独自の模様)

早速インストール手順に従って sudo yum install MariaDB-connect-engine を叩いたが見つからず。

公式のガイドに従ってパッケージリポジトリを追加しようとしたがAmazon Linux 2023が見つからず。

あれー、と思って sudo yum list --available mariadb* と叩いたら、

Available Packages
mariadb-connector-c-devel.x86_64   3.1.13-1.amzn2023.0.3     amazonlinux
mariadb-connector-c-test.x86_64    3.1.13-1.amzn2023.0.3     amazonlinux
mariadb105-connect-engine.x86_64   3:10.5.23-1.amzn2023.0.1  amazonlinux
mariadb105-devel.x86_64            3:10.5.23-1.amzn2023.0.1  amazonlinux
mariadb105-oqgraph-engine.x86_64   3:10.5.23-1.amzn2023.0.1  amazonlinux
mariadb105-pam.x86_64              3:10.5.23-1.amzn2023.0.1  amazonlinux
mariadb105-rocksdb-engine.x86_64   3:10.5.23-1.amzn2023.0.1  amazonlinux
mariadb105-sphinx-engine.x86_64    3:10.5.23-1.amzn2023.0.1  amazonlinux
mariadb105-test.x86_64             3:10.5.23-1.amzn2023.0.1  amazonlinux

名前が違っていたというオチ。無事インストール完了。

そのままMariaDBにCONNECTエンジンを登録する。

mariadb -u username -p
Enter password:
MariaDB [(none)]> INSTALL SONAME 'ha_connect.so';
Query OK, 0 rows affected (0.005 sec)
MariaDB [(none)]> show engines;
+--------------------+---------+-------------------------------------------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                                                         | Transactions | XA   | Savepoints |
+--------------------+---------+-------------------------------------------------------------------------------------------------+--------------+------+------------+
| Aria               | YES     | Crash-safe tables with MyISAM heritage. Used for internal temporary tables and privilege tables | NO           | NO   | NO         |
| MRG_MyISAM         | YES     | Collection of identical MyISAM tables                                                           | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables                                       | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears)                                  | NO           | NO   | NO         |
| MyISAM             | YES     | Non-transactional engine with good performance and small data footprint                         | NO           | NO   | NO         |
| CSV                | YES     | Stores tables as CSV files                                                                      | NO           | NO   | NO         |
| ARCHIVE            | YES     | gzip-compresses tables for a low storage footprint                                              | NO           | NO   | NO         |
| FEDERATED          | YES     | Allows one to access tables on other MariaDB servers, supports transactions and more            | YES          | NO   | YES        |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                                                              | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, foreign keys and encryption for tables                | YES          | YES  | YES        |
| SEQUENCE           | YES     | Generated tables filled with sequential values                                                  | YES          | NO   | YES        |
| CONNECT            | YES     | Management of External Data (SQL/NOSQL/MED), including Rest query results                       | NO           | NO   | NO         |
+--------------------+---------+-------------------------------------------------------------------------------------------------+--------------+------+------------+

↑一番下に「CONNECT」が追加された。ひとまずインストールは完了。

 

Oracleへの接続設定

Oracleへの接続方法を書いてくれているブログが見つかったので、ここを参考に進めていく。

MariaDB CONNECT Storage Engine access to Oracle 11GR2 - Serge Frezefond 's blog

 

Oracle Instant Client(ODBCドライバ)のインストール

OracleのODBCドライバであるOracle Instant Clientをダウンロードする。

上記リンクから「Instant Client for Linux x86-64」をクリックし、

  • Basic Package (ZIP)
  • SDK Package (ZIP)
  • ODBC Package (ZIP)

をwgetしてunzipする。場所は適当に /opt/oracle/instantclient_23_4 とかに。

場所が決まったら .bash_profile に下記2行を追加しておく。

CLIENT_HOME=/opt/oracle/instantclient_23_4 ; export CLIENT_HOME
LD_LIBRARY_PATH=$CLIENT_HOME:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH

 

unixODBCのインストール

次に sudo yum install unixODBC unixODBC-devel を叩く。

インストール後、 odbcinst -j を叩くと各種情報が表示される。

unixODBC 2.3.9
DRIVERS............: /etc/odbcinst.ini ←この2ファイルを編集する
SYSTEM DATA SOURCES: /etc/odbc.ini     ←この2ファイルを編集する
FILE DATA SOURCES..: /etc/ODBCDataSources
USER DATA SOURCES..: /home/ec2-user/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8

 

ODBC設定

まず /etc/odbcinst.ini を開き、末尾に下記を追記する。

[ORACLE_DRIVER]
Description     = Oracle ODBC driver
Driver          = /opt/oracle/instantclient_23_4/libsqora.so.23.1

次に /etc/odbc.ini を開き、Oracleの接続情報を入力する。

[ORACLE_DNS]
Driver       = ORACLE_DRIVER
ServerName   = xxx.xxx.xx.xxx:xxxx/orcl
UserName = orclUser
Password = orclPass

 

接続テスト

isql ORACLE_DNS orclUser orclPass -v コマンドを叩き、OracleにODBC接続できることを確認する。

+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+

SQL> quit

 

CREATE TABLEしてみるも上手く行かない

ODBCの設定も上手く行ったようなので、MariaDB上でCREATE TABLEしてみる。

CREATE TABLE TEST_TABLE
ENGINE=CONNECT,
TABLE_TYPE=ODBC,
TABNAME='ORA.TEST_TABLE'
CONNECTION='Driver=ORACLE_DRIVER;DSN=ORACLE_DNS;UID=OrclUser;PWD=OrclPass'

結果はエラー。

 #HY000 Unsupported table type ODBC 

「TABLE TYPE」で指定できるオプションは公式サイトで紹介されている。勿論「ODBC」も含まれているのに...。

 

TABLE_TYPE=ODBC が認識されない問題で沼る

ODBC接続できると聞いてCONNECTエンジンをインストールしたのに、肝心の「ODBC」を認識してくれないとはこれ如何に。

"Unsupported table type ODBC"で検索してもごく僅かの情報しかない。

こちらのバグチケット(結構古い)によると、CONNECTエンジンの本体であるha_connect.soのコンパイル時にODBCが有効になっていないとダメらしい。

Solution there was that odbc needs to be compiled in (ODBC_SUPPORT precompiler option).

connect.odbc                             w5 [ skipped ]  No ODBC support
connect.odbc_oracle                      w5 [ skipped ]  No ODBC support
spider/bg.direct_update_part             w4 [ pass ]     83
connect.odbc_postgresql                  w5 [ skipped ]  No ODBC support
connect.odbc_sqlite3                     w5 [ skipped ]  No ODBC support
connect.odbc_sqlite3_grant               w5 [ skipped ]  No ODBC support
connect.odbc_xls                         w5 [ skipped ]  No ODBC support

ソースを確認すると、TABLE_TYPEを判別した結果がTAB_NIY(定義なし)の場合に"Unsupported table type %s"が出力されることが分かった。

5475   ttp= GetTypeID(topt->type);
~~~~
5532        // Check table type
5533        if (ttp == TAB_UNDEF) {
5534            topt->type = (src) ? "MYSQL" : (tab) ? "PROXY" : "DOS";
5535            ttp = GetTypeID(topt->type);
5536            sprintf(g->Message, "No table_type. Was set to %s", topt->type);
5537            push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message);
5538        } else if (ttp == TAB_NIY) {
5539            sprintf(g->Message, "Unsupported table type %s", topt->type);
5540            rc = HA_ERR_INTERNAL_ERROR;
5541            goto err;
5542        } // endif ttp

本来なら、5475行目で呼び出しているGetTypeID()関数の中でTAB_ODBCを判定するのだが、

136 #if defined(ODBC_SUPPORT)
137                  : (!stricmp(type, "ODBC"))  ? TAB_ODBC
138 #endif

ODBC_SUPPORTが有効でない場合に、TAB_ODBC判定部分がコメントアウトされてしまいTAB_NIYと判定されるようだ。

CMakeLists.txtを見る限り、cmake時にCONNECT_WITH_ODBCオプションがONになっていればODBC_SUPPORTは有効になるはずだが...

 ./storage/connect/CMakeLists.txt
187 OPTION(CONNECT_WITH_ODBC "Compile CONNECT storage engine with ODBC support" ON)
188
189 IF(CONNECT_WITH_ODBC)
190   if(UNIX)

250     IF(ODBC_OK)
251       INCLUDE_DIRECTORIES(${ODBC_INCLUDE_DIR})
252       add_definitions(-DODBC_SUPPORT)
253       SET(CONNECT_SOURCES ${CONNECT_SOURCES} tabodbc.cpp odbconn.cpp)
254     ELSE()
255       SET(ODBC_LIBRARY "")
256     ENDIF()

何らかの理由でCONNECT_WITH_ODBCオプションがOFFでコンパイルされたものが同梱されているのだろうか。

試しにGitHubからソースを持ってきて自分ビでルドしてみたものの、持ってきたソースのバージョンがよくなかったのか、ha_connect.coとMariaDBのバージョンが合わないと怒られてしまった(後述)。

 

自前でビルドするのは諦める

初心に戻り参考記事のとおり進めることに。

参考記事ではrpmファイルから新しいha_connect.soファイルを抽出していたので、これを真似ることにする。

Amazon Linux 2023はFedoraベースではあるものの、リポジトリはRHELを使うといいとの情報があったので、公式サイトからRHEL用のリポジトリを確認。

rpmファイルをwgetし、rpmファイルから抽出したha_connect.soファイルをMariaDBのpluginフォルダにぶち込む。

wget https://ftp.yz.yamagata-u.ac.jp/pub/dbms/mariadb/yum/10.5/rhel/9Server/x86_64/rpms/MariaDB-connect-engine-10.5.23-1.el9.x86_64.rpm
rpm2cpio ./MariaDB-connect-engine-10.5.23-1.el9.x86_64.rpm | cpio --extract  --make-directories

sudo cp -p /home/temp/usr/lib64/mysql/plugin/ha_connect.so /usr/lib64/mariadb/plugin/
sudo chown root:root /usr/lib64/mariadb/plugin/ha_connect.so

あとはMariaDBにログインしてha_connect.soを再インストールする(pluginフォルダに入れただけでは差し替わらないので注意)。

mysql -u user -p
MariaDB [(none)]> UNINSTALL SONAME 'ha_connect';
Query OK, 0 rows affected (0.002 sec)

MariaDB [(none)]> INSTALL SONAME 'ha_connect.so';
Query OK, 0 rows affected (0.010 sec)

ちなみに、バージョンが合わないときはこんな感じで怒られる。

MariaDB [(none)]> INSTALL SONAME 'ha_connect';
ERROR 1126 (HY000): Can't open shared library 'ha_connect.so' (errno: 8, API version for STORAGE ENGINE plugin CONNECT not supported by this version of the server)

ha_connect.soの差し替えが完了したので再度CREATE TABLEを実行するとエラーメッセージが変わった。

#HY000 SQLDriverConnect: [unixODBC][Driver Manager]Can't open lib '/opt/oracle/instantclient_23_4/libsqora.so.23.1' : file not found

無事ODBCは有効になったようだが、次は何だ。

 

OracleのODBCドライバを認識しない問題

実はこのエラーも参考記事で言及されているのだが、肝心の対応方法が書かれていない。

mysqlのサービスが動いているLinuxユーザー"mysql"にもOracle ODBC driver (libsqora.so.12.1)へのアクセスが必要」とのことだが、Linuxのmysqlユーザーはnologinユーザーであり、.bash_profileなんて無い。

試しに /etc/environment にも下記を書いてみたが効果なし。

CLIENT_HOME=/opt/oracle/instantclient_23_4 ; export CLIENT_HOME
LD_LIBRARY_PATH=$CLIENT_HOME:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH

実はこっちの公式Web記事に答えが書いてあった。

sudo vi /etc/ld.so.conf.d/mariadb.conf

で新たなconfファイルを作成し、oracle instant clientのパスを追加。

/opt/oralce/instantclient_23_4/

そしてldconfigを叩くことで変更内容を反映。無事ライブラリパスを通すことができた。

sudo /sbin/ldconfig

 

ようやくCREATE TABLEが通った

あらためて下記SQLを流すとTEST_TABLEが作成された。

CREATE TABLE TEST_TABLE
ENGINE=CONNECT,
TABLE_TYPE=ODBC,
TABNAME='ORA.TEST_TABLE'
CONNECTION='Driver=ORACLE_DRIVER;DSN=ORACLE_DNS;UID=OrclUser;PWD=OrclPass'

作成されたTEST_TABLEを開くと、OracleにあるORA.TEST_TABLEテーブルの内容が表示された...が、日本語(マルチバイト文字)が「?」に文字化けしている。

 

文字化けを解消する

Oracle側で下記SQLを実行した結果、Oracle側の文字コードはSJIS(JA16SJISTILDE)であることが分かった。

SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET';

MariaDB側の文字コードがlatin1だったのでutf8に変えてみたが効果なし。

export NLS_LANG=JAPANESE_JAPAN.JA16SJISTILDE
export NLS_LANG=JAPANESE_JAPAN.JA16SJISTILDE
export NLS_CHARACTERSET=JA16SJISTILDE
export NLS_CHARACTERSET=Japanese_Japan.JA16SJISTILDE

など叩いてみるも、現ログイン中ユーザーにしか影響がないため意味なし。

何となくMariaDBというよりはunixODBC→Oracle Instant Clinet→Oracle接続時の文字コード指定が間違っている感じ。

CONNECTエンジンというかunixODBCに「Oracle接続時の文字コードはこれだよ」と指定してやらないといけないのだがやり方が分からない。

しばらくネットの海を漂っていると、公式Webに専用の項目があることに気が付く。

sudo systemctl edit mariadb.service

を叩いて

[Service]
Environment=NLS_LANG=JAPANESE_JAPAN.JA16SJISTILDE

を追記して保存する。最後に

sudo systemctl restart mariadb.service

を叩いてMariaDBのサービスを再起動すると日本語が文字化けしなくなった!これで大体は完了。

 

未解決問題あり

最後に、いくつか未解決の問題が残っている。

Oracleの日付型カラムを含むテーブルをCONNECTでCREATE TABLEすると、MariaDBではTimestamp型でテーブルが作成されるようだ。そしてそのTimestamp型を含むテーブルをSELELCTするとMariaDBがクラッシュする。

Oracle側のテーブル定義を見てみると型は TIMESTAMP(6) と定義されている。(6)は秒の小数桁数(デフォルト値)

MariaDB側に作成されたカラムは Timestamp型 で、これは少数桁を持たない。おそらくこのミスマッチが原因だろう。

MariaDBのログを見るとsignal 11ということでバグを踏んでるっぽいのだが判断できず。

[ERROR] mysqld got signal 11 ;
Sorry, we probably made a mistake, and this is a bug.

更に検証してみると、文字列の取得結果に絵文字や「𠮷」などの4バイト文字が入るとクラッシュする。

おそらく特定の型でデータのサイズ長を誤って扱っており、アクセス違反を起こしているのだろう。将来的なバージョンアップで修正されることを期待するしかない。

できる事といえばバリデーションの強化と、Timestamp型を使用しないようCREATE TABLE時に自分で型を指定することぐらいか。

 

CREATE TABLE時に型を指定する方法

例えば以下のようにCREATE TABLEすると、

CREATE TABLE ORA_TEST_TABLE
    ENGINE=CONNECT
    TABLE_TYPE=ODBC
    TABNAME='ORA.TEST_TABLE'
    CONNECTION='DSN=ORACLE_DNS;UID=orclUser;PWD=orclPass';

作成されるテーブルの各カラムの型はCONNECTエンジンの裁量で決められる。

例えばOracle側のORA.TEST_TABLEに日付型の項目があった場合、MariaDB側はTimestamp型で作成されてしまう。

(他の型は大体いい感じに作成されるが、整数値NUMBER(6,0)までもが実直にdecimal(6,0)になるので場合によってはIntegerとかに置き換えてもよさそう)

これを避けるためには、CREATE TABLE時に一つずつ項目名と型を指定する。

CREATE TABLE ORA_TEST_TABLE (
    TEST_ID int NOT NULL,
    TEST_TIME1 datetime,
    TEST_TIME2 datetime,
    TEST_TEXT varchar(32)
)   ENGINE=CONNECT
    TABLE_TYPE=ODBC
    TABNAME='ORA.TEST_TABLE'
    CONNECTION='DSN=ORACLE_DNS;UID=orclUser;PWD=orclPass';

これでTimestamp型を避けてDatetime型でテーブルを作成することができる。

一つずつ項目を書くのは手間なので、まず項目を指定せずCREATE TABLEし、SHOW CREATE TABLE ORA_TEST_TABLE; を実行して自動生成されたCREATE TABLE文を確認、気に入らない箇所を手直ししてからDROP TABLE→再度CREATE TABLEすると効率がいい。

ちなみに↑の方法でtimestamp型ではなくtimestamp(6)で定義してみたがやはりクラッシュしたので、CONNECTエンジンはOracleのTIMESTAMPデータの扱いに難があるのだろう。

というか、OracleのTIMESTAMP型はタイムゾーンを持たないただの日時情報なんだからMariaDB側はdatetime型で十分なはず。なんでtimestamp型にしてしまうんだろう...。

 

日付型の制約

他にも問題というかデータ型の制約がある。CONNECTエンジンはこちらに書かれたデータ型にしか対応しておらず、MariaDB(InnoDBエンジン)が対応しているデータ型よりかなり制限される。

特に厳しいのが日付型。内部的には4バイトの符号付きIntegerで、0=1970/1/1 12:00:00 amを起点に 1901/12/13 20:45:52 ~ 2038/1/18 19:14:07のデータしか保持できない。

CONNECT handles dates from 13 December 1901, 20:45:52 to 18 January 2038, 19:14:07.
CONNECT Data Types - MariaDB Knowledge Base

もしリンク先のテーブルがこれを超える値だった場合、SELECTで取得される値はオーバフロー/アンダーフローした結果となる。

例えば元のデータが 3999/12/31 00:00:00 とすると、このデータをCONNECTエンジンで取得すると 1970/1/1  9:00:00 となる。

これは結構厄介で、気づかないうちに意図しない日付になってしまう可能性がある。

対策としては、リンク先のDBで(ビューを作るなどして)日付を文字列としてVARCHAR型で保持、CONNECT後にDATE_FORMAT関数などを使って日付型に戻す方法が紹介されている

もしくは、リンク先のビューで大きすぎる値は最大値(2038/1/18 19:14:07)に、小さすぎる値は最小値(1901/12/13 20:45:52)に置き換えてSELECTするようにしておけばオーバフロー/アンダーフローしなくなる。

 

その他のTips

CREATE TABLE時、TABNAME='ORA.TEST_TABLE'で指定したテーブルに存在しない項目を指定するとSELECT時にエラーになる。

ORA-00904: "AAAAA": invalid identifier

↑適当に「AAAAA」という項目を指定してCREATE TABLEし、SELECTしてみた結果。

CREATE TABLE時はエラーの有無をチェックせず処理成功してしまうので、テーブル作成後に一度SELECTしてエラーが出ないかどうかの確認は必須。

 

ということで、いくつか問題はあるものの無事接続はできた。

他にも実用上の問題や制約が出てきそうではあるが、一応使えるようになったので一旦はこれで運用してみる。

コメントする