わかりやすい SQLServerのネットワークコンポーネント解説
ちょっと、古い内容になります。クライアントがSQLServerとネットワークを通じて行っているコミュニケーションについて解説します。クライアントがSQLServerにアクセスする際の動作に関する基礎的な情報や、SQLServerとクライアントが通信を行う際に使用しているアプリケーション層のプロトコルTDS(Tabular Data Stream:表計式データストリーム)について解説しています。
クライアントとの通信に必要な作業
オペレーティングシステムとネットワーク
SQLServerがクライアントと通信を行なうためには、まず大前提として、インストールされているコンピュータ同士がネットワークを介して問題なく通信できる状態にある必要があります。そのためには、それぞれのコンピュータのWindowsオペレーティングシステムに、TCP/IPや名前付きパイプ注といったネットワークコンポーネントがインストールされている必要があります。また、ネットワーク上にファイアウォールが存在する場合には、双方が問題なく通信できるように設定しておかなければなりません。
SQLServerコンポーネント
SQLServerはクライアントとデータの通信を行なう際に、独自のフォーマット形式や抽象化アルゴリズムを使用しています。それらを使用するためには、クライアント側にもSQLServer用の接続コンポーネントをインストールする必要があります。また、SQLServerのエディションによっては、デフォルト設定では待ち受けを行なっていないプロトコルがあります。そのため、必要に応じてSQLServer構成マネージャなどを使用して、プロトコルを有効化してください。
SQLServerとクライアントとの通信
SQLServerは、クライアントと通信する際に「SNI(SQLServerNetworkInterface)」と呼ばれる層がTCP/IPや名前付きパイプなどの各プロトコルを抽象化します。そのため、SNIの上位層ではプロトコル間の差異を意識する必要はありません(図1)。なお、SQLServer2000までは「Net-Library」と呼ばれるコンポーネントが同じ処理を担当していました。
図1:SQL Serverネットワークコンポーネント(SQL Server)
図2:SQL Serverネットワークコンポーネント(クライアント)
また、SQL Serverと通信を行うクライアントもSNIを使用するため、先述のとおり、接続コンポーネントをクライアント側にもインストールする必要があります。SQLServer及びクライアントは、送信するデータを「TDS形式」と呼ばれるフォーマットに成型し、SNI層に受け渡します。SNI層では、接続の際に使用されるプロトコルのルールに従って、受け取ったデータをカプセル化します。
図3:TDS
各クライアントはSQLServerと通信を行う際に、1つのプロトコルだけを使用します。しかしながら、SQLServer構成マネージャーを使用して、複数のプロトコルの優先順位を設定して有効化できるようになっていきます。このように設定しておくと、何らかの問題で特定のプロトコルでの接続ができない場合でも、別のプロトコルでの接続を試行します。もしも、2番目に使用したいプロトコルでSQLServerに問題なく接続できた場合は、処理の継続が可能となります。
一方、SQLServerは複数種のプロトコルを待ち受けできます。そのため、あるクライアントがTCP/IPで接続要求を行い。別のクライアントが名前付きパイプで接続してきても問題なく対応できます。
SQLServerがクライアントとの通信で使用できるプロトコルは、次のとおりです。
- TCP/IP
- 名前付きパイプ
- 共有メモリ
- VIA(仮想インターフェースアダプタ)
共有メモリプロトコルに関しては、ローカル接続の場合(SQLServerとクライアントが同一コンピュータの場合)のみ使用されます。
TDS(表形式データストリーム)
TDS(TubularDataStream:表形式データストリーム)とは、SQLServerとクライアントがリクエストや戻り値を受け渡すために使用している、アプリケーションレベルのプロトコルです。いったん、SQLServerとクライアントがいずれかのトランスポート/セッションレベルのプロトコル(TCP/IPや名前付きパイプなど)を使用して接続が確立されると、TDSのルールに従ったメッセージの送受信が開始されます。
TDSはメッセージ送受信の際の、ログインやセキュリティに関する機能も備えています。また、SQLServerから返されるTDSのデータには、戻り値に含まれるローの名前や形式などの情報が含まれています。
クライアントから送信されるメッセージ
ログイン
クライアントがSQLServerとの接続を確立する際に送信するログインのために必要な情報を含むメッセージです。
SQLコマンド
SQLコマンドやSQLコマンドバッチを含むメッセージです。ASCII文字列で記述されたSQLコマンドやSQLコマンドバッチはバッファにコピーされ、SQLServerに送信されます。SQLコマンドバッチは、複数のバッファに分割されることがあります。
バイナリデータを含むSQLコマンド
insertbulkのような、ASCII文字列のSQLコマンドとともにバイナリデータを含むメッセージです。
リモートプロシージャコール
リモートプロシージャコール(RPC)注3実行時に使用されるデータストリームです。RPCの名前、実行オプションやパラメータを含みます。RPCはSQLコマンドと同じメッセージ内で送信されることはありません。
アテンション
クライアントが、すでにSQLServerに対して送信済みのリクエストをキャンセルすることを示すために送信するメッセージです。
トランザクションマネージャリクエスト
クライアントが分散トランザクションに参加する必要がある場合に、MSDTC(分散トランザクションコーディネータ)注4へエンリストを行なうためのメッセージです。
SQLServerから送信されるメッセージ
ログインレスポンス
クライアントからのログイン要求に対するレスポンスメッセージです。ログイン完了もしくはエラーメッセージなどが含まれています。
ロー(行)データ
クライアントが実行したSQLコマンドなどの結果セットです。テーブルのデータやカラム名、データタイプなどの情報によって構成されています。
戻り値ステータス
クライアントからのリクエストとしてRPCが実行された際の結果を含むメッセージです。
リターンパラメータ
UDF(UserDefinedFunction:ユーザー定義関数)RPC、ストアドプロシージのリターンパラメータをSQLServerからクライアントへ受け渡すために使用されるメッセージです。
リクエストの終了
「DONE」データストリームと呼ばれるSQLServerからのメッセージ送信が完了することを示すために使用されるメッセージです。
エラーメッセージと情報メッセージ
SQLServerがクライアントからのリクエストを処理する際に、エラーや何らかの情報をクライアントに送信する必要がある場合に使用されるメッセージです。
アテンション
SQLServerがクライアントから受け取ったアテンションメッセージを認識した時点で、クライアントに送信されるメッセージです。「DONE」データストリームなどが含まれます。
サイベース時代から続くTDSの歴史
TDSを理解するDBMSは、Microsoft SQL Server以外にも存在します。それはサイベース社の「Sybase Adaptive ServerEnterprise」(かつてのSQLServer)です。元来、TDSはサイベースによって制定された仕様に基づいていました。Sybase System10以前のSQLServerやMicrosoft SQL Server6.5は、TDSバージョン4.2という同一フォーマットのプロトコルを使用していました。しかし、サイベースはSybase System 11以降のSQL ServerやAdaptive Server Enterpriseで使用するために、TDSバージョン5.0を開発しました。
また、マイクロソフトも独自の機能を実現するために、Microsoft SQL Server 7.0以降から変更を加え始め、両者の互換性は断たれることになりました。目新しいところではSQL Server 2005用のTDS開発時に、MARS(MultipleActiveRowSets:複数のアクティブな結果セット)を実装するための仕様変更が実施されました。最新バージョンのTDSは、SQL Server 2008に含まれるTDS7.3となります。
SQLServerとクライアントのデータの受け渡し
SQL Serverがクライアントとデータの送受信を行なう際には、「バッファ」と呼ばれる構造体を使用します。バッファに対して一度に行なえる操作は、「書き込む」もしくは「読み込む」のどちらかです。SQL Serverとクライアントは1つ、もしくはそれ以上の数のバッファを使用してメッセージをやり取りします。また、バッファサイズはSQL Serverとクライアントが接続のネゴシエーションを行なう際に変更することもできます。
バッファは、必ずバッファヘッダーとバッファデータの組み合わせで存在します。バッファヘッダーには、各種の管理情報が含まれます。例えば、バッファの受け渡しを行なっているSQL Server内のプロセスのSPID(ServerProcessID)や、バッファデータに含まれるメッセージタイプなどです。バッファデータには、前節で紹介したメッセージのうちのいずれかのデータが含まれています。(図4)
図4:バッファ
デフォルト設定では、バッファサイズ(バケットサイズ)は4096バイトです。SQLServer全体の設定を変更する場合は、SQLServerManagementStudioなどのクエリツールから次のコマンドを実行するか、管理ツールのGUIを使用してサーバーのプロパティの値を変更してください(xxxxにはバイト単位でバッファサイズを指定します)。 また、個々のクライアントの設定を変更する場合は、各アプリケーションの接続文字列などの変更を行なう必要があります。例えば、bulkinsertなどで大量のデータをSQLServerに受け渡す際に、バッファサイズを変更することでパフォーマンスが向上することがあります。しかし、ネットワークトラフィックに与える影響などがあるため、十分に検証してから変更を行なうことをお勧めします。
exec sp_configure network packet size, xxxx
go
reconfigure
go
また、個々のクライアントの設定を変更する場合は、各アプリケーションの接続文字列などの変更を行なう必要があります。例えば、bulkinsertなどで大量のデータをSQLServerに受け渡す際に、バッファサイズを変更することでパフォーマンスが向上することがあります。しかし、ネットワークトラフィックに与える影響などがあるため、十分に検証してから変更を行なうことをお勧めします。
トークン付きデータストリームとトークンなしデータストリーム
TDSで受け渡すメッセージの種類は、トークン付きデータストリームとトークンなしデータストリームに分かれています。先ほどのバッファに続いて、この「トークン」も広範に使われている言葉であるため、その名称から実体を推測することが難しいと思います。TDS用語として使用されているトークンは、バッファデータ内に含まれているデータを、より詳細に識別する必要がある際に付加される情報を意味します。トークンは1バイトで構成され、各ビットの配列でバッファデータの内容を示します。
トークンなしデータストリーム
トークンなしデータストリームは、SQL Serverとクライアントが受け渡すメッセージの内容がシンプルな場合に使用されます。それはバッファデータに含まれるデータの内容について、バッファヘッダーの情報以上に詳細な解説が必要ない場合です。そのような場合、複数のバッファに分割されて送信が行なわれたバッファデータでも、受信後に容易に結合して使用することができます。
図5:トークンなしデータストリーム
具体的には、表1のメッセージの送受信でトークンなしデータストリームが使用されます。
表1:トークンなしデータストリームが使用されるメッセージの送受信
クライアントからのメッセージ | ログイン |
SQLコマンド | |
アテンション | |
トランザクションマネージャリクエスト | |
SQL Serverからのメッセージ | アテンション |
トークン付きデータストリーム
SQL Serverでクエリが実行され、その結果セットをクライアントへ送信するような場合、ローデータTDSメッセージタイプが使用されます。その際のローデータメッセージには、複数のロー(行)、複数のカラム(列)、カラムのデータ型、カラムのデータ長などが含まれています。
これは、複数のバッファに分割されて送信されてきたデータを、クライアントが受け取った後に、クエリの実行結果の形式に復元するために必要となるからです。そのような情報は、各バッファのバッファヘッダー部分で記述するには情報量が多いため、バッファデータ内のデータへトークンという形式で付加されます。そのため、トークンなしデータストリームと比べると複雑なデータの送受信の際に、トークン付きデータストリームは使用されています。
トークン付きデータストリームでは、バッファデータ内がトークンとトークンデータの組み合わせに分割されます。また、データの送受信に複数のバッファが使用される場合、必ずしもトークンデータはトークンと同じバッファに含まれる必要はなく、トークンデータの一部が後続のバッファによって送信されることもあります。(図6)
図6:トークン付きデータストリーム
トークン付きデータストリームが使用されるのは、表2のメッセージの送受信の際です。
クライアントからのメッセージ | バイナリデータを含むSQLコマンド |
リモートプロシージャコール | |
SQL Serverからのメッセージ | ログインレスポンス |
ロー(行)データ | |
戻り値ステータス | |
リターンパラメータ | |
リクエストの終了 | |
エラーメッセージ、情報メッセージ |
クライアントが接続時に使用するプロトコルの管理
通常、クライアントがSQLServerに接続する際には、クライアントアプリケーションの接続文字列などにSQLServer名を指定します。その場合に、次のように「プロトコル名:インスタンス名」の形式で指定を行なうことによって、接続時のプロトコルを明示的に設定できます(以下は、sqlcmd使用時の例です)。
TCP/IPの場合(プロトコル名「tcp」を指定)
sqlcmd -E -Stcp:SQL1\Instancel
名前付きパイプの場合(プロトコル名「np」を指定)
sqlcmd- E -Snp:SQL1\Instancel
共有メモリの場合(プロトコル名「Ipc」を指定)
sqlcmd -E -S1pc:SQL1\Instancel
この方法を使用することによって、クライアントからの接続時に使用されるプロトコルの管理がとても簡単になります。その一方で、次のような考慮点が発生します。すでに紹介したように、SQLServerは複数のプロトコルでクライアントからの接続を待ち受けすることができます。また、接続時にクライアントも複数のプロトコルの中から使用するものを選択できます。
前述の方法でクライアントが接続する際のプロトコルを指定すると、何らかの原因でそのプロトコルを使用した接続が確立できないときは、問題が解決するまでの期間、SQLServerと接続することができません。しかし、接続時にSQLServer名だけを指定していれば、優先順位の高いプロトコルでの接続が何らかの原因で失敗した場合でも、クライアント側で優先順が次に高いプロトコルを使用して接続を再度試みます。そのため、接続ができないリスクを軽減できる可能性があります。(図7)
図7:プロトコルを変更してのリトライ
1,TCP/IPを使用しての接続に失敗
2,名前付きパイプでの再試行で接続成功
前述のように、クライアントが使用する各プロトコルの優先順位の設定や、有効化/無効化はSQLServer構成マネージャで設定できます。
接続先情報のキャッシュ
デフォルトインスタンスへの接続
SQL Serverのデフォルトインスタンスは、明示的に変更しなければ、常にポート番号1433を使用してクライアントからの接続要求を待ち受けています。そのため、クライアントが接続要求を行なう場合に必要な作業はとてもシンプルです。何らかの手段で、接続しようとしているSQL Serverが稼動しているコンピュータをネットワーク上で見つけることができれば、ポート番号1433に対して接続要求を行なうことで、SQL Serverとのコミュニケーションを開始できます。
名前付きインスタンスへの接続
名前付きインスタンスの場合は、もう少し複雑な作業が必要になります。詳細は、次ページで解説しています。
接続情報キャッシュ
名前付きインスタンスへ接続するたびにSQL Browser サービスに必要な情報を問い合わせていると、当然のことながら不要な遅延を発生させるオーバーヘッドとなります。その遅延を回避するために、クライアントは接続情報の履歴をキャッシュする動作を実装しています。キャッシュには次のレジストリが使用されます。
SQLServer2008の場合
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\SNI10.0\LastConnect
SQLServer2005の場合
HKEY_LOCAL_MACHINE\SOFTWARE\MicrosoftMSSQLServerClient\SNI9.0LastConnect
SQLServer2000以前のバージョンの場合
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\SuperSocketNetLib\LastConnect
クライアントがSQLServerとの接続に成功すると、上記のレジストリに次の値を格納します。
- 接続に使用したプロトコル名
- SQLServerのインスタンス名
- SQLServerが待ち受けをしているポート番号
それ以降の接続試行時には、まず最初にローカルコンピュータのレジストリ内にキャッシュした情報を確認します。そのため、サーバーコンピュータで動作していSQL Browserサービスとのやり取りの手間を省くことができます。
ただし、接続先の名前付きインスタンスが再起動され、さらに使用していたポート番号が変わってしまった場合は、キャッシュした情報での接続はできなくなります。そのような場合は、SQLBrowserサービスから再度情報を得て接続を行ない、キャッシュ情報も新たな値を使用して書き換えます。