For the following code...
using System.Transactions;
using Microsoft.Data.SqlClient;
var connectionString = "...";
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot }))
{
TestExec(connectionString);
TestExec(connectionString);
scope.Complete();
}
TestExec(connectionString);
static void TestExec(string connectionString)
{
using var conn = new SqlConnection(connectionString);
conn.Open();
var cmd = new SqlCommand
{
CommandText = @"
DECLARE @useroptions table (Opt varchar(50), Value varchar(50));
INSERT @useroptions EXEC ('dbcc useroptions')
SELECT Value AS [isolation level], CURRENT_TRANSACTION_ID() AS [tran_id], @@SPID FROM @useroptions WHERE Opt = 'isolation level'",
Connection = conn
};
using var reader = cmd.ExecuteReader();
while (reader.Read())
Console.WriteLine($"Isolation Level:{reader.GetString(0)} TransactionId:{reader.GetInt64(1)}, Session:{reader.GetInt16(2)}");
}
When the Connection String is for on premise SQL Server then typical output is below
Isolation Level:snapshot TransactionId:2696760, Session:57
Isolation Level:snapshot TransactionId:2696760, Session:57
Isolation Level:snapshot TransactionId:2696821, Session:57
When the Connection String is for Azure SQL Database then typical output is...
Isolation Level:snapshot TransactionId:4636394, Session:92
Isolation Level:read committed snapshot TransactionId:4636394, Session:92
Isolation Level:read committed snapshot TransactionId:4636446, Session:92
Neither output is really what is hoped for.
In the "On Premise" case the isolation level is not reset when the scope ends and a new transaction starts. This causes many complaints (such as this StackOverflow question).
In the "SQL Database" case the isolation level is reset but happens too early and is not maintained for the whole scope. I came across this behaviour as in my case the first query does a read and the second a write and the code was relying on optimistic concurrency exceptions which never came (as the write query was not running at snapshot as expected).
It looks as though the "on premise" behaviour was temporarily the same as the SQL database behaviour but this was rolled back. This second bug is reported here.
Can anyone shed any light on this matter?
- Is the difference in behaviour due to differing implementations in the server (of
sp_reset_connection
) or the client? - If it is Server Side was this a deliberate design decision to fix the issue on premise as per KB3025845 but leave Azure behaving differently?
- If that was a deliberate design decision is there some fundamental reason why we can't get both cases working as hoped for?
Enlist=false
to the connection string so the behavior difference seems related to distributed transaction support in Azure SQL Database. I suspect Azure SQL Databasesp_reset_connection
behaves differently when the TDS RESETCONNECTIONSKIPTRAN flag is set on the reused pooled connection due to the transaction scope.Console.Write
and three different TransactionIds so it no longer seems to be respecting the scope for either transactions or isolation level!sp_executesql
rather than an ad-hoc batch.