ADO.NET SQLServer: How to prevent closed connectio

2019-03-25 19:22发布

问题:

i Dispose an SqlConnection object, but of of course it isn't really closed. i need closed connection to not hold locks on database objects. How can i prevent closed connections from holding locks?


Explanation of the above sentance for those who don't know:

  • When you close an ADO or ADO.NET connection, you aren't actually severing the connection to SQL Server. The ADO/ADO.NET infrastructure keeps the connection around in case you want to use it again. The connections are kept lingering around in what's called "The Connection Pool".

  • After a few minutes of not being used, the connection will be actually closed. Although, not really. TCP/IP has it's own method for keeping TCP connections open for a few more minutes (in the "CLOSE_WAIT" state). This is done in case you ask to open a TCP connection to the same IP:Port again. If so, it can use that already open TCP connection.

  • With connection pooling and SQL Server, the connection is still established to SQL Server. Each connection has a database context that it is sitting in. As long as a connection is sitting in that database: it holds a shared database (S-DB) lock on that database.

  • A Shared-Database lock simply means, "Don't delete this database while i'm in it please."

How can i prevent it from holding a shared lock on my database, while keeping the benefits of connection pooling?


My ad-hoc solution right now is every time a developer called Dispose:

connection.Dispose()

change it into a call to a global helper function:

Database.DisposeConnection(connection);

which changes the database context to master:

public static void DisposeConnection(SqlConnection connection)
{
    //Stop holding a database lock - in my database at least
    ADOHelper.ExecuteNonQuery(connection, "USE master");

    connection.Dispose();
}

It solves my immediate problem; closed connections aren't holding a lock on my database.

But now i'm worried that connection pooling will have its brain scrambled - because i switched database contexts behind its back.


In case anyone didn't know, or thought otherwise:

From the SDK:

Close and Dispose are functionally equivalent.

回答1:

You can place the database in single user mode in order to restore it. IIRC, something like this...

ALTER DATABASE TheDatabaseToRestore SET SINGLE_USER WITH  ROLLBACK IMMEDIATE;
RESTORE DATABASE TheDatabaseToRestore 
FROM DISK =N'Z:\Path\To\Backup\BackupFile';
ALTER DATABASE TheDatabaseToRestore SET MULTI_USER;

see: Obtain Exclusive Access to Restore SQL Server and/or Restore Database Backup using SQL for some more background.

Edit: Single user mode is intended for backups and restores (which, on the non-express edition, is what I use it for regularly). Using it kicks out all other connections, and new connections cannot be made. I haven't played with the various "WITH" options such as with "ROLLBACK IMMEDIATE", but their usage seems straightforward.



回答2:

Have you tried SqlClient.ClearPool?

From MSDN:

ClearPool clears the connection pool that is associated with the connection. If additional connections associated with connection are in use at the time of the call, they are marked appropriately and are discarded (instead of being returned to the pool) when Close is called on them.

Just call ClearPool on every connection, but you lose the pooling benefits if you do this.

public class DataFactory
{
  public SqlConnection CreateConnection(string connString)
  {
    SqlConnection conn = new SqlConnection(connString);
    SqlClient.ClearPool(conn);
    return conn;
   }
}

Alternatively, you can disable pooling all together using the Pooling property of the connection string:

string connString = "Data Source=MYSERVER;Initial Catalog=myDb;Integrated Security=true;Pooling=false"


回答3:

As you say when you Close or Dispose a connection it goes back into the pool but is not really closed.

What you then need to do in to close all connections in the pool. This can be done with a ClearAllPools command.

ClearAllPools resets (or empties) the connection pool. If there are connections in use at the time of the call, they are marked appropriately and will be discarded (instead of being returned to the pool) when Close is called on them.

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.clearallpools(VS.80).aspx

There is also a ClearPool command that does the same thing but for a single connection.

Hope this helps

Shiraz



回答4:

Execute "USE TempDB;" first. Then close it.

Or "USE master;", if that's more to your liking.

S-DB locks on either of those databases shouldn't matter during normal production operation, as you cannot get rid of either one and continue running the Server anyway.



回答5:

Reading the comment, you want to restore it.

OK, take it off-line.

Restore is not something that an app should be doing, so the DBA runs this before the RESTORE.

ALTER DATABASE foo SET OFFLINE WITH ROLLBACK IMMEDIATE