如何停止 Sql Server 的用户实例?(在停止 Sql Express 服务后,Sql Express 用户实例数据库文件仍被锁定)

6

当使用SQL Server Express 2005的用户实例功能时,连接字符串如下:

<add name="Default" connectionString="Data Source=.\SQLExpress;
  AttachDbFilename=C:\My App\Data\MyApp.mdf;
  Initial Catalog=MyApp;
  User Instance=True;
  MultipleActiveResultSets=true;
  Trusted_Connection=Yes;" />

我们发现即使停止SqlExpress服务,我们仍然无法复制数据库文件MyApp.mdf和MyApp_Log.ldf(因为它们被锁定),必须将SqlExpress服务从自动启动模式设置为手动启动模式,然后重新启动计算机,才能复制这些文件。
我认为停止SqlExpress服务应该停止所有用户实例,从而释放对这些文件的锁定。但事实并非如此。有谁能解释一下如何停止用户实例,以便其数据库文件不再被锁定?
更新:好吧,我停止了懒惰,启动了Process Explorer。锁定是由sqlserver.exe持有的,但有两个sql server实例。
sqlserver.exe  PID: 4680  User Name: DefaultAppPool
sqlserver.exe  PID: 4644  User Name: NETWORK SERVICE

该文件正在由PID为4680的sqlserver.exe实例打开。

停止"SQL Server (SQLEXPRESS)"服务时,结束了PID为4644的进程,但PID为4680的进程被留下来了。

由于剩余进程的所有者是DefaultAppPool,我尝试的下一个步骤是停止IIS(此数据库正在从ASP.Net应用程序中使用)。不幸的是,这也没有终止该进程。

手动结束剩余的sql server进程会删除在数据库文件上的打开文件句柄,使它们可以被复制/移动。

不幸的是,我希望在WiX安装程序的一些预/后安装任务中复制/恢复这些文件 - 因此我希望有一种方法可以通过停止Windows服务来实现这一点,而不必外壳杀死所有sqlserver.exe的实例,因为这会带来一些问题:

  1. 杀死所有sqlserver.exe实例可能对那些在其机器上拥有其他Sql Server实例的用户产生不良影响。
  2. 我无法轻松重新启动这些实例。
  3. 为安装程序引入额外的复杂性。

是否有人对如何关闭与特定用户实例相关联的sql server实例有进一步想法?


感谢大家的回答 - 最有用的答案是使用SSEUtil.exe。虽然我们需要从WiX安装程序中调用它,但我们可以看到这可能会有一些问题,即使我们使用哪种方法都是如此 - 所以我们可能只需为用户提供一些手动的预安装步骤,让他们在升级之前使用SSEUtil.exe自行处理。 - Bittercoder
我很好奇,SSEUtil.exe不能解决权限问题吗?如果用户必须使用SSEUtil自己处理这个问题,那么安装程序在调用SSEUtil时不应该起作用吗? - AMissico
在我的测试中,这似乎并不是这种情况 - 我相信这只是因为安装程序在调用SSEUtil时没有提升权限...但我还没有时间进一步调查。另外,我们发现,在Windows7 x64上安装了SqlServer和SqlServer Express时,即使传递-s .\SqlExpress参数,SSEUtil也无法正确列出所有活动用户实例。所以我们仍然有一些边缘情况需要解决 :) - Bittercoder
4个回答

7
使用“SQL Server Express Utility”(SSEUtil.exe)或命令分离由SSEUtil使用的数据库。
SQL Server Express Utility,SSEUtil是一个工具,可以让您轻松地与SQL Server交互,http://www.microsoft.com/downloads/details.aspx?FamilyID=fa87e828-173f-472e-a85c-27ed01cf6b02&DisplayLang=en 此外,关闭最后一个连接后停止服务的默认超时时间为一小时。在开发环境中,您可能希望将其更改为五分钟(允许的最小值)。
此外,您可能通过Visual Studio的Server Explorer数据连接打开了一个连接,请确保从那里断开任何数据库。
H:\Tools\SQL Server Express Utility>sseutil -l
1. master
2. tempdb
3. model
4. msdb
5. C:\DEV_\APP\VISUAL STUDIO 2008\PROJECTS\MISSICO.LIBRARY.1\CLIENTS\CORE.DATA.C
LIENT\BIN\DEBUG\CORE.DATA.CLIENT.MDF

H:\Tools\SQL Server Express Utility>sseutil -d C:\DEV*
Failed to detach 'C:\DEV_\APP\VISUAL STUDIO 2008\PROJECTS\MISSICO.LIBRARY.1\CLIE
NTS\CORE.DATA.CLIENT\BIN\DEBUG\CORE.DATA.CLIENT.MDF'

H:\Tools\SQL Server Express Utility>sseutil -l
1. master
2. tempdb
3. model
4. msdb

H:\Tools\SQL Server Express Utility>

使用.NET Reflector,以下命令用于分离数据库。
string.Format("USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{1}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tEXEC sp_detach_db [{1}]\nEND", dbName, str);

谢谢,我需要在许多客户机器上从 WiX 安装程序中调用此操作(这些机器上没有安装 SSEUtil),但我会将其作为可能的选项进行探索。 - Bittercoder
1
SSEUtil是一个.NET应用程序。使用.NET Reflector来查看它如何分离数据库。 - AMissico
我建议将WiX安装程序的要求作为您完整问题的一部分。这很容易被忽略。 - AMissico

2

我一直使用以下助手方法在单元测试中分离连接到SQL Server的MDF文件(这样SQ Server将释放对MDF和LDF文件的锁定,单元测试可以自行清理)...

private static void DetachDatabase(DbProviderFactory dbProviderFactory, string connectionString)
{
    using (var connection = dbProviderFactory.CreateConnection())
    {
        if (connection is SqlConnection)
        {
            SqlConnection.ClearAllPools();

            // convert the connection string (to connect to 'master' db), extract original database name
            var sb = dbProviderFactory.CreateConnectionStringBuilder();
            sb.ConnectionString = connectionString;
            sb.Remove("AttachDBFilename");
            var databaseName = sb["database"].ToString();
            sb["database"] = "master";
            connectionString = sb.ToString();

            // detach the original database now
            connection.ConnectionString = connectionString;
            connection.Open();
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "sp_detach_db";
                cmd.CommandType = CommandType.StoredProcedure;

                var p = cmd.CreateParameter();
                p.ParameterName = "@dbname";
                p.DbType = DbType.String;
                p.Value = databaseName;
                cmd.Parameters.Add(p);

                p = cmd.CreateParameter();
                p.ParameterName = "@skipchecks";
                p.DbType = DbType.String;
                p.Value = "true";
                cmd.Parameters.Add(p);

                p = cmd.CreateParameter();
                p.ParameterName = "@keepfulltextindexfile";
                p.DbType = DbType.String;
                p.Value = "false";
                cmd.Parameters.Add(p);

                cmd.ExecuteNonQuery();
            }
        }
    }
}

注:

  • SqlConnection.ClearAllPools() 在消除“隐形”连接方面非常有帮助(当连接被池化时,即使您关闭它,它仍然保持活动状态;通过显式清除池连接,您无需担心在所有连接字符串中将池标志设置为 false)。
  • “魔法成分”的调用是系统存储过程 sp_detach_db (Transact-SQL)
  • 我的连接字符串包含“AttachDBFilename”,但没有包含“User Instance=True”,因此此解决方案可能不适用于您的场景。

感谢提供代码。在我的情况下,我需要从WiX安装程序中调用它(而不是应用程序本身、测试夹具等)。另外,不幸的是,用户实例的数据库用户可能没有足够的权限在master上调用sp_detach_db。但这确实给了我一些其他的尝试选项。 - Bittercoder
以下MSDN文档页面http://msdn.microsoft.com/en-us/library/bb264564(SQL.90).aspx指出:“通过调用sp_detach_db从实例中分离数据库将关闭文件。这是Visual Studio使用的方法,以确保在IDE在用户实例之间切换时关闭数据库文件。”--看起来Visual Studio本身使用sp_detach_db来关闭用户实例 :-)。我假设@AMissico建议使用的SSEUtil工具也使用它(并且它以可从Wix调用的便捷exe形式出现) - Milan Gardian
我更新了我的答案,包括SSEUtil使用的实际命令。 - AMissico
我认为随着Sql 2008的更改,@keepfulltextindexfile正在被弃用。 - Keith

1

我还不能发表评论,因为我的声望还不够高。有人能把这个信息移到另一个答案里吗,这样我们就不会有重复的了?

我刚刚使用了这篇文章来解决我的WIX卸载问题。我使用了AMissico答案中的这行代码。

string.Format("USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{1}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tEXEC sp_detach_db [{1}]\nEND", dbName, str);

使用WIX时效果很好,只是我不得不添加一件东西才能让它为我工作。

我将sp_detach_db删除了,然后将数据库恢复在线状态。如果你不这样做,卸载后WIX会保留mdf文件。一旦我将数据库恢复在线状态,WIX就可以正确地删除这些mdf文件。

这是我的修改行。

string.Format( "USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{0}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tALTER DATABASE [{0}] SET ONLINE\nEND", dbName );

-3

这可能不是您要找的内容,但是免费工具Unlocker具有命令行界面,可以从WIX中运行。(我已经使用unlocker一段时间了,并发现它非常稳定,非常擅长于它最擅长的解锁文件。)

Unlocker可以解锁和移动/删除大多数任何文件。

缺点是需要一个文件锁定的应用程序将不再拥有它。(但有时仍然可以正常工作。)请注意,这不会杀死拥有锁定的进程。它只是删除了它的锁定。(重新启动您停止的sql服务可能足以重新锁定和/或正常工作。)

您可以从这里获取Unlocker: http://www.emptyloop.com/unlocker/

要查看命令行选项,请运行unlocker -H这里为方便起见:

Unlocker 1.8.8
命令行用法: Unlocker.exe 对象 [选项] 对象: 包括驱动器的完整路径到文件或文件夹 选项: /H 或 -H 或 /? 或 -?: 显示命令行用法 /S 或 -S: 解锁对象而不显示 GUI /L 或 -L: 对象是包含要解锁文件列表的文本文件 /LU 或 -LU: 类似于 /L,带有要解锁文件的 Unicode 列表 /O 或 -O: 在 Unlocker 目录中输出 Unlocker-Log.txt 日志文件 /D 或 -D: 删除文件 /R Object2 或 -R Object2: 重命名文件,如果设置了 /L 或 /LU,则 object2 指向包含文件新名称的文本文件 /M Object2 或 -M Object2: 移动文件,如果设置了 /L 或 /LU,则 object2 指向包含文件新位置的文本文件
假设您的目标是用安装程序中的文件替换 C:\My App\Data\MyApp.mdf,您需要像 unlocker C:\My App\Data\MyApp.mdf -S -D 这样的东西。这将删除该文件,以便您可以复制一个新文件。

哼,真是讨厌,明明被踩了却没有任何评论说明原因。 - Vaccano
1
我想,这个回答被踩的原因是,如果MDF文件被锁定,那肯定有它的原因,使用Unlocker可能会损坏数据库或者更糟糕的情况。 - AMissico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接