none
VisualStudio 2010 C# から、ロックを行う方法がわかりません。 RRS feed

  • 質問

  • フォームに BindingSource[bindingSource1]
    を、張り付け、
    [DataMember=MData]
    [DataSource=MYDBDataSet]

    を指定すると、フォームに自動的に
    [mYDBDataSet]
    [mDataTableAdapter]
    が追加される。

    テーブル名:MDATA
    列名:[No], [Code], [Data] 
    で、以下のようなデータが入っています

    [1] 1, 1, 'Test'
    [2] 1, 2, 'Ret'
    [3] 1, 3, 'Ret'
    [4] 2, 1, 'Test'
    [5] 2, 2, 'Ret'
    [6] 2, 3, 'Ret'

    ソリューションの MYDBDataSet.xsd からデザイナーを開いて
    MDataTableAdapter に[クエリの追加]で下記2つのSQLコードを記述します。

    1.現在のNoの最大値を取得 <<FillByMax>>
    [SQLステートメントを使用する][単一の値を返す SELECT(E)]
    SELECT MAX(No)
    FROM MData

    2.データを保存 << InsertQuery >>
    [SQLステートメントを使用する][INSERT(I)]
    INSERT INTO MData(No, Code, Data)
    VALUES(@No, @Code, @Data)

    呼び出し側は

    private void SetData(int num, string Message)
    {
     int Max = (int)mDataTableAdapter.FillByMax();
     mDataTableAdapter.Insert(Max + 1, num, Message);
    }

    このような状態で、ロックをかけなくても複数のPCからアクセスされる場合
    問題なく動作するのでしょうか?

    ロックをかけるとすればどうすればよいのでしょうか?
    ロックのコマンドが見つけられず、方法がわかりませんでした。
    初歩的なことなのでしょうが、よろしくお願いします。

    2013年1月23日 1:14

回答

  • SQL Serverはロックを行う際、ロックの対象とロックの目的が必要です。単に「ロックをかけたい」はできません。この辺りを明確にしてください。

    次に、ロックの期間ですがトランザクションの終了までです。ところが質問文に挙げられているADO.NETのTableAdapterは非接続型であり、用が済み次第コネクションそのものを終了してしまいます。同時にトランザクションも終了してしまうため、ロックを維持することはできません。

    以上を踏まえてC#側でどのような戦略で進めるか、そこから検討する必要があります。C#上での話になりますが

    • ロックは使わず非接続型でよく用いられる楽観的排他制御を行う
    • 非接続型のまま、SQL Serverとは別のMSDTC(分散トランザクションコーディネーター)でトランザクション(=ロック)を維持させる
    • TableAdapterの使用はあきらめ接続型でロックを行う

    辺りでしょうか。

    とりあえずSQL Serverフォーラムは不適切です。
    # モデレーターさんが移動してくれると思います。

    • 回答の候補に設定 佐伯玲 2013年1月25日 2:25
    • 回答としてマーク 佐伯玲 2013年1月29日 2:48
    2013年1月23日 1:44
  • このような状態で、ロックをかけなくても複数のPCからアクセスされる場合
    問題なく動作するのでしょうか?

    ロックをかけるとすればどうすればよいのでしょうか?
    ロックのコマンドが見つけられず、方法がわかりませんでした。
    初歩的なことなのでしょうが、よろしくお願いします。

    何をもって問題無いと言うかですが、
    同じNoが振られる事が無いかという話しであれば振られるケースは存在します。

    個人的には単純な数値のインクリメント列であれば
     自動インクリメント列 を用いるのが手っ取り早いと思いますよ。
    ※複雑であれば採番用の管理テーブルを用いる等。

    今の造りのままやりたいのであれば、SELECTしてからその値を元にINSERTする形が良いと思います。
    ※「INSERT INTO MData(No, Code, Data) SELECT MAX(No) + 1, @Code, @Data FROM MData」

    • 回答の候補に設定 佐伯玲 2013年1月25日 2:25
    • 回答としてマーク 佐伯玲 2013年1月29日 2:48
    2013年1月23日 1:50
  • このような状態で、ロックをかけなくても複数のPCからアクセスされる場合
    問題なく動作するのでしょうか?

    既に回答が出ていますが、ご心配されているとおり、意図した通り動かないことがあり得ます。タイミングによっては同じNoが振られるでしょう。ロックをかけるには、SQL Serverでは、Isolation Levelというのを用います。このレベルによってどのような動作をした時にどのようなロックがかかるかが決まります。大まかに言えば、通常、データを読み書きする動作のパターンは決まっていますので、そのパターンを選ぶことによって、ロックを自働的に行ってくれる仕組みが用意されているということです。
    ロックはトランザクション期間で発生しますから、Isolation Levelもトランザクションに対して指定します。よって、
    int Max = (int)mDataTableAdapter.FillByMax();
    mDataTableAdapter.Insert(Max + 1, num, Message);
    の処理はトランザクションで処理をすれば良いことになります。 .NET Framework 2.0以降をお使いだと思いますから、TransactionScopeを使って以下のように簡単に書けます。

    using (TransactionScope ts = new TransactionScope())
    {
        int Max = (int)mDataTableAdapter.FillByMax();
        mDataTableAdapter.Insert(Max + 1, num, Message);
        ts.Complete();
    }

    TransactionScopeで開始されるトランザクションのデフォルトのIsolation LevelはSerializableであり、一番厳しいトランザクションですから、このトランザクション中は他からのアクセスが制限されますので、目的が達成できると思います(この部分について以下で訂正)。

    なお、SQL Server 2008 以降、かつ、.NET Framework 2.0 SP1以降をお使いの場合は、MSDTCに昇格しないようになりましたから、上記のコードで問題ありませんが、それ以前の環境だとMSDTCに昇格してしまいますので、MSDTCを有効にしていないとエラーが発生します。もっともこの場合はMSDTCを有効にするのではなく、MSDTCに昇格しないようにコードを組むのが一般的だと思います。MSDTCに昇格しないようにするには、トランザクションの間中、一つのコネクションのみを使うようにするだけです。TableAdapterにはConnectionプロパティがありますから、ここが全て同じコネクションであれば良いわけです。

    (訂正)

    「このトランザクション中は他からのアクセスが制限されますので、目的が達成できると思います。」は誤りです。訂正します。serializableでも他から読めてしまいます。

    (追記)

    Isolation Level(分離レベル)では無理っぽいので、テーブルヒントを使わないとダメっぽいですね。以下のようにWITH (XLOCK)を付けて下さい。もちろん、TransactionScopeも使う必要があります。

    SELECT MAX(No) FROM MData WITH (XLOCK) 


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 編集済み trapemiya 2013年1月23日 5:16 訂正および追記
    • 回答の候補に設定 佐伯玲 2013年1月25日 2:25
    • 回答としてマーク 佐伯玲 2013年1月29日 2:48
    2013年1月23日 2:55

すべての返信

  • SQL Serverはロックを行う際、ロックの対象とロックの目的が必要です。単に「ロックをかけたい」はできません。この辺りを明確にしてください。

    次に、ロックの期間ですがトランザクションの終了までです。ところが質問文に挙げられているADO.NETのTableAdapterは非接続型であり、用が済み次第コネクションそのものを終了してしまいます。同時にトランザクションも終了してしまうため、ロックを維持することはできません。

    以上を踏まえてC#側でどのような戦略で進めるか、そこから検討する必要があります。C#上での話になりますが

    • ロックは使わず非接続型でよく用いられる楽観的排他制御を行う
    • 非接続型のまま、SQL Serverとは別のMSDTC(分散トランザクションコーディネーター)でトランザクション(=ロック)を維持させる
    • TableAdapterの使用はあきらめ接続型でロックを行う

    辺りでしょうか。

    とりあえずSQL Serverフォーラムは不適切です。
    # モデレーターさんが移動してくれると思います。

    • 回答の候補に設定 佐伯玲 2013年1月25日 2:25
    • 回答としてマーク 佐伯玲 2013年1月29日 2:48
    2013年1月23日 1:44
  • このような状態で、ロックをかけなくても複数のPCからアクセスされる場合
    問題なく動作するのでしょうか?

    ロックをかけるとすればどうすればよいのでしょうか?
    ロックのコマンドが見つけられず、方法がわかりませんでした。
    初歩的なことなのでしょうが、よろしくお願いします。

    何をもって問題無いと言うかですが、
    同じNoが振られる事が無いかという話しであれば振られるケースは存在します。

    個人的には単純な数値のインクリメント列であれば
     自動インクリメント列 を用いるのが手っ取り早いと思いますよ。
    ※複雑であれば採番用の管理テーブルを用いる等。

    今の造りのままやりたいのであれば、SELECTしてからその値を元にINSERTする形が良いと思います。
    ※「INSERT INTO MData(No, Code, Data) SELECT MAX(No) + 1, @Code, @Data FROM MData」

    • 回答の候補に設定 佐伯玲 2013年1月25日 2:25
    • 回答としてマーク 佐伯玲 2013年1月29日 2:48
    2013年1月23日 1:50
  • このような状態で、ロックをかけなくても複数のPCからアクセスされる場合
    問題なく動作するのでしょうか?

    既に回答が出ていますが、ご心配されているとおり、意図した通り動かないことがあり得ます。タイミングによっては同じNoが振られるでしょう。ロックをかけるには、SQL Serverでは、Isolation Levelというのを用います。このレベルによってどのような動作をした時にどのようなロックがかかるかが決まります。大まかに言えば、通常、データを読み書きする動作のパターンは決まっていますので、そのパターンを選ぶことによって、ロックを自働的に行ってくれる仕組みが用意されているということです。
    ロックはトランザクション期間で発生しますから、Isolation Levelもトランザクションに対して指定します。よって、
    int Max = (int)mDataTableAdapter.FillByMax();
    mDataTableAdapter.Insert(Max + 1, num, Message);
    の処理はトランザクションで処理をすれば良いことになります。 .NET Framework 2.0以降をお使いだと思いますから、TransactionScopeを使って以下のように簡単に書けます。

    using (TransactionScope ts = new TransactionScope())
    {
        int Max = (int)mDataTableAdapter.FillByMax();
        mDataTableAdapter.Insert(Max + 1, num, Message);
        ts.Complete();
    }

    TransactionScopeで開始されるトランザクションのデフォルトのIsolation LevelはSerializableであり、一番厳しいトランザクションですから、このトランザクション中は他からのアクセスが制限されますので、目的が達成できると思います(この部分について以下で訂正)。

    なお、SQL Server 2008 以降、かつ、.NET Framework 2.0 SP1以降をお使いの場合は、MSDTCに昇格しないようになりましたから、上記のコードで問題ありませんが、それ以前の環境だとMSDTCに昇格してしまいますので、MSDTCを有効にしていないとエラーが発生します。もっともこの場合はMSDTCを有効にするのではなく、MSDTCに昇格しないようにコードを組むのが一般的だと思います。MSDTCに昇格しないようにするには、トランザクションの間中、一つのコネクションのみを使うようにするだけです。TableAdapterにはConnectionプロパティがありますから、ここが全て同じコネクションであれば良いわけです。

    (訂正)

    「このトランザクション中は他からのアクセスが制限されますので、目的が達成できると思います。」は誤りです。訂正します。serializableでも他から読めてしまいます。

    (追記)

    Isolation Level(分離レベル)では無理っぽいので、テーブルヒントを使わないとダメっぽいですね。以下のようにWITH (XLOCK)を付けて下さい。もちろん、TransactionScopeも使う必要があります。

    SELECT MAX(No) FROM MData WITH (XLOCK) 


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 編集済み trapemiya 2013年1月23日 5:16 訂正および追記
    • 回答の候補に設定 佐伯玲 2013年1月25日 2:25
    • 回答としてマーク 佐伯玲 2013年1月29日 2:48
    2013年1月23日 2:55
  • こんにちは、ハローこんにちは さん
    フォーラムオペレータの佐伯 玲 です。

    みなさんから情報がたくさん寄せられておりますが内容はご確認いただけましたでしょうか?
    参考になる情報だと思われたので勝手ながら私のほうで「回答としてマーク」とさせて頂きました。

    それぞれご確認いただいた内容やその結果などをご返信いただけましたらと思います。

    宜しくお願いいたします。
    __________________________
    日本マイクロソフト株式会社 フォーラム オペレータ 佐伯 玲

    2013年1月29日 2:47