7

明らかに間違った分離レベルで実行されるいくつかのデータベース コードに問題があります。コードのこの特定の部分では、ロックを最小限に抑えるために「READ UNCOMMITTED」で実行することになっています。この時点では、不整合なデータは問題ありません。

ただし、コードは実際には READ COMMITTED で読み取りますが、その理由はわかりません。

行ったことは次のとおりです。

  1. 接続を開く
  2. この接続で「SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED」を実行します
  3. ブレークポイントにヒット
  4. SQL を実行する

ブレークポイントで、次のコマンドをデータベースに発行します。

select s.session_id, s.transaction_isolation_level, st.text from sys.dm_exec_sessions s
inner join sys.sysprocesses sp on (sp.spid = s.session_id) 
CROSS APPLY sys.dm_exec_sql_text(sp.sql_handle) st

この SQL は現在 4 つのプールされた接続を報告します。そのうちの 1 つは、ブレークポイントを超えて SQL を実行できる接続で、次の状態です。

53  2   SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

すなわち。セッション 53 には分離レベル 2 (READ COMMITTED) があり、このセッションで最後に実行された SQL はその「SET TRANSACTION ...」コマンドでした。

どうすればいいの?

.NET コードが開く前にこの接続が有効でなかったことを SQL プロファイラーで確認したため、接続プールから再利用されませんでした。

しかし、接続が新しくなり、その接続で実行された最初の唯一の SQL が明示的に READ UNCOMMITTED を使用するように指示した場合、接続が依然として READ COMMITTED である可能性はありますか?

ここで何を見るべきですか?

接続文字列 (編集されたビットを含む) は次のようになります。

SERVER=hostname;DATABASE=dbname;Integrated Security=false;USER ID=sa;PASSWORD=****;Application Name=appname;Type System Version=SQL Server 2000;Workstation ID=hostname;

接続は通常のSqlConnection接続であり、通常の方法で開かれます。

残念ながら、SqlConnection を開く通常のコードを記述しても問題を再現できないため、アプリケーションの状態に何らかの問題があるはずですが、SqlProfiler と Sql Server の両方が「はい、SQL は実行されましたが、いいえ、私は気にしないでください。

これには何が影響しますか?

まったく同じコードが他の接続も開きます。つまり、コードが何度も実行され、多くの接続が開かれるため、複数の接続がプールに入れられますが、この問題が発生するのは最初の接続だけです。

これは SQL Server 2008 R2 であり、2012 でもこの問題を再現しました。

編集

OK、もう少し情報。

まず、プーリングを有効にしています。つまり、明示的に無効にしておらず、接続文字列をいじって「N」個のプールを作成していません。

ただし、この接続は、この特定の接続文字列で最初に開かれるため、プールから取得されません。また、永久に「病気」であるという以下の私のメモも参照してください。

この接続は次のように設定されています。

var conn = new SqlConnection(...);
conn.StateChance += connection_StateChange;

private void connection_StateChange(Object sender, StateChangeEventArgs e)
{
    if (e.CurrentState == ConnectionState.Open)
    {
        using (IDbCommand cmd = ((SqlConnection)sender).CreateCommand())
        {
            cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
            cmd.ExecuteNonQuery();
        }

これより前に他の SQL を実行していません。

このコードは、アプリケーションの存続期間中に度も使用されることに注意してください。最終的に間違っているのは、最初に開いた接続のみです。

この接続も永久に病気になります。接続を開くたびに (接続プールから取得する場合でも)、上記の状態変更イベントが実行され、分離レベルを再度設定しようとします。これも失敗しますが、この単一の接続のみです。

さらに、この質問を投稿してから、これに影響を与える 1 つのことがわかりました。

上に投稿した接続文字列を変更することにより:

...;Type System Version=SQL Server 2000;...

これに:

...;Type System Version=SQL Server 2008;MultipleActiveResultSets=true;...

その後、この問題はなくなります。前述のブレークポイントで、接続は「READ UNCOMMITTED」状態になります。

これはおせっかいでした。実際にコードを実行するまで、概要で接続が報告されませんでした。

デバッグを続けています。

4

1 に答える 1

5

ここでの問題は、パラメーターをとらないSqlConnection.BeginTransactionがデフォルトで読み取りコミットされることです。そのページの「デフォルトの分離レベル」テキストが何であるかを理解していなかったと思います。

そのページには次のテキストがあります。

分離レベルを指定しない場合、デフォルトの分離レベルが使用されます。BeginTransaction メソッドで分離レベルを指定するには、iso パラメーターを受け取るオーバーロード (BeginTransaction) を使用します。トランザクションに設定された分離レベルは、トランザクションが完了した後、接続が閉じられるか破棄されるまで持続します。スナップショット分離レベルが有効になっていないデータベースで分離レベルをスナップショットに設定しても、例外はスローされません。トランザクションは、デフォルトの分離レベルを使用して完了します。

(私のハイライト)

以下は、次のことを示す LINQPad スクリプトです。

void Main()
{
    using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated security=true"))
    {
        conn.Open();
        Dump(conn, "after open");

        using (var cmd = new SqlCommand())
        {
            cmd.Connection = conn;
            cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
            cmd.ExecuteNonQuery();
        }

        Dump(conn, "after set iso");

        using (var cmd = new SqlCommand())
        {
            cmd.Connection = conn;
            cmd.CommandText = "BEGIN TRANSACTION";
            cmd.ExecuteNonQuery();
        }

        Dump(conn, "after sql-based begin transaction");

        using (var cmd = new SqlCommand())
        {
            cmd.Connection = conn;
            cmd.CommandText = "COMMIT";
            cmd.ExecuteNonQuery();
        }

        Dump(conn, "after sql-based commit");

        var trans = conn.BeginTransaction();

        Dump(conn, "after .net begin transaction", trans);

        trans.Commit();

        Dump(conn, "after .net commit");
    }
}

public static void Dump(SqlConnection connection, string title, SqlTransaction transaction = null)
{
    using (var cmd = new SqlCommand())
    {
        cmd.Connection = connection;
        if (transaction != null)
            cmd.Transaction = transaction;
        cmd.CommandText = "SELECT transaction_isolation_level FROM sys.dm_exec_sessions WHERE session_id = @@SPID";
        Debug.WriteLine(title + "=" + Convert.ToInt32(cmd.ExecuteScalar()));
    }
}

次のように出力されます。

after open=2
after set iso=1
after sql-based begin transaction=1
after sql-based commit=1
after .net begin transaction=2
after .net commit=2

ここで、SQL を使用してトランザクションを手動で開始およびコミットしても分離レベルは変更されませんが、分離レベルを明示的に指定せずに .NET でトランザクションを開始すると、コミットされた読み取りに変更されることがわかります。

私たちが読んだどこでも、分離レベルを明示的に指定せずにトランザクションを開始すると、セッションの分離レベルを継承すると述べたので、.NET が同じことをしないことを理解していなかったと思います。

于 2013-06-10T11:59:08.473 に答える