Вспомогательный объект transactionscope, который истощает пул соединений не удастся, - помочь?
Некоторое время назад я задал вопрос о переходе TransactionScope на MSDTC, когда я этого не ожидал. (предыдущий вопрос)
Суть его сводилась к тому, что в SQL2005 для использования TransactionScope можно только создать экземпляр и открыть одно соединение SqlConnection в течение срока службы TransactionScope. С помощью SQL2008 можно создать экземпляр нескольких соединений SqlConnections, но только одно из них может быть открыто в любой момент времени. SQL2000 всегда приведет к неисправности...мы не поддерживаем SQL2000 в нашем приложении, Приложение WinForms, кстати.
Нашим решением проблемы с одним соединением было создание вспомогательного класса TransactionScope, называемого LocalTransactionScope (он же "LTS"). Он обертывает TransactionScope и, самое главное, создает и поддерживает один экземпляр SqlConnection для нашего приложения. Хорошая новость в том, что это работает - мы можем использовать LTS в разных частях кода, и все они присоединяются к внешней транзакции. Очень симпатичный. Проблема в том, что каждый корень LTS созданный экземпляр создаст и эффективно уничтожит соединение из пула соединений. Купить 'эффективно убить я имею в виду его примеру SqlConnetion, который откроет Новый соединения (по любой причине, он никогда не использует соединение из пула,) и когда этот корень его утилизировать, он закрывает и ликвидирует объект sqlconnection, который предполагается выпустить соединения в пул, так что его можно повторно использовать, однако, он явно никогда не используется повторно. Бассейн раздувается, пока не достигнет максимума если установлено соединение max-pool-size+1, то приложение выходит из строя.
Ниже я прикрепил урезанную версию кода LTS и пример класса консольного приложения, который продемонстрирует исчерпание пула соединений. Чтобы наблюдать за раздуванием пула соединений, используйте SQL Server Managment Studio 'Activity Monitor' или этот запрос:
SELECT DB_NAME(dbid) as 'DB Name',
COUNT(dbid) as 'Connections'
FROM sys.sysprocesses WITH (nolock)
WHERE dbid > 0
GROUP BY dbid
Я прикрепляю здесь LTS и пример консольного приложения, которое вы можете использовать, чтобы продемонстрировать для себя, что это будет потреблять соединения из пула и никогда не будет повторно использовать или освобождать их. Вам нужно будет добавить ссылку на систему.Операции.dll для компиляции LTS.
Обратите внимание: именно LTS корневого уровня открывает и закрывает SqlConnection, который всегда открывает новое соединение в пуле. Наличие вложенных экземпляров LTS не имеет значения, поскольку только корневой экземпляр LTS устанавливает SqlConnection. Как вы можете видеть, строка соединения всегда одна и та же, поэтому она должна быть повторное использование соединений.
Есть ли какое-то тайное условие, которое мы не встречаем, которое заставляет соединения не использоваться повторно? Есть ли какое-либо решение для этого, кроме полного отключения пула?
public sealed class LocalTransactionScope : IDisposable
{
private static SqlConnection _Connection;
private TransactionScope _TransactionScope;
private bool _IsNested;
public LocalTransactionScope(string connectionString)
{
// stripped out a few cases that need to throw an exception
_TransactionScope = new TransactionScope();
// we'll use this later in Dispose(...) to determine whether this LTS instance should close the connection.
_IsNested = (_Connection != null);
if (_Connection == null)
{
_Connection = new SqlConnection(connectionString);
// This Has Code-Stink. You want to open your connections as late as possible and hold them open for as little
// time as possible. However, in order to use TransactionScope with SQL2005 you can only have a single
// connection, and it can only be opened once within the scope of the entire TransactionScope. If you have
// more than one SqlConnection, or you open a SqlConnection, close it, and re-open it, it more than once,
// the TransactionScope will escalate to the MSDTC. SQL2008 allows you to have multiple connections within a
// single TransactionScope, however you can only have a single one open at any given time.
// Lastly, let's not forget about SQL2000. Using TransactionScope with SQL2000 will immediately and always escalate to DTC.
// We've dropped support of SQL2000, so that's not a concern we have.
_Connection.Open();
}
}
/// <summary>'Completes' the <see cref="TransactionScope"/> this <see cref="LocalTransactionScope"/> encapsulates.</summary>
public void Complete() { _TransactionScope.Complete(); }
/// <summary>Creates a new <see cref="SqlCommand"/> from the current <see cref="SqlConnection"/> this <see cref="LocalTransactionScope"/> is managing.</summary>
public SqlCommand CreateCommand() { return _Connection.CreateCommand(); }
void IDisposable.Dispose() { this.Dispose(); }
public void Dispose()
{
Dispose(true); GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
_TransactionScope.Dispose();
_TransactionScope = null;
if (!_IsNested)
{
// last one out closes the door, this would be the root LTS, the first one to be instanced.
LocalTransactionScope._Connection.Close();
LocalTransactionScope._Connection.Dispose();
LocalTransactionScope._Connection = null;
}
}
}
}
Это программа.cs, который покажет исчерпание пула соединений:
class Program
{
static void Main(string[] args)
{
// fill in your connection string, but don't monkey with any pooling settings, like
// "Pooling=false;" or the "Max Pool Size" stuff. Doesn't matter if you use
// Doesn't matter if you use Windows or SQL auth, just make sure you set a Data Soure and an Initial Catalog
string connectionString = "your connection string here";
List<string> randomTables = new List<string>();
using (var nonLTSConnection = new SqlConnection(connectionString))
using (var command = nonLTSConnection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = @"SELECT [TABLE_NAME], NEWID() AS [ID]
FROM [INFORMATION_SCHEMA].TABLES]
WHERE [TABLE_SCHEMA] = 'dbo' and [TABLE_TYPE] = 'BASE TABLE'
ORDER BY [ID]";
nonLTSConnection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string table = (string)reader["TABLE_NAME"];
randomTables.Add(table);
if (randomTables.Count > 200) { break; } // got more than enough to test.
}
}
nonLTSConnection.Close();
}
// we're going to assume your database had some tables.
for (int j = 0; j < 200; j++)
{
// At j = 100 you'll see it pause, and you'll shortly get an InvalidOperationException with the text of:
// "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool.
// This may have occurred because all pooled connections were in use and max pool size was reached."
string tableName = randomTables[j % randomTables.Count];
Console.Write("Creating root-level LTS " + j.ToString() + " selecting from " + tableName);
using (var scope = new LocalTransactionScope(connectionString))
using (var command = scope.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "SELECT TOP 20 * FROM [" + tableName + "]";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.Write(".");
}
Console.Write(Environment.NewLine);
}
}
Thread.Sleep(50);
scope.Complete();
}
Console.ReadKey();
}
}
2 ответа:
Ожидаемый шаблон TransactionScope/SqlConnection, согласно MSDN:
using(TransactionScope scope = ...) { using (SqlConnection conn = ...) { conn.Open(); SqlCommand.Execute(...); SqlCommand.Execute(...); } scope.Complete(); }
Таким образом, в Примере MSDN conenction располагается Внутри области, Перед областью завершения. Ваш код, хотя и отличается, он располагает соединение после область завершена. Я не эксперт в вопросах TransactionScope и его взаимодействия с SqlConnection (я знаю Некоторые вещи, но ваш вопрос идет довольно глубоко), и я не могу найдите любые спецификации, что является правильным шаблоном. Но я бы предложил вам пересмотреть свой код и избавиться от одноэлементного соединения до завершения внешней области, аналогично образцу MSDN.
Кроме того, я надеюсь, что вы понимаете, что ваш код развалится в тот момент, когда в ваше приложение войдет второй поток.
Законен ли этот кодекс?
using(TransactionScope scope = ..) { using (SqlConnection conn = ..) using (SqlCommand command = ..) { conn.Open(); SqlCommand.Execute(..); } using (SqlConnection conn = ..) // the same connection string using (SqlCommand command = ..) { conn.Open(); SqlCommand.Execute(..); } scope.Complete(); }