how to deal with sql server connection pooling leaking transaction isolation levels

When you are using Microsoft SQL Server connection pooling, a recycled connection gets all the session details reset (eg: temp tables are deleted). Internally this is done by the special sproc sp_reset_connection. This answer has a full list of details which are reset – What does sp_reset_connection do . One notable thing which is not on this list is the transaction isolation level. So, for example, if a connection has run a transaction at the read uncommitted level, and that connection gets recycled, the next thread which receives that connection will have a transaction isolation level of read uncommitted, which is different from the default on brand new connections of read committed. This behavior is intended (by Microsoft), as it’s documented in the TDS spec; there’s this sentence in there “Distributed
transactions and isolation levels will not be reset.
“.

This is all background. There are several other SO questions about leaky transaction isolation levels, but none of them cover what I want to know …

  • one – How to prevent leak of transaction isolation level in pooled connections
  • another – SQL Server: Isolation level leaks across pooled connections

Given that Dapper and Entity Framework have a common pattern of opening a connection before running one query and then closing it after, it would seem that those libraries are expecting connection pooling to be in use. To expand, when a SqlConnection object starts out as closed, EF statements, and dapper database methods all will open the connection before executing, and then close it after. If the connection has already been opened, either explicitly by calling Open or as a side effect of starting a transaction, then Dapper and EF use the connection as is (they don’t close it after).

Since this common pattern is used by Microsoft (and others), this suggests (to me) that connection pooling is expected to be used. And the SqlConnection object even has connection pooling enabled by default.

So this leaky (or persistent) transaction isolation level thing would seem to be highly counterintuitive. It’s like a really nasty boobytrap for the uninitiated. If a query needs to be run as read uncommitted (or less troublesome, serialized), and then is innocently returned to the connection pool, the next thread which comes along may get a nasty surprise.

here’s a small sample program demonstrating this nasty surprise:

private static void PoolTest()
{
    for (int poolMode = 1; poolMode <= 2; poolMode += 1)
    {
        string poolTest = poolMode == 1 ? "simple" : "pooled";
        string unique = DateTime.UtcNow.Ticks.ToString();
        var csb = new SqlConnectionStringBuilder()
        {
            DataSource = @"localhost", InitialCatalog = "AdventureWorks",
            IntegratedSecurity = true, WorkstationID = unique
        };
        // set up an antagonist connection to manage the test table, and to insert a record uncommitted
        csb.Pooling = false;
        var setupConn = new SqlConnection(csb.ConnectionString + ";");
        // set up a protagonist connection to run the test
        csb.Pooling = poolMode == 2;
        var testConn = new SqlConnection(csb.ConnectionString);
        // use a "global" temp table so that both connections can see it
        string evilTable = "##eviltable" + unique;

        // open the antagonist connection (and keep it open), create the table, and insert a record, committed
        setupConn.Open();
        setupConn.Execute($"create table {evilTable} (MyKey varchar(10) not null primary key, MyValue varchar(20))");
        setupConn.Execute($"insert into {evilTable} (MyKey, MyValue) values ('key1', 'expected')");
        bool scary = false;

        // all queries are run using dapper.  when dapper encounters a closed connection, it opens the connection,
        // runs the query, and then closes the connection.  this strategy is not unique to dapper, entity
        // framework works the same way.  to see such a thing in action, use dapper's Execute and Query to create a
        // temp table (single-pound) and then try to access it (it won't be there).  you have to explicitly open a
        // connection or start a transaction to prevent this behavior (again, this isn't just dapper; EF does the
        // same thing).  this would seem to open and close a LOT of connections to the database server, which is
        // why connection pooling is attractive.
        // run 4 test scenarios:
        // 1st: just a simple query
        // 2nd: on an alternate connection, begin a transaction, insert a record (dirty), and stall and later
        //    rollback.  while the alternate connection is stalled, run the query.  it should block until
        //    the alternate connection rolls back
        // 3rd: the same as the 2nd scenario, except that before we start, the primary connection starts
        //    a transaction using read-uncommitted and then immediately rolls it back.  you would think this
        //    wouldn't matter.
        // 4th: same as the 1st scenario
        
        for (int test = 1; test <= 4; test += 1)
        {
            // for scenario 3, open a transaction at read uncommitted level, and roll it back
            if (test == 3)
            {
                testConn.Open();
                var presumablyInnocent = testConn.BeginTransaction(IsolationLevel.ReadUncommitted);
                presumablyInnocent.Rollback();
                testConn.Close();
            }

            Thread background = null;
            // for scenarios 2 and 3, insert a record uncommitted (transaction still
            // open), and wait 3 seconds
            if (test == 2 || test == 3)
            {
                var lockingTransaction = setupConn.BeginTransaction(IsolationLevel.ReadCommitted);
                setupConn.Execute($"insert into {evilTable} (MyKey, MyValue) values ('key2', 'UNEXPECTED')",
                    transaction: lockingTransaction);
                background = new Thread(() => {
                    Thread.Sleep(TimeSpan.FromSeconds(3));
                    lockingTransaction.Rollback();
                });
                background.Start();
            }

            // probe the test table, writing all records to console
            var items = testConn.Query<Dto>($"select MyKey, MyValue from {evilTable}").ToList();
            foreach (var item in items)
                Console.WriteLine($"{poolTest}/{test}: {item}");
            scary |= items.Any(item => item.MyValue == "UNEXPECTED");
            // wait for the background thread (if there was one)
            background?.Join();
        }
        if (scary)
            Console.WriteLine($"{poolTest}/F: we got a dirty read, all unexpected-like; isn't that scary");
        if (poolMode == 1)
            Console.WriteLine();
    }
}

// this is the test table, a simplification of tables used everywhere
private class Dto
{
    public string? MyKey { get; set; }
    public string? MyValue { get; set; }
    public override string ToString() => $"{MyKey} {MyValue}";
}

There are several solutions to this issue. None of them are ideal. The most obvious is to reset the transaction isolation level of a connection before Closing or Disposing it, returning it to the pool. In EF Core, it is possible to use a DB Interceptor (IDbConnectionInterceptor) to do this.
I’ve seen various answers suggesting use of a different connection string for each prospective isolation level, or just never using isolation levels at all; these are not very useful outside of toy or very small programs.

So here’s my actual question:
What does Microsoft intend for us to do ? Every straightfoward example I’ve seen is susceptible to this leak. I’ve seen the TDS documentation specify this behavior, but there’s never any hint as to why it’s done this way. So, why is connection pooling implemented in such a way that new connections and recycled connections have different (and troublesome) default transaction isolation level behavior?

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật