"设置交易隔离级别无法解读"没有采取?还是我看错了路?

ang*_*son 6 sql-server transactions read-uncommitted isolation-level

我们遇到一些显然以错误的隔离级别执行的数据库代码的问题.在代码的这个特定部分中,它应该以"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
Run Code Online (Sandbox Code Playgroud)

这个SQL现在报告了4个池化连接,其中一个是我们的连接,我们可以超越断点来执行我们的SQL,具有以下状态:

53  2   SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
Run Code Online (Sandbox Code Playgroud)

即.会话53具有隔离级别2(READ COMMITTED),并且在此会话上执行的最后一个SQL是"SET TRANSACTION ..."命令.

怎么会这样?

我们使用SQL事件探查器验证了在我们的.NET代码打开它之前这个连接没有生效,所以它没有从连接池中重用.

然而,通过新的连接,并且在其上执行的唯一和第一个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;
Run Code Online (Sandbox Code Playgroud)

连接是正常SqlConnection连接,以正常方式打开.

不幸的是,如果我们编写打开SqlConnection的正常代码,我们就无法重现问题,所以必须有应用程序状态的东西,但是因为SqlProfiler和Sql Server都告诉我们是,所以SQL被执行了,但是没有,我不在乎.

有什么可以影响这个?

完全相同的代码也打开其他连接,即代码执行多次并打开许多连接,因此多个连接最终在池中,但只有第一个连接最终出现此问题.

这是SQL Server 2008 R2,我们也在2012年重现了这个问题.

编辑

好的,还有一些信息.

首先,我们正在启用池化,或者更确切地说,我们没有明确禁用池,也没有将连接字符串弄为"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();
        }
Run Code Online (Sandbox Code Playgroud)

我们之前没有执行任何其他SQL.

请注意,此代码是用许多应用程序的生命周期中的时间,它只是它打开一个最终被错误的第一个连接.

这种联系也会永久生病.由于每次打开连接(即使我们可能将其从连接池中取出),上述状态更改事件也会执行,尝试再次设置隔离级别.这也失败了,但仅限于此单一连接.

此外,自从我发布此问题以来,我们发现了一件影响这一点的事情.

通过更改我在上面发布的连接字符串:

...;Type System Version=SQL Server 2000;...
Run Code Online (Sandbox Code Playgroud)

对此:

...;Type System Version=SQL Server 2008;MultipleActiveResultSets=true;...
Run Code Online (Sandbox Code Playgroud)

然后这个问题就消失了,在前面列出的断点处,连接现在具有"READ UNCOMMITTED"状态.

这是一个红色的鲱鱼,在我们实际执行代码之前,我们的概述中不再报告连接.

我们正在继续调试.

ang*_*son 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()));
    }
}
Run Code Online (Sandbox Code Playgroud)

它会输出:

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
Run Code Online (Sandbox Code Playgroud)

在这里您可以看到,通过 SQL 手动启动和提交事务不会改变隔离级别,但在 .NET 中启动事务而未明确说明隔离级别仍会将其更改为已提交读。

由于我们阅读的任何地方,在没有明确说明隔离级别的情况下启动事务都表示它继承了会话的隔离级别,我想我们不明白.NET 不会这样做。