Rails のデータベースにMS SQL Serverのリンクサーバーを使うためのメモ

そんなケースはほとんどないかもしれませんが。。

先日実際にあったのでそのときのメモです。

Ruby on Rails (v2.2) による社内システムをお客様の既存システムと連携をするために、次の条件で実装しました。

  • データベースエンジンにMS SQL Server 2000を使う
  • 既存システムのデータベースとの連携をリアルタイムに行う

既存システムのデータベースへの参照、更新はリアルタイムに行う必要があったため、リンクサーバーをviewで参照することにしました。

RailsアプリケーションはWindowsサーバー上に設置することにしました。

1. まずはSQLサーバーに接続

SQLサーバーをエンジンとして使うにはSQLサーバー用のアダプタが必要なので、インストールします。

Connect To MicrosoftSQLServer From Rails On Linux Boxを参考にしてインストール

> gem install rails-sqlserver-2000-2005-adapter -s http://gems.github.com

Ruby/DBIにふくまれるADO.rb を C:\ruby\lib\ruby\site_ruby\1.8\DBD\ADO にコピーします。

database.yml の設定はいつも通りです。 adapter をMSSQL用に変更

さらに、Rails上では文字コードはUTF-8の方が何かと安心なので、environment.rb に次のように指定します。

require 'win32ole'
WIN32OLE.codepage = WIN32OLE::CP_UTF8

2. 既存システムの複合キーを解釈する

既存システムのidは当然ですが、Railsの規約通りではありませんでした。 さらに、メインのテーブルに複合キーがあり、複数カラムでユニークキーとなっていました。

とはいえ、Rails のHABTMを使いたいので、Composite Primary Keys プラグインを利用しました。

> gem install composite_primary_keys

environment.rb に次の行を追加します。

require 'composite_primary_keys'

model では次のようにして複合キーを指定しました。

set_primary_keys :col1, :col2
has_one :status, :foreign_key => ['col1', 'col2']

3. リンクサーバーに接続

これで設定はOKかなと思ったんですが、実際に動かしてみると2つの問題がありました。

1つ目、更新できない。。

致命的です。更新しようとするとエラーが発生してしまいました。 調べてみると、分散クエリと分散トランザクションに説明がありました。

長い、、、ですが、問題になっているのはここ。

上記の規則は、入れ子になったトランザクションをサポートしないプロバイダに対する制限事項として、分散トランザクションでの更新操作は、XACT_ABORT が ON の場合のみ行えることを示しています。

リンクサーバーによる接続は「入れ子になったトランザクションをサポートしないプロバイダ」ということ。 RailsではRailsの機能でリクエストごとにトランザクションが発生してしまうので、リンクサーバによる接続時に自動的に発生するローカルトランザクションと入れ子になってしまうようです。

回避する方法として、「SET XACT_ABORT ON」にして、トランザクションが入れ子になることを許容するようにしました。

Railsがトランザクションを開始する前に実行するために、environment.rb に次のコードを入れて、接続直後にSQLを実行するようにしました。

# リンクサーバーに対応するために接続後に XACT_ABORT ON にする
module ActiveRecord
  module ConnectionAdapters
    class SQLServerAdapter < AbstractAdapter
      alias :initialize_original :initialize

      def initialize(connection, logger, connection_options=nil)
        initialize_original(connection, logger, connection_options)
        @connection.execute("SET XACT_ABORT ON")
      end
    end
  end
end

2つ目、リンクサーバーへの接続が大量に発生

もうひとつ、アプリケーションを使っているとリンクサーバーへの接続が大量に発生してしまうということがありました。 実は、原因はわからなかったのですが、この接続がリンク先のリソースをロックする状況が発生していました。

対策として、productionモードでもRailsに接続を保持させないようにすることにしました。

environment.rb に以下を追加

require 'action_controller/dispatcher'
ActionController::Dispatcher.after_dispatch do
  ActiveRecord::Base.clear_reloadable_connections!
end

さて、正直なところ、これが正解かわかりませんが。試行錯誤の末、社内システムは無事に稼動しています。

Comments are closed.