SQL Server 2005 におけるデータベースの同時実行性と行レベルのバージョン管理

このページはアーカイブです。記載されている内容は情報提供のみを目的としており、ページ内のリンクは有効でない可能性がありますが、これらの情報についてマイクロソフトはいかなる責任も負わないものとします。

By Kalen Delaney

トピック

データベースの同時実行性の概要
まとめ

データベースの同時実行性の概要

現在のリレーショナル データベース システムでは、データに対する同時接続を (数千はないとしても) 数百単位は見込んでいます。同じデータへの同時アクセスを、ユーザーや接続に与える影響をできるだけ最小限に抑えて管理する方法は多様にありますが、採用する方法はデータベース システムのアーキテクチャによって決まります。ほとんどのデータベース システムでは、同時アクセスの管理方法を何らかの方法で制御する機能をアプリケーションの開発時に使用できます。そのため、同時実行性とデータの整合性とのバランスを取ることができます。

Microsoft® SQL Server™ 2005 には、行レベルのバージョン管理 (RLV : Row Level Versioning) という新しいテクノロジがあります。RLV を使用すると、新しい方法で同時アクセスを処理できます。SQL Server 2005 機能の多くは RLV を中心に設計されているため、この新しい機能を活用するために他のアプリケーション制御は必要ありません。データベース管理者は、他の機能 (新しい分離レベルなど) については、データベースごとに RLV を明示的に有効にすることが必要です。これにより、以前の SQL Server バージョンが備える動作ロック機能に依存しているアプリケーションとの間に、下位互換性を維持できます。

このホワイト ペーパーでは、SQL Server 2005 で強化された同時実行性を中心に説明します。サーバーサイドについては、RLV テクノロジを利用するすべての SQL Server 機能を説明します。スナップショット分離、複数のアクティブな結果セット (MARS : Multiple Active Result Sets)、オンライン インデックスの再構築などの新機能についても説明します。SQL Server 2005 の RLV は、データベースのトリガをサポートするときにも使用されます。そのため、SQL Server 2000 と 2005 のトリガ動作の違いについても説明します。クライアント側に関する同時実行性の強化内容として、CLR オブジェクトの同時実行性、新しい SQL ネイティブ クライアントからのトランザクション制御、Windows Enterprise Services からのトランザクション制御、キュー コンポーネントからのトランザクション制御、および Service Broker 対応アプリケーションを使用した同時実行性について説明します。

RLV の主な利点とクライアントサイドの機能強化の 1 つに、同等以上のデータ整合性で、より高いデータベースの同時接続性を SQL Server で実現できることが挙げられます。ここでは、新しい機能と既存機能を比較するために、既存の同時実行機能について説明します。

データベースの同時実行性の定義

同時実行性は、同時に共有データにアクセスしたり、同時に共有データを変更したりする複数プロセスの機能として定義できます。互いにブロックすることなく同時に実行できるユーザー プロセス数が多いほど、データベース システムの同時実行性は高くなります。

データの変更プロセスによって、他のプロセスがその変更データを読み取りできない場合、またはデータの読み取りプロセスによって、他のプロセスがそのデータを変更できない場合、同時実行性が低くなります。また、複数プロセスが同じデータを同時に変更しようとすると常にデータの整合性が損なわれる場合も、同時実行性が低くなります。

データベース システムで同時実行性が低くなる状況に対処する方法は、使用している同時実行制御がオプティミスティックかペシミスティックかによって決まる部分があります。ペシミスティック同時実行制御は、読み取り操作に影響が及ぶ可能性のあるデータに対する変更操作の頻度が高いという想定で機能します。言い換えると、システムがペシミスティックであれば、操作の競合を想定することになります。ペシミスティック同時実行制御を使用する場合の既定動作では、ロックを使用して、別のプロセスが使用中のデータに対するアクセスをブロックします。オプティミスティック同時実行制御は、別のプロセスが読み取り中のデータを任意のプロセスが変更する可能性が (あるとしても) 低いという想定で機能します。オプティミスティック同時実行制御を使用する場合の既定動作では、行のバージョン管理を使用して、変更を実行する前にデータの状態を確認できます。

従来の動作

従来、サーバー レベルでの SQL Server の同時実行制御モデルは、ペシミスティックであり、ロックに基づいていました。同時実行制御では、ほとんどのアプリケーションで最善の方法として現在でもロックが使用されていますが、アプリケーションが小規模の場合、ブロックの問題は深刻になることがあります。

最も大きな問題になるのは、ロックによって、書き込みによる読み取りのブロック、または読み取りによる書き込みのブロックが発生する場合です。トランザクションによって行が変更されると、変更されたデータに対して排他ロックがかけられます。SQL Server の既定動作では、書き込んだユーザーがコミットするまで、他のトランザクションで行を読み取ることはできません。一方、SQL Server では " READ UNCOMMITTED 分離レベル" をサポートしています。この分離レベルは、セッションの分離レベルを設定するか、または NOLOCK テーブル ヒントを指定することでアプリケーションから設定できます。NOLOCK のスキャンは、常に慎重に使用する必要があります。トランザクション上、整合性のある結果を返すことが保証されていないためです。

SQL Server 2005 以前は、同時実行ソリューションを実現する場合、書き込みユーザーによって読み取りユーザーがブロックされるのを回避できる反面、データの非整合性というリスクを容認する必要がありました。また、結果が常にコミット済みデータに基づく必要がある場合は、変更がコミットされるまで待つ必要がありました。

行のバージョン管理の概要

アプリケーションで、結果が常にコミット済みデータに基づくことを必須にしている場合でも、2 つの可能性があります。読み取りユーザーが最新のコミット済みデータ値を保持する必要がある場合、書き込みユーザーがトランザクションを完了し、変更をコミットするまで、読み取りユーザーは (ロック状態で) 待つのが適切です。または、コミット済みデータが最新バージョンでない場合でも、単にコミット済みデータ値を保持するだけで十分な場合もあります。この場合、SQL Server から以前にコミットされた行の値 (つまり古いバージョン) を提供されていれば、ユーザーは読み取り操作を実行できます。

SQL Server 2005 では、"スナップショット分離 (SI : Snapshot Isolation)" という新しい分離レベルを導入しました。また、READ COMMITTED スナップショット分離 (RCSI : Read Committed Snapshot Isolation) という、ロックしない READ COMMITTED 分離の方法も新たに導入しました。このような分離レベルに基づく行のバージョン管理では、以前にコミット済みの値をブロックされずに読み取ることができます。そのため、システムの同時実行性が向上します。SQL Server では、このような同時実行性を実現するために、行が更新されたときにその行の古いバージョンを維持する必要があります。場合によっては同じ行の古いバージョンを複数維持する必要があるため、この新しい動作は、"複数バージョンの同時実行制御" または "行レベルのバージョン管理" とも呼ばれます。

行の古いバージョンを複数格納するため、tempdb データベースが使用するディスク容量が増えます。複数バージョンを格納するディスク容量は、適切に監視および管理する必要があります。

バージョン管理は、トランザクションでデータを変更する際に古いバージョンのデータを維持し、それにより、データベースの "スナップショット" (またはデータベースの一部) を古いバージョンから構築できるようにすることで機能します。

テーブルのレコードまたはインデックスが更新されると、新しいレコードには、更新を実行しているトランザクションのトランザクション シーケンス番号が付加されます。古いバージョンのレコードはバージョン ストアに格納され、新しいレコードにはバージョン ストアにある古いレコードへのポインタが含まれます。バージョン ストアの古いレコードには、さらに古いバージョンへのポインタが含まれることもあります。あるレコードの古いバージョンはすべて、リンクされたリストでつながっています。場合によっては、SQL Server で正しいバージョンを取得するときに、複数のポインタをたどることがあります。バージョンのレコードをバージョン ストアに格納する必要があるのは、バージョンのレコードを要求する操作が存在する間だけです。

次の図では、最新バージョンのレコードがトランザクション T3 で生成され、通常のデータ ページに格納されます。古いバージョンのレコードは、トランザクション T2 とトランザクション Tx で生成され、バージョン ストア (tempdb) のページに格納されています。

図 1   レコードのバージョン

**1   ** レコードのバージョン

行レベルのバージョン管理によって、SQL Server でオプティミスティック同時実行モデルを実現できるようになりました。アプリケーションの要件がある場合、または既定のペシミスティック モデルによる同時実行性の低下を回避する場合に有効です。分離レベルに基づく行のバージョン管理に切り替えるには、この新しい同時実行モデルの使用によるトレードオフを慎重に検討する必要があります。バージョン ストアのために増える tempdb の使用率を監視するという管理要件が追加されるほか、バージョン管理を使用すると、古いバージョンに対するメンテナンス処理が行われるため、更新操作のパフォーマンスが低下します。データを読み取るユーザーがいないときでも、更新操作においては、内部的にバージョン管理作業が発生します。行レベルのバージョン管理を使用する読み取りユーザーがいる場合、適切なバージョンのデータを検索するために、リンクのポインタをたどるという作業も発生します。

さらに、スナップショットを分離によるオプティミスティック同時実行モデルでは、(楽観的に) 更新の競合が多数発生するとは想定していません。そのため、同じデータを同時に更新する競合が予想される場合、スナップショット分離レベルを選択しないでください。スナップショット分離は、書き込みユーザーによって読み取りユーザーがブロックされないようにするときに有効ですが、同時の書き込みは依然許可されていません。既定のペシミスティック モデルでは、最初の書き込みユーザーによって他のユーザの書き込みはすべてブロックされます。一方、スナップショット分離レベルを使用した場合は、、トランザクション開始時点のデータの状態が、他のトランザクションからの書き込みによって異なる状態になってしまった場合、そのデータに書き込みを試みるとエラーとなります。アプリケーションは再度トランザクションを実行する必要があります。このような更新の競合は SI でのみ発生し、強化された READ COMMITTED の分離レベルである RCSI では発生しません。さらに、スナップショット分離を使用する際の更新の競合を減らすためのガイドラインがあります。

行レベルのバージョン管理を使用してスナップショット分離をサポートする方法の詳細については、Kimberly Tripp 著のホワイト ペーパー「SQL Server 2005 Beta 2 スナップショット分離」(https://www.microsoft.com/japan/technet/prodtechnol/sql/2005/SQL05B.mspx) を参照してください。

行レベルのバージョン管理を利用する他の SQL Server 2005 機能

行レベルのバージョン管理を SQL Server 2005 に追加した主な理由は、オプティミスティック同時実行性のためにバージョン ストアを維持し、データの書き込みユーザーがすべての読み取りユーザーをブロックする問題を解決するための行バージョン管理に基づく分離レベルをサポートするためでしたが、この新しいデータ管理方法を利用する SQL Server 2005 の機能は他にもあります。たとえば、複数のアクティブな結果セット (MARS) とオンライン インデックスの再構築という 2 つの新機能があります。そして、3 つ目の機能として、既存の機能であるトリガの新しい管理方法があります。ここでは、これらの SQL Server 2005 機能で行レベルのバージョン管理を使う方法について概要を説明します。

トリガと行のバージョン管理

トリガは、SQL Server の初期バージョンから実装されていた機能です。また、SQL Server 2005 以前の製品では、任意の過去データ (バージョン管理されたデータ) を利用できる唯一の機能がトリガでした。トリガ固有の機能の 1 つに、"deleted" テーブルという擬似テーブルにアクセスする機能があります。トリガが DELETE トリガの場合、"deleted" テーブルには、トリガによって発生した操作で削除された行がすべて含まれます。トリガが UPDATE トリガの場合、"deleted" テーブルには、トリガを発生させた更新ステートメントで変更されたすべての行の古いバージョン データ (つまり、更新が発生する前のデータ) が含まれます。以前のバージョンの SQL Server での deleted テーブルの作成は、トランザクション ログをスキャンして、トリガが関連付けられているテーブルを更新した最新トランザクションのすべてのログ レコードを検索することで行っていました。トランザクション ログは読み取りではなく書き込みに最適化されているため、ログ レコードのスキャンには時間のかかる可能性があります。大規模な OLTP システムの場合、最新トランザクションのログ レコードが既にディスクへ書き込まれているために物理的な I/O 操作が発生する可能性があります。このような場合、新しい方法により既存のトリガのパフォーマンスを改善できます。

SQL Server 2005 では、deleted テーブルは、行レベルのバージョン管理を使用して具体化されます。関連するトリガが定義されているテーブルで更新または削除が実行されると、行のバージョン管理に基づく分離レベルが有効にされているかどうかに関係なく、テーブルの変更内容はバージョン管理されます。トリガから "deleted" テーブルにアクセスする必要がある場合、バージョン ストアからデータが取得されます。更新または挿入による新しいデータは、"inserted" テーブルでアクセスできます。SQL Server 2005 のトリガで inserted テーブルをスキャンすると、最新バージョンの行が検索されます。

tempdb はバージョン ストアに使用されるため、SQL Server 2000 でトリガを多用しているアプリケーションでは、SQL Server 2005 にアップグレードすると tempdb へのアクセス量が増えるため注意が必要です。

オンラインインデックスの作成と行レベルのバージョン管理

インデックスの作成と再構築は SQL Server の新機能ではありませんが、SQL Server 2005 では、テーブルやインデックスをオフラインにすることなくインデックスの作成と再構築を実行できるようになりました。以前のバージョンでは、クラスタ化インデックスを構築または再構築する場合、テーブルが排他ロックされるため、対象テーブルにアクセスする事はできませんでした。非クラスタ化インデックスを構築または再構築する場合についても、インデックスが作成されているテーブルには共有ロックがかけられるため、データを読み取ることはできても変更はできませんでした。さらに、非クラスタ化インデックスを再構築している間、インデックス自体はまったく使用できず、インデックスを使用したクエリのパフォーマンスは低下していました。

SQL Server 2005 では、行レベルのバージョン管理により、完全にオンラインでインデックスの構築や再構築ができるようになりました。インデックスを構築している間、SQL Server では既存のテーブルをスキャンし、インデックスが開始されたときのデータのバージョンを確認します。スナップショット分離が有効かどうかにかかわらず、テーブルに加えられた変更はすべてバージョン管理されます。テーブルからデータを読み取る要求は、バージョン管理されたデータにアクセスします。

複数のアクティブな結果セットと行レベルのバージョン管理

複数のアクティブな結果セット (MARS) は、SQL Server 2005 のクライアントサイドの機能です。MARS を実装する場合、サーバーサイドの機能であるバージョン ストアに依存します。そのため、MARS に加えて、行レベルのバージョン管理機能についてもここで説明します。クライアントサイドの同時実行性の考慮事項に関するセクションでは MARS の説明はしていません。

Microsoft SQL Server 2005 では、データベース エンジンにアクセスするアプリケーションにおいて MARS のサポートを拡張しています。SQL Server の以前のバージョンでは、データベース アプリケーションで既定の結果セットを使用した場合、1 つの接続で複数のアクティブなステートメントを維持できませんでした。そのため、1つの接続内では、1 つのバッチによる結果セットをすべて処理またはキャンセルしてから、他のバッチを実行する必要がありました。SQL Server 2005 では、1 つの接続につき複数の要求をインターリーブ実行できる新しい機能を導入しました。具体的には、1 つの接続につきアクティブな結果セットを複数保持できるようになりました。

MARS が有効な接続内で、他のバッチ上で実行されている他のステートメントとインターリーブできるのは、SELECT ステートメントと BULK INSERT ステートメントのみです。DDL とデータ変更ステートメントは実行が完了しないと他のMARS要求の実行に切り替える事が出来ません。

MARS 接続で 2 つのバッチが送信され、一方に SELECT ステートメントが含まれ、もう一方に UPDATE ステートメントが含まれる場合、SELECT が結果セット全体を処理する前に UPDATE の実行を開始できます。ただし、SELECT ステートメントが次に進む前に UPDATE ステートメントの実行を完了する必要があり、UPDATE による変更はすべてバージョン管理されます。どちらのステートメントも同じトランザクションで実行されている場合、SELECT ステートメントが実行された後に UPDATE ステートメントで加えられた変更については、SELECT では確認できません。これは、SELECT で要求したデータ行は古いバージョンであるためです。

同時実行性に関するクライアント側の課題

SQL Server 2005 のストレージ エンジンには同時実行制御の機能があり、トランザクションとロックを管理しています。ただし、ユーザーはクライアント アプリケーション、コンポーネント、およびサービスを経由して SQL Server にアクセスするため、これらのプログラミング コンポーネントは SQL Server の同時実行制御の管理方法に影響を受けます。また、接続設定が同時実行性に及ぼす影響、およびクライアント アプリケーションから効率的にトランザクションを管理する方法について理解することも重要です。

以前のバージョンの SQL Server では、SQL Server から実行されるコマンドは、トランザクションを開始したアプリケーション レイヤにかかわらず、いずれも Transact-SQL コマンドである必要がありました。

SQL Server 2005 の SQL CLR 機能、サーバーサイドのプログラミング インフラストラクチャ (Service Broker 機能など)、およびデータ アクセス プロバイダという新しい各機能によって、同時実行性とトランザクション管理は大幅に強化されました。ただし、このような強化によって複雑さも増しています。

  1. SQL CLR オブジェクトの同時実行制御

    SQL CLR プロシージャ内から実行されるアクションでは、DTC で管理されたトランザクションを開始する必要はありません。SQL Server データの更新、挿入、または削除を行う操作は、標準的な Transact-SQL プロシージャと同じトランザクション原則に従います。

    SQL CLR オブジェクトは、組み込みの Data Access Provider により SqlContext 経由で SQL Server データを使用できます。SQL CLR オブジェクトは、SqlContext オブジェクトの Pipe プロパティを使用して呼び出し元の接続に結果を送信できます。次に例を示します。

    <SQLProcedure()> _
    Public Shared Sub GetServerVersion  ()
    Dim cmdGetVersion as SqlCommand = SqlContext.CreateCommand()
    
    cmdGetVersion.Commandtext = “SELECT @@VERSION”
    cmdGetVersion.CommandType = CommandType.Text
    
    SqlContext.Pipe.Send(cmdGetVersion.ExecuteScalar().ToString())
    End Sub
    

    一見単純なようですが、実際に行われている処理内容を説明します。トレースを設定して、上記のコマンドを実行したときの処理内容をウォッチすると、次のイベントを確認できます。(トレース情報はプロファイラでも確認ができます)

    SQL:BatchStartingEXEC cbo.GetServerVersion
    SQL:StmtStartingEXEC cbo.GetServerVersion
    SP:StartingEXEC cbo.GetServerVersion
    SP:StmtStarting SELECT @@VERSION
    

    これは、以下の Transact-SQL ストアド プロシージャを定義して実行した場合とまったく同じイベントであることに注目してください。

    CREATE PROCEDURE dbo.GetServerVersion
    AS
    SELECT @@VERSION
    GO
    

    次に、AdventureWorks データベースの Production.Product テーブルにあるデータを更新する CLR ストアド プロシージャを作成します。

    <SqlProcedure()> _
    Public Shared Sub UpdateListPriceByProductID(ByVal ProductID As SqlInt32)
    Try
    Dim cmdUpdate As SqlCommand = SqlContext.CreateCommand()
    Dim parProductID As SqlParameter = _
    cmdUpdate.Parameters.Add("@ProductID", SqlDbType.Int)
    
    parProductID.Direction = ParameterDirection.Input
    cmdUpdate.CommandText = "UPDATE Production.Product " _
    + "SET ListPrice = ListPrice * 1.1 " _
    + "WHERE ProductID = @ProductID"
    
    parProductID.Value = ProductID
    
    cmdUpdate.ExecuteNonQuery()
    
    Catch e As Exception
    SqlContext.Pipe.Send(e.Message)
    End Try
    End Sub
    

    ここでも、このストアド プロシージャを実行したときに SQL Server で発生する動作を確認するためにトレースを使用します。以下の表は、サーバーで発生するイベントをまとめたものです。以降の説明に出てくるイベントを参照できるように、イベント番号も示しています。

    1

    SQL:BatchStarting

    
    

    EXECUTE dbo.UpdateListPriceByProductID 444;


     

     

    Management Studio で実行するバッチが、実行を開始します。

    2

    SQL:StmtStarting

    EXECUTE dbo.UpdateListPriceByProductID 444;
    

     

     

    バッチ内部で最初のステートメントが実行を開始します。

    3

    SP:Starting

    EXECUTE dbo.UpdateListPriceByProductID 444;
    

     

     

    このステートメントから dbo.UpdateListPriceByProductID CLR ストアド プロシージャが呼び出され、このストアド プロシージャが実行を開始します。

    4

    SP:StmtStarting

    UPDATE Production.Product
    SET ListPrice = ListPrice * 1.1
    WHERE ProductID = @ProductID
    

     

     

    データベースで何らかのアクションを実行するこのストアド プロシージャに含まれる、最初のステートメントです。

    接続イベントがないことに注意してください。このプロシージャでは、SQLContext オブジェクトを使用して、最新の接続コンテキストへの参照を取得しているためです。

    5

    SQLTransaction

    75691 UPDATE 0 – Begin
    

     

     

    実行されるステートメントはデータ変更操作のため、SQL Server では自動的に暗黙的なトランザクションを開始します。

    6

    SP:Starting

    UPDATE Production.Product
    SET ListPrice = ListPrice * 1.1
    WHERE ProductID = @ProductID
    

     

     

    Production.Product テーブルで定義された uProduct という AFTER UPDATE トリガがあります。このトリガは、ストアド プロシージャと同様に内部的に管理されるため、SP:Starting イベントとなります

    ただし、このトリガの実行は、トランザクション番号 75691 の制御を受けて発生します。このトリガを作成するコードを以下に示します。

    CREATE TRIGGER [Production].[uProduct]
    ON [Production].[Product]
    AFTER UPDATE NOT FOR REPLICATION AS
    BEGIN
    SET NOCOUNT ON;
    UPDATE [Production].[Product]
    SET [Production].[Product].[ModifiedDate]
    = GETDATE()
    FROM inserted
    WHERE inserted.[ProductID] =
    [Production].[Product].[ProductID];
    END;
    

    7

    SP:StmtStarting

    SET NOCOUNT ON;
    

     

     

    トリガ内部で最初のステートメントが開始されます。

    8

    SP:StmtCompleted

    SET NOCOUNT ON;
    

     

     

    トリガ内部で最初のステートメントが完了します。

    9

    SP:StmtStarting

    UPDATE [Production].[Product]
    SET [Production].[Product].[ModifiedDate] = GETDATE()
    FROM inserted
    WHERE inserted.[ProductID] = [Production].[Product].[ProductID];
    

     

     

    トリガ内部で 2 番目のステートメントが開始されます。

    10

    SP:StmtCompleted

    UPDATE [Production].[Product]
    SET [Production].[Product].[ModifiedDate] = GETDATE()
    FROM inserted
    WHERE inserted.[ProductID] = [Production].[Product].[ProductID];
    

     

     

    トリガ内部で 2 番目のステートメントが完了します。

    11

    SP:Completed

    UPDATE Production.Product
    SET ListPrice = ListPrice * 1.1
    WHERE ProductID = @ProductID
    

     

     

    uProduct トリガの実行が完了します。

    12

    SP:StmtCompleted

    UPDATE Production.Product
    SET ListPrice = ListPrice * 1.1
    WHERE ProductID = @ProductID
    

     

     

    手順 4 で開始された元の UPDATE ステートメントの実行が完了します。

    13

    SQLTransaction

    75691 UPDATE 1 – Commit
    

     

     

    トランザクション 75691 は、ステートメントが完了すると自動的にコミットされます。このトランザクションは、このステートメントでデータベースに書き込む必要があったときに、自動的に開始されたためです。

    14

    SP:Completed

    EXECUTE [dbo].[UpdateListPriceByProductID] 444;
    

     

     

    手順 3 で開始されたストアド プロシージャが完了します。

    15

    SQL:StmtCompleted

    EXECUTE [dbo].[UpdateListPriceByProductID] 444;
    

     

     

    手順 2 で開始されたステートメントが完了します。

    16

    SQL:BatchCompleted

    EXECUTE [dbo].[UpdateListPriceByProductID] 444;
    

     

     

    手順 1 で開始されたバッチが完了します。

    トレース イベントでは、CLR コードで実際に実行されている内容はわかりません。CLR ストアド プロシージャによって Transact-SQL トリガの実行を強制しても、トランザクションの観点からは何も問題がありません。
    
    上記のトレース情報からわかるのは、SqlContext オブジェクトを使用している限り、SQL CLR ストアド プロシージャ内部から送信されたコードは、このプロシージャを呼び出す接続と同じ SPID で実行される、ということです。
    
    SQL CLR オブジェクトは、.NET データ プロバイダのいずれかを使用して外部のデータベース システムにアクセスすると、標準の .NET アプリケーションから接続したように動作します。トランザクションの観点からは何も違いはありません。次のセクションでは、ADO.NET 2.0 の同時実行性管理について説明します。
    
    SQL CLR オブジェクトは、インプロセスのデータ プロバイダ経由で、SQL Serverインスタンス に接続するべきです。
    
    1. ADO.NET 2.0 からの同時実行性管理

      ADO.NET は、以前のクライアント データ アクセスの方法論とはまったく異なる、データベース アプリケーションとコンポーネントを作成する 2 種類のプログラミング モデルを提供しました。

      • オプティミスティック同時実行性に基づく非接続モデルは、DataSet 型と DataAdapter 型を中心に構築されました。

      • ペシミスティック同時実行性に基づく接続モデルは、SQLCommand 型と SQLDataReader 型を中心に構築されました。

      非接続モデルでは、アプリケーションは SqlDataAdapter または TableAdapter を使用してデータベースから要求されたデータを読み取ります。データの整合性を保って読み取ることができるように、この操作の間は共有ロックが使用されます。その後、この読み取り操作でかけられたロックをすべて解除して、データベースの接続を解除します。この点で言うと、トランザクション動作はほとんど以前の ADO.NET と同じです。そのため、MSDN で入手できる幅広い情報を参照できます。

      ADO.NET 2.0 では SQL Server .NET データ プロバイダが強化されました。このデータ プロバイダでは SQL Server 2005で使用できる新機能が使用可能です。MARS や新しいスナップショット分離レベルなど、この新しいプロバイダで公開される新機能の一部については、このドキュメントで既に説明しました。

      ADO.NET で強化された点でトランザクション動作が変わる可能性があるのは、コマンドを非同期に実行する機能です。この機能では、バックエンドで SQL Server の新しい MARS 機能を使用します。ただし、ADO.NET に関するこの問題は特殊なものではありません。このドキュメントで前述した動作とまったく同じです。

      .NET Framework Version 2.0 には新しいトランザクション マネージャが 2 つあります。Lightweight Transaction Manager (LTM) と OleTx Transaction Manager です。この 2 つのトランザクション マネージャへのアクセスは、System.Transactions 名前空間によってカプセル化されています。

      System.Transaction の詳細については、Juval Lowy のホワイト ペーパー「Introducing System.Transactions」(https://www.microsoft.com/downloads/details.aspx?FamilyId=AAC3D722-444C-4E27-8B2E-C6157ED16B15&displaylang=en) (英語) を参照してください。System.Transactions を自動的に活用できるように ADO.NET 2.0 が登録済みです。つまり、コードで ADO.NET 2.0 の Enterprise Services を使用すると、必要な場合にバックエンドで LTM または OleTx が使用されます。このように、アプリケーションはトランザクション モデルを宣言型の方法で使用しています。

      この新しい技術を説明するために、同じサンプル アプリケーションを System.Transactions を使用して実行した場合と使用しないで実行した場合を比較します。

      最初の例では、SQLCommand オブジェクト経由で SQL ステートメントを実行していますが、明示的にトランザクションを制御していません。

      Private Sub TestSystemTransactions()
            Dim conAW As New SqlConnection(sConnString)
            Dim sQuery1 As String, count1 As Integer
            Dim cmd1 As SqlCommand = conAW.CreateCommand()
      
            sQuery1 = "UPDATE Production.Product " _
               + "SET ListPrice = ListPrice * 1.1 " _
               + "WHERE ProductNumber LIKE 'EC%'"
      
            cmd1.CommandText = sQuery1
      
               Try
                  conAW.Open()
                  Dim result1 As IAsyncResult = cmd1.BeginExecuteNonQuery()
      
                  While result1.IsCompleted = False
                     Console.WriteLine("Waiting ({0})", count1)
                     ' Wait for 1/10 second, so the counter
                     ' doesn't consume all available resources 
                     ' on the main thread.
                     Threading.Thread.Sleep(100)
                     If result1.IsCompleted = False Then count1 += 1
                  End While
      
                  Console.WriteLine("Command complete. Affected {0} rows.", _
                      cmd1.EndExecuteNonQuery(result1))
      
               Catch ex As SqlException
                  Console.WriteLine("Error ({0}): {1}", ex.Number, ex.Message)
               Catch ex As InvalidOperationException
                  Console.WriteLine("Error: {0}", ex.Message)
               Catch ex As Exception
                  Console.WriteLine("Error: {0}", ex.Message)
               Finally
                  conAW.Close()
                  Console.ReadLine()
               End Try
         End Sub
      

      ここでも、このストアド プロシージャを実行したときに SQL Server で行われる動作を確認するためにトレースを確認します。以下の表は、サーバーで発生するイベントをまとめたものです。以降の説明に出てくるイベントを参照できるように、イベント番号も示しています。

      1

      SQL:BatchStarting

      
      

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1 WHERE ProductNumber LIKE 'EC%'


       

       

      作成した VB コンソール アプリケーションから、cmd1 SqlCommand オブジェクト経由でバッチを実行します。

      2

      SQL:StmtStarting

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      手順 1 と同じです。

      3

      SQLTransaction

      162198 UPDATE 0 – Begin
      

       

       

      実行されるステートメントはデータ変更操作のため、SQL Server では暗黙的なトランザクションを自動的に開始します。

      4

      SP:Starting

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      Production.Product テーブルで定義された uProduct という AFTER UPDATE トリガがあります。このトリガは、ストアド プロシージャと同様に内部的に管理されるため、SP:Starting イベントを確認します。

      ただし、このトリガの実行は、トランザクション番号 162198 の制御を受けて発生します。このドキュメントで前述したのと同じトリガです。

      5

      SP:StmtStarting

      SET NOCOUNT ON;
      

       

       

      トリガ内部で最初のステートメントが開始されます。

      6

      SP:StmtCompleted

      SET NOCOUNT ON;
      

       

       

      トリガ内部で最初のステートメントが完了します。

      7

      SP:StmtStarting

      UPDATE [Production].[Product]
      SET [Production].[Product].[ModifiedDate] = GETDATE()
      FROM inserted
      WHERE inserted.[ProductID] = [Production].[Product].[ProductID];
      

       

       

      トリガ内部で 2 番目のステートメントが開始されます。

      8

      SP:StmtCompleted

      UPDATE [Production].[Product]
      SET [Production].[Product].[ModifiedDate] = GETDATE()
      FROM inserted
      WHERE inserted.[ProductID] = [Production].[Product].[ProductID];
      

       

       

      トリガ内部で 2 番目のステートメントが完了します。

      9

      SP:Completed

      UPDATE Production.Product
      SET ListPrice = ListPrice * 1.1
      WHERE ProductID = @ProductID
      

       

       

      uProduct トリガの実行が完了します。

      10

      SP:StmtCompleted

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      手順 4 で開始された元の UPDATE ステートメントの実行が完了します。

      11

      SQLTransaction

      162198 UPDATE 1 – Commit
      

       

       

      トランザクション 162198 は、ステートメントが完了すると自動的にコミットされます。このトランザクションは、このステートメントでデータベースに書き込む必要があったときに、自動的に開始されたためです。

      12

      SQL:BatchCompleted

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      手順 1 で開始されたバッチが完了します。

      次に示す 2 番目の例では、同じ SQLCommand オブジェクト経由で同じ SQL ステートメントを実行しますが、トランザクションを明示的に制御します。
      
          Private Sub TestSystemTransactions()
                Dim conAW As New SqlConnection(sConnString)
                Dim sQuery1 As String, count1 As Integer
                Dim cmd1 As SqlCommand = conAW.CreateCommand()
          
                sQuery1 = "UPDATE Production.Product " _
                   + "SET ListPrice = ListPrice * 1.1 " _
                   + "WHERE ProductNumber LIKE 'EC%'"
          
                cmd1.CommandText = sQuery1
          
                Dim myTSScope As New TransactionScope
          
                Using myTSScope
                   Try
                      conAW.Open()
                      Dim result1 As IAsyncResult = cmd1.BeginExecuteNonQuery()
          
                      While result1.IsCompleted = False
                         Console.WriteLine("Waiting ({0})", count1)
                         ' Wait for 1/10 second, so the counter
                         ' doesn't consume all available resources 
                         ' on the main thread.
                         Threading.Thread.Sleep(100)
                         If result1.IsCompleted = False Then count1 += 1
                      End While
          
                      myTSScope.Complete()
          
                      Console.WriteLine("Command complete. Affected {0} rows.", _
                          cmd1.EndExecuteNonQuery(result1))
          
                   Catch ex As SqlException
                      Console.WriteLine("Error ({0}): {1}", ex.Number, ex.Message)
                   Catch ex As InvalidOperationException
                      Console.WriteLine("Error: {0}", ex.Message)
                   Catch ex As Exception
                      Console.WriteLine("Error: {0}", ex.Message)
                   Finally
                      conAW.Close()
                      Console.ReadLine()
                   End Try
                End Using
             End Sub
      
        
      
      トレース情報を確認すると、このコードを実行したときの SQL Server の操作シーケンスを確認できます。
      
      <table>
      <colgroup>
      <col style="width: 33%" />
      <col style="width: 33%" />
      <col style="width: 33%" />
      </colgroup>
      <tbody>
      <tr class="odd">
      <td><p>1</p></td>
      <td><p>TM: Begin Tran の開始</p></td>
      <td><pre IsFakePre="true" xmlns="http://www.w3.org/1999/xhtml">
      

      BEGIN TRANSACTION

       

       

      セッションが確立するとすぐに、トランザクション マネージャはトランザクションを作成しようとします。これは、コードで TransactionScope オブジェクトを使用しているためです。

      2

      SQLTransaction

      164587 user_transaction 0 – Begin
      

       

       

      このトランザクションは、前のサンプルでは UPDATE ステートメントでトリガされていましたが、今回はトランザクション マネージャで開始されたトランザクションです。UPDATE ステートメントはまだ実行されていません。実際には、この SQLTransaction は、手順 1 で実行された BEGIN TRANSACTION ステートメントで生成されたものです。

      3

      TM: Begin Tran の完了

      BEGIN TRANSACTION
      

       

       

      この接続で、ユーザー トランザクションは正常に作成されました。また、このトランザクションの XactSequence 番号が取得されました。この場合の XactSequence 番号は 236223201281 で、このトランザクションのトランザクション マネージャで管理されるすべての操作が識別されます。

      4

      SQL:BatchStarting

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      作成した VB コンソール アプリケーションから、cmd1 SqlCommand オブジェクト経由でバッチを実行します。

      5

      SQL:StmtStarting

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      手順 1 と同じです。

      6

      SP:Starting

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      Production.Product テーブルで定義された uProduct という AFTER UPDATE トリガがあります。このトリガは、ストアド プロシージャと同様に内部的に管理されるため、SP:Starting イベントを確認します。

      ただし、このトリガの実行は、XactSequence 番号 236223201281 の制御を受けて発生します。このドキュメントで前述したのと同じトリガです。

      7

      SP:StmtStarting

      SET NOCOUNT ON;
      

       

       

      トリガ内部で最初のステートメントが開始されます。

      8

      SP:StmtCompleted

      SET NOCOUNT ON;
      

       

       

      トリガ内部で最初のステートメントが完了します。

      9

      SP:StmtStarting

      UPDATE [Production].[Product]
      SET [Production].[Product].[ModifiedDate] = GETDATE()
      FROM inserted
      WHERE inserted.[ProductID] = [Production].[Product].[ProductID];
      

       

       

      トリガ内部で 2 番目のステートメントが開始されます。

      10

      SP:StmtCompleted

      UPDATE [Production].[Product]
      SET [Production].[Product].[ModifiedDate] = GETDATE()
      FROM inserted
      WHERE inserted.[ProductID] = [Production].[Product].[ProductID];
      

       

       

      トリガ内部で 2 番目のステートメントが完了します。

      11

      SP:Completed

      UPDATE Production.Product
      SET ListPrice = ListPrice * 1.1
      WHERE ProductID = @ProductID
      

       

       

      uProduct トリガの実行が完了します。

      12

      SP:StmtCompleted

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      手順 4 で開始された元の UPDATE ステートメントの実行が完了します。

      13

      SQL:BatchCompleted

      UPDATE Production.Product SET ListPrice = ListPrice * 1.1
      WHERE ProductNumber LIKE 'EC%'
      

       

       

      手順 4 で開始されたバッチが完了します。

      14

      TM: Commit Tran の開始

      COMMIT TRANSACTION
      

       

       

      myTSScope オブジェクトの Complete メソッドは、トランザクション マネージャに対して、XactSequence 番号 236223201281 で定義されたトランザクション (SQL トランザクション 164587 と識別されます) をコミットするように要求します。

      15

      SQLTransaction

      164587 user_transaction 1 – Commit
      

       

       

      これは、手順 2 でトランザクション マネージャが開始したユーザー トランザクションです。この手順でコミットされます。

      16

      TM: Commit Tran の完了

      COMMIT TRANSACTION
      

       

       

      この接続のユーザー トランザクションは、正常にコミットされました。

      前のサンプルとは異なる方法でトランザクションが作成され、TransactionScope オブジェクトから明示的にトランザクションをコミットするように要求されるまで保持されたことが、トレース情報でよくわかります。このように操作することで、より綿密にトランザクションを制御できます。また、同じトランザクションにより多くの操作を含めることができます。
      
      このケースで重要な点は、System.Transactions は、すべて標準的な SQL トランザクションのトランザクション操作に基づいている、ということです。要求されたデータ ストレージは 1 つのみで、トランザクションの分散は不要だったためです。2 つの異なるサーバー上のデータの更新をアプリケーションが要求した場合は、System.Transactions は代わりに DTC トランザクションを開始します。
      
      1. 新しい SQL ネイティブ クライアントからの同時実行制御とトランザクション制御

        SQL Server 2005 の新しい SQL ネイティブ クライアントは、MDAC に取って代わるように設計されました。SQL OLE DB プロバイダと SQL ODBC ドライバがネイティブのダイナミック リンク ライブラリ (DLL) に結合され、これら 2 つのインターフェイスの機能が拡張されました。ActiveX Data Objects (ADO) と互換性があり、パフォーマンスと機能に優れた ADO ベースのデータベース アプリケーションへの移行パスが用意されています。

        SQL ネイティブ クライアントの主な目的は、ODBC や OLEDB をネイティブで使用している C++ 開発者に対して、SQL Server 2005 固有の機能のサポートを提供することです。C++ の開発者は、ADO や ADO.NET を使用しないこともあります。このような "開発しやすい" プログラミング モデルには、アプリケーション プロセス レイヤが追加されていて、アプリケーションのパフォーマンスが低下するためです。

        たとえば、SQL ネイティブ クライアントは、MARS と非同期操作をネイティブにサポートしており、これらの機能はADO.NET経由でなくても使用する事が出来ます。

        3.1 SQL ネイティブクライアントを使用した SQLOLEDB トランザクション処理の考慮事項

        既定では、SQLOLEDB は自動的なセッション ベースのトランザクション制御を使用しています。このとき、クライアント アプリケーションから手動で制御しなくても、1つの処理は 1 つのトランザクションと見なされます。

        複数の作業単位を同じトランザクションに追加することもできます。SQLOLEDB を使用すると、ITransactionLocal インターフェイス経由で SQL Server トランザクションをより細かく制御できます。このインターフェイスでは、StartTransaction、Commit、および Abort の各メソッドを公開します。

        さらに広範囲にトランザクションを制御するには、SQL Server 2005 の SQLOLEDB を、MS-DTC で管理されている既存の分散トランザクションに参加させます。SQLOLEDB は、ITransactionJoin::JoinTransaction メソッドを使用して、分散トランザクションに参加できます。

        SQLOLEDB プログラミング モデルを使用するクライアント アプリケーションの場合、次のようにさまざまな方法でトランザクション分離レベルを制御できます。

        • SQLOLEDB の既定の自動コミット モードでは、DBPROPSET_SESSION プロパティの DBPROP_SESS_AUTOCOMMITISOLEVELS

        • ローカルで手動コミットしたトランザクションでは、ITransactionLocal::StartTransaction メソッドの isoLevel パラメータ

        • MS DTC で調整した分散トランザクションでは、ITransactionDispenser::BeginTransaction メソッドの isoLevel パラメータ

        大半のデータベース アプリケーションで、適切なトランザクション分離レベルを選択することが重要となります。たとえば、ビジネス要件と両立できる方法でデータ アクセスが実行されるように、分離レベルの制限を厳しくすることがあります。また、別の例では、トランザクション分離レベルを高くすることで同時実行性を低くすることもあります。SQLOLEDB アプリケーションの既定の分離レベルは DBPROPVAL_TI_READCOMMITTED です。これは、SQL Server の既定のトランザクション分離レベルである READ COMMITTED と同等です。

        3.2 SQL ネイティブクライアントを使用した ODBC トランザクション処理の考慮事項

        SQLOLEDB とは対照的に、SQL ネイティブ クライアント経由で ODBC プログラミング モデルを使用するデータベース アプリケーションは、セッションレベルでトランザクションを管理できます。この ODBC モデルは、次の 2 つのトランザクション モデルを公開します。

        • 自動コミット モード   SQL Server のステートメントごとの自動コミット モードをネイティブで使用します。データベース アプリケーションは、セッションが確立された後に作成されたトランザクションについて、コミットまたはロールバックする動作を実行する必要はありません。このようなアクションは、ステートメントごとに SQL Server によって自動的に管理されるためです。

        • 手動コミット モード   SQL Server の IMPLICIT_TRANSACTION 機能に基づいています。このモードでは、接続が確立した後 (または前の SqlEndTran 以降) に実行されたすべての動作を、同じトランザクションの一部と見なします。このトランザクションは、アプリケーションで SqlEndTran メソッドが実行された場合にのみ終了します。アプリケーションでこの作業をコミットせずに接続を閉じると、すべての作業は自動的にロールバックされます。

        ODBC アプリケーションでは、SQLSetConnectAttr メソッドを呼び出して自動コミット モードをオフにすることで、自動コミット モードを手動コミット モードにいつでも切り替えることができます。

        手動モードでトランザクションを終了するには、データベース アプリケーションは SQL_COMMIT オプションまたは SQL_ROLLBACK オプション付きで SqlEndTran メソッドを呼び出す必要があります。

        分散トランザクションの場合、ODBC アプリケーションは、自動コミット モードを使用しないで実行する必要がありますが、SQLEndTran を呼び出す必要はありません。SQLEndTran は MS-DTC で直接管理されるためです。ただし、分散トランザクションに参加する必要があるプロセスの場合、SQLOLEDB モデルよりもやや複雑になります。

        • クライアント アプリケーションは MS-DTC OLE インターフェイスの ITRansactionDispenser::BeginTransaction メソッドを呼び出し、トランザクション コンテキストへの参照を取得します。

        • クライアント アプリケーションは SQLSetConnectAttr メソッドを呼び出し、以前の手順で取得した DTC オブジェクトに SQL_COPT_SS_ENLIST_IN_DTC 属性を設定します。

        • これで、この接続は分散トランザクションに参加しました。

        ODBC プログラミング モデルでは、トランザクション分離レベル Repeatable Read を Serializable として実装することに注意してください。トランザクション分離レベルは、SQL_ATR_TXN_ISOLATION を使用して、SQL_TXN_READ_UNCOMMITTED、SQL_TXN_READ_COMMITTED、SQL_TXN_REPEATABLE_READ、SQL_TXN_SS_SNAPSHOT、または SQL_TXN_SERIALIZABLE のいずれかの値に設定できます。

        ODBC モデルでは MARS をサポートしています。MARS は既定で有効です。アプリケーションは、SQLSetConnectAttr メソッドを呼び出して SQL_COPT_SS_MARS_ENABLED を SQL_MARS_ENABLED_YES または SQL_MARS_ENABLED_NO に設定することで、この動作を制御できます。

      2. COM+ Services の管理コンポーネントからのトランザクション管理

        COM+ はアプリケーションに複数のサービスを提供しますが、そのいくつかは、分散環境でのトランザクションを管理するために設計されています。トランザクション関連のサービスには以下のものがあります。

        • 自動トランザクション処理   宣言型のトランザクション処理機能を適用します。

        • BYOT (Bring Your Own Transaction)   COM+ の分散トランザクション コーディネータ (DTC) にアクセスする BYOT 機能により、トランザクション形式を継承できます。

        • コンペンセート リソース マネージャ (CRM : Compensating Resource Manager)   非トランザクション リソースに、原子性と持続性の各プロパティを適用します。

        • キュー コンポーネント   非同期のメッセージ キュー機能を実現します。

        4.1 自動的なトランザクション処理

        このサービスには、設計時に宣言型の方法でクラスのトランザクション動作を構成する機能があります。また、このクラスから作成されるオブジェクトが、実行時にトランザクションに参加する方法を指示できます。このとき、トランザクションの開始点と終了点を制御するコードを別に追加する必要はありません。

        この技術によって、アプリケーション コードのトランザクション管理が単純になりますが、デメリットもあります。重要な点は、動作を理解しパフォーマンスの低下を招かないようにすることです。

        トランザクション処理で推奨される通常の手順では、セッションは必要になったときにのみ開き、閉じる場合はできるだけ早く閉じます。同様に、トランザクションは必要になったときにのみ開始し、不要になったときはトランザクションを終了することが推奨されます。

        提供されるコンポーネントでの一般的な問題の 1 つは、アプリケーションによりセッションが開かれる (既存のトランザクションとのセッションに参加する) とすぐに、コンポーネントが新しいトランザクションを作成することです。つまり、このセッションで何を実行しようと、それらは 1 つのトランザクションの一部になり、セッションが最初に開かれた以降に実行されたすべてのステートメントが含まれることを意味します。セッション全体にわたるトランザクションの一部にする必要のないステートメントの実行までトランザクションに含まれるため、同時実行性は低下します。結果的に、SQL Server の既定動作である自動コミット モードの方がパフォーマンスが良くなることがあります。

        COM+ トランザクションが SERIALIZABLE トランザクション分離レベルにより既定で作成されることを理解していない開発者が多いのも問題の 1 つです。この場合、このセッションで実行されたステートメントで共有ロックを獲得すると、トランザクションが終了するまでロックは解除されません。COM+ トランザクションのトランザクション分離レベルを変更するのは簡単ですが、分離レベルの変更が必要になる場合もある点に注意してください。さらに、設計されているオブジェクトが分散トランザクションにおいてルート オブジェクトになるかどうかは明確でない場合もあるため、問題は複雑になります。そのため、現在のコンポーネントからトランザクション分離レベルを常に変更できるとは限りません。また、要求されている方法で同時実行性を制御できないこともあります。

        データベース アプリケーションが 1 つの SQL Server インスタンスのデータのみを使用し、COM+ によるサービスは自動トランザクション以外必要でない場合には、このアプリケーションを、COM+ ベースの分散トランザクションではなく、ローカルのトランザクションに基づいて書き直すことをお勧めします。COM+ で開発は単純になりますが、トランザクションのデバッグが難しくなります。また、COM+ では、トランザクションごとにパフォーマンスが低下します (多くの場合、20% ~ 50%)。分散トランザクションが必要でなければ、この問題は回避できます。

        4.2 BYOT (Bring Your Own Transaction)

        BYOT 機能により、トランザクション形式を継承できます。これによって COM+ コンポーネントは、既存の分散トランザクション プロトコル (DTC) またはトランザクション インターネット プロトコル (TIP) の各トランザクションで使用する接続に参加できます。この技術は、複雑な状況では管理が難しくなることもありますが、自動トランザクションよりも綿密に制御できます。

        4.3 コンペンセートリソースマネージャ (CRM)

        COM+ には、非トランザクション オブジェクトを DTC トランザクションに含めることができるコンペンセート リソース マネージャ (CRM : Compensating Resource Manager) があります。CRM は単純化されたリソース マネージャであり、復元ログによって、少なくともトランザクションの原子性 ("オール オア ナッシング" 動作) と持続性は実現されます。

        重要な点は、非トランザクション オブジェクトを DTC トランザクションに追加する意味を理解することです。この実装で複雑さが増し、一般には DTC トランザクションの実行時間が長くなります。さらに、このような非トランザクション オブジェクトにより、考慮が必要になるエラー条件が概して多くなります。結果的に、トランザクションがロールバックされる可能性が高くなり、ユーザーから見て明らかにパフォーマンスが低下します。

        このサービスを使用しない方がよいという意味ではありませんが、サービスを使用する前に必要性を評価することが重要です。このサービスを必要とする明確なビジネス要件があり、他の手段で簡単に解決できない場合、CRM が最善の解決策になることもあります。ただし、アプリケーションを監視して、ユーザーが容認できる方法で特定のビジネス要件を満たす必要があります。CRM の詳細についてはこのドキュメントでは説明しません。CRM サービスの詳細については、https://msdn2.microsoft.com/en-us/library/ms680326.aspx (英語) を参照してください。

        4.4 キューコンポーネント

        キュー コンポーネントは、Microsoft メッセージ キュー (MSMQ : Microsoft Message Queue) 機能をコンポーネントに公開し、非同期のメッセージ キューを提供する COM+ サービスです。これらのメッセージをトランザクションにすることができます。これによって、メッセージは順序どおりに配信され、メッセージが重複して配信されるのを防ぎ、配信先キューから正常に取得できるようになります。

        トランザクション内のメッセージ セットを送信する場合、メッセージをグループ化して 1 つのトランザクションを構成する処理を伴います。キュー技術は、トランザクション内とトランザクション外で異なります。トランザクション内の場合はメッセージ管理と直接関連してくるため、このドキュメントでは扱いません。

        トランザクション外のキューには、データベースなどの外部リソースが関係します。このようなトランザクションは、MS-DTC など、メッセージ キュー システムに含まれない外部のトランザクション コーディネータに依存しています。外部のトランザクション コーディネータには、トランザクション内で使用される場合よりも複雑なプログラミング モデルがあります。

        基本的に、キュー コンポーネントを使用する理由が、SQL Server の 1 インスタンスに関するデータベース アプリケーションのスケーラビリティを拡張するためだけの場合は、Service Broker に基づくアーキテクチャを採用することをお勧めします。

      3. 同時実行性と非同期の Service Broker が有効なアプリケーション

        Service Broker は SQL Server 2005 ストレージ エンジンのコンポーネントであり、キューに基づくメッセージ フレームワークのインフラストラクチャによって、信頼性が高くスケーラブルなデータベース アプリケーションを作成できます。独自仕様のキュー データベース アプリケーションでは、一般にキュー テーブルを多用しています。このような場合、クライアント アプリケーションとキュー サービスは、少数の行の挿入、選択、および削除を頻繁に実行しているため、同時実行性の問題がよく発生します。このような同時実行性の問題が発生する主な理由は、SQL Server のロック マネージャがキュー テーブルと標準的なテーブルを区別せず、アプリケーションがキュー テーブル内のデータにアクセスすると、通常のロック メカニズムをすべて適用することにあります。パフォーマンスの高いカスタムのキュー メカニズムを設計することは困難です。一般に、キュー テーブルへのアクセス回数が高い場合にロック メカニズムを使用すると、キュー テーブルは頻繁にロックされ、デッドロックの発生率も高くなります。

        Service Broker は、カスタムのキュー アプリケーションの代替として機能し、キューとメッセージのインフラストラクチャ全体を管理できます。Service Broker を使用すると、そのフレームワークを使用するアプリケーションの作成に集中することができます。

        Service Broker を使用するアプリケーションは、メッセージに基づいています。このメッセージはメッセージ交換の一部としてキューに送信され、メッセージ交換グループにグループ化されます。アプリケーションは、メッセージをトランザクションの一部としてメッセージ交換に送信します。そのため、各メッセージ交換のメッセージは、Service Broker にすべて登録されるか、またはまったく登録されないかのいずれかの結果になります。

        Service Broker にはアクティブ化という機能があります。アクティブ化を使用すると、キューに到着するメッセージの量に合わせてアプリケーションを動的に拡張および縮小できます。データベース内部のストアドプロシージャ、およびデータベース外部で実行されるプログラムの両方でアクティブ化を利用できます。データベース内部のストアドプロシージャのアクティブ化を利用した場合、Service Broker はキューによって指定されたストアド プロシージャのコピーを開始します。ただし、アクティブ化されるストアドプロシージャの最大インスタンス数はMAX_QUEUE_READERS オプションで制限されます。複数のアクティブ化されたインスタンスによりキューに含まれる保留中のメッセージ交換を処理する事が出来ます。

        Service Broker のトランザクションは、次に示す 2 つの個別の手順で処理されます。

        1. アプリケーションで 1 つのトランザクションを作成して、単一のビジネス トランザクションの一部として送信されるメッセージやメッセージ グループの整合性を保証します。このトランザクションには、このようなメッセージをキューに追加し、すべてのメッセージを論理的にグループ化するために必要な Service Broker の動作がすべて含まれます。このトランザクションをロールバックする必要がある場合でも、メッセージ交換の整合性は保たれます。これは、コミットされたトランザクションのみが Service Broker メッセージ交換の一部になるためです。

        2. Service Broker サービスでは、各メッセージ交換グループをそのトランザクション内で処理する必要があります。メッセージ交換のステータスを反映する対象は、そのメッセージ交換に含まれるメッセージが要求するデータベースの動作だけでなく、インフラストラクチャ テーブルへの変更もすべて含まれます。この処理の詳細については、MSDN の「SQL Server 2005 Service Broker の紹介」を参照してください。

        最初の処理では、同時実行性の問題が発生する可能性はあまりありません。SQL Server で行レベルのロックがかかるためです。各メッセージ交換グループは 1 つの作業単位を表します。1 つのトランザクションで各作業単位がキューに挿入され、1 つのトランザクションとして処理されます。Service Broker は、各メッセージが 1 回だけ処理されることを保証します。そのため、同じデータについて競合する 2 つのプロセスが同時に発生することはほとんどなく、このようなイベントは Service Broker で透過的に管理されます。

        メッセージ交換グループのロックは Service Broker によって自動的に処理されます。ヒントなど、手動で管理するためのメカニズムはありません。特定のメッセージ交換グループのメッセージを同時に処理できるのは、1 つのキュー読み取りサービスのみです。

        次のステートメントで、メッセージ交換グループのロックを獲得します。

        • BEGIN DIALOG CONVERSATION (Transact-SQL)

        • BEGIN CONVERSATION TIMER (Transact-SQL)

        • END CONVERSATION (Transact-SQL)

        • MOVE CONVERSATION (Transact-SQL)

        • RECEIVE (Transact-SQL)

        • SEND (Transact-SQL)

        ただし、どのデータベース アプリケーションでも (特に非接続モデルに基づくアプリケーションの場合は)、データの更新がそのデータの状態に依存しないようにアプリケーションを設計することが重要です。以前に読み取ったデータ値に依存することなく、可能な場合はトランザクション処理中のデータが他のユーザから更新されないように、アプリケーションを設計する必要があります。その理由は、2 つの操作がシリアル化できるトランザクションの一部にならない限り、以前に読み取った値が、更新操作が実行されるときまで同じである保証がないためです。これは Service Broker アプリケーションにも当てはまります。この場合、適切にトランザクションが設計されていないと、あるメッセージが SQL Server データの変更を要求し、別のメッセージで同じデータに異なる変更を要求した場合、更新が失われることがあります。一方の更新は失われる可能性が高くなります。これは Service broker の問題ではなく、アプリケーション設計の問題です。

        ここで取り上げたのは、オプティミスティック同時実行の技術が役立つ一般的なケースです。この技術により、あるアプリケーションが、データの最新状態に基づいて何らかのデータベースの変更を要求する場合、データベースにその変更を適用するまでに、別のプロセスによって元のデータが変更されるという問題を回避できます。オプティミスティック同時実行を実装するために、各メッセージ交換には、データの現在の状態と以前の状態に関して十分な情報を含める必要があります。メッセージを処理するサービスはこの情報を利用して、新しい変更を許可するか、トランザクションをロールバックするかを決定することができます。

        言い換えると、アプリケーションは SQL Server からデータを読み取るときに、そのデータを元の値として保持する必要があります (これは DataSets が自動的に処理します)。アプリケーションがこのデータ値を変更する要求を送信する場合、メッセージに変更後の値と元の値の両方を含めることが必要です。生成される UPDATE ステートメントでは、SET 句で変更後の値が使用され、WHERE 句で元の値が使用されます。このとき、データが最初に読み取ったデータと同じであると、WHERE 句で同じ行を再度検索し、SET 句でその値を更新する方法を決定します。

        ただし、データを読み取った後に別のプロセスでデータが変更された場合は、WHERE 句は既存の行とは一致しないため、この UPDATE 操作では行は変更されません。この場合、SQL Server エラーは発生しませんが、このような Service Broker サービスを実装するストアド プロシージャは、このイベントの通知を送信する必要があります。この通知によって、クライアント アプリケーションは同時実行違反と判断できます。

        処理が成功しなかったメッセージはポイズン メッセージになります。アプリケーションは、ポイズン メッセージを適切に管理できるように設計する必要があります。特に、このポイズン メッセージの理由が同時実行性の問題である場合、そのイベントについてクライアント アプリケーションに通知する必要があります。クライアント アプリケーションは、このような状況を解決しなければなりません。また、オプティミスティック同時実行の環境では、データの消失として扱うことも考えられます。一般的なソリューションとしては、データベースで入手できる元の値をユーザーに提示し、再度更新するか、変更案を破棄するかを確認する方法があります。

      まとめ

      SQL Server 2005 では、新機能の追加によりデータへの同時アクセス機能が改善され、既存機能も変更されました。機能の多くは、SQL Server 2005 の新しい行レベルのバージョン管理 (RLV) テクノロジに基づいて構築されています。一部の機能は、トリガなどの RLV テクノロジに基づいています。アプリケーションの変更は必要なく、開発者の観点からもほとんど気が付きません。2 つのレベルを備えるスナップショット分離などの他の機能は、新しいデータベース オプションを有効にする必要があり、多少のコード変更が必要になることもあります。スナップショット分離を十分に活用するには、同時実行性と整合性のバランスを再評価することが求められます。

      SQL Server 2005 では、クライアントが SQL Server と通信する場合の同時実行性も強化されました。SQL Server で同時実行性を内部的に管理する方法を理解すると、クライアント アプリケーションにおける最適な同時実行制御の値を判断できるようになります。