トップ回答者
VisualStudio 2010 C# から、ロックを行う方法がわかりません。

質問
-
フォームに 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 MData2.データを保存 << 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からアクセスされる場合
問題なく動作するのでしょうか?ロックをかけるとすればどうすればよいのでしょうか?
ロックのコマンドが見つけられず、方法がわかりませんでした。
初歩的なことなのでしょうが、よろしくお願いします。
回答
-
SQL Serverはロックを行う際、ロックの対象とロックの目的が必要です。単に「ロックをかけたい」はできません。この辺りを明確にしてください。
次に、ロックの期間ですがトランザクションの終了までです。ところが質問文に挙げられているADO.NETのTableAdapterは非接続型であり、用が済み次第コネクションそのものを終了してしまいます。同時にトランザクションも終了してしまうため、ロックを維持することはできません。
以上を踏まえてC#側でどのような戦略で進めるか、そこから検討する必要があります。C#上での話になりますが
- ロックは使わず非接続型でよく用いられる楽観的排他制御を行う
- 非接続型のまま、SQL Serverとは別のMSDTC(分散トランザクションコーディネーター)でトランザクション(=ロック)を維持させる
- TableAdapterの使用はあきらめ接続型でロックを行う
辺りでしょうか。
とりあえずSQL Serverフォーラムは不適切です。
# モデレーターさんが移動してくれると思います。 -
このような状態で、ロックをかけなくても複数のPCからアクセスされる場合
問題なく動作するのでしょうか?ロックをかけるとすればどうすればよいのでしょうか?
ロックのコマンドが見つけられず、方法がわかりませんでした。
初歩的なことなのでしょうが、よろしくお願いします。何をもって問題無いと言うかですが、
同じNoが振られる事が無いかという話しであれば振られるケースは存在します。個人的には単純な数値のインクリメント列であれば
自動インクリメント列 を用いるのが手っ取り早いと思いますよ。
※複雑であれば採番用の管理テーブルを用いる等。今の造りのままやりたいのであれば、SELECTしてからその値を元にINSERTする形が良いと思います。
※「INSERT INTO MData(No, Code, Data) SELECT MAX(No) + 1, @Code, @Data FROM MData」 -
このような状態で、ロックをかけなくても複数の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/
すべての返信
-
SQL Serverはロックを行う際、ロックの対象とロックの目的が必要です。単に「ロックをかけたい」はできません。この辺りを明確にしてください。
次に、ロックの期間ですがトランザクションの終了までです。ところが質問文に挙げられているADO.NETのTableAdapterは非接続型であり、用が済み次第コネクションそのものを終了してしまいます。同時にトランザクションも終了してしまうため、ロックを維持することはできません。
以上を踏まえてC#側でどのような戦略で進めるか、そこから検討する必要があります。C#上での話になりますが
- ロックは使わず非接続型でよく用いられる楽観的排他制御を行う
- 非接続型のまま、SQL Serverとは別のMSDTC(分散トランザクションコーディネーター)でトランザクション(=ロック)を維持させる
- TableAdapterの使用はあきらめ接続型でロックを行う
辺りでしょうか。
とりあえずSQL Serverフォーラムは不適切です。
# モデレーターさんが移動してくれると思います。 -
このような状態で、ロックをかけなくても複数のPCからアクセスされる場合
問題なく動作するのでしょうか?ロックをかけるとすればどうすればよいのでしょうか?
ロックのコマンドが見つけられず、方法がわかりませんでした。
初歩的なことなのでしょうが、よろしくお願いします。何をもって問題無いと言うかですが、
同じNoが振られる事が無いかという話しであれば振られるケースは存在します。個人的には単純な数値のインクリメント列であれば
自動インクリメント列 を用いるのが手っ取り早いと思いますよ。
※複雑であれば採番用の管理テーブルを用いる等。今の造りのままやりたいのであれば、SELECTしてからその値を元にINSERTする形が良いと思います。
※「INSERT INTO MData(No, Code, Data) SELECT MAX(No) + 1, @Code, @Data FROM MData」 -
このような状態で、ロックをかけなくても複数の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/