开发者

When is a started service not a started service? (SQL Express)

We require programmatic access to a SQL Server Express service as part of our application. Depending on what the user is trying to do, we may have to attach a database, detach a database, back one up, etc. Sometimes the service might not be started before we attempt these operations. So we need to ensure the service is started. Here is where we are running into problems. Apparently the ServiceController.WaitForStatus(ServiceControllerStatus.Running) returns prematurely for SQL Server Express. What is really puzzling is that the master database seems to be immediately available, but not other databases. Here is a console application to demonstrate what I am talking about:

namespace ServiceTest
{
    using System;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.ServiceProcess;
    using System.Threading;

    class Program
    {
        private static readonly ServiceController controller = new ServiceController("MSSQL$SQLEXPRESS");
        private static readonly Stopwatch stopWatch = new Stopwatch();

        static void Main(string[] args)
        {
            stopWatch.Start();

            EnsureStop();
            Start();
            OpenAndClose("master");

            EnsureStop();
            Start();
            OpenAndClose("AdventureWorksLT");

            Console.ReadLine();
        }

        private static void EnsureStop()
        {
            Console.WriteLine("EnsureStop enter, {0:N0}", stopWatch.ElapsedMilliseconds);

            if (controller.Status != ServiceControllerStatus.Stopped)
            {
                controller.Stop();
                controller.WaitForStatus(ServiceControllerStatus.Stopped);
                Thread.Sleep(5000); // really, really make sure it stopped ... this has a problem too.
            }

            Console.WriteLine("EnsureStop exit, {0:N0}", stopWatch.ElapsedMilliseconds);
        }

        private static void Start()
        {
            Console.WriteLine("Start enter, {0:N0}", stopWatch.ElapsedMilliseconds);
            controller.Start();
            controller.WaitForStatus(ServiceControllerStatus.Running);
            // Thread.Sleep(5000); 
            Console.WriteLine("Start exit, {0:N0}", stopWatch.ElapsedMilliseconds);
        }

        private static void OpenAndClose(string database)
        {
            Console.WriteLine("OpenAndClose enter, {0:N0}", stopWatch.ElapsedMilliseconds);
            var connection = new SqlConnection(string.Format(@"Data Source=.\SQLEXPRESS;initial catalog={0};integrated security=SSPI", database));
            connection.Open();
            connection.Close();
            Console.WriteLine("OpenAndClose exit, {0:N0}", stopWatch.ElapsedMilliseconds);
        }
    }
}

On my machine, this will consistently fail as written. Notice that the connection to "master" has no problems; only the connection to the other database. (You can reverse the order of the connections to verify this.) If you uncomment the Thread.Sleep in the Start() method, it will work fine.

Obviously I want to avoid an arbitrary Thread.Sleep(). Besides the rank code smell, what arbitary value would I put there? The only thing we can think of is to put some dummy connections to our target database in a while loop, catching the SqlException thrown and trying again until it works. But I'm thinking there must be a more elegant solution out there to know when the service is really ready to be used. Any ideas?

EDIT: Based on feedback provided below, I added a check on the status of the database. However, it is still failing. It looks like even the state is not reliable. Here is the function I am calling before OpenAndClose(string):

private static void WaitForOnline(string database)
{
    Console.WriteLine("WaitForOnline start, {0:N0}", stopWatch.ElapsedMilliseconds);

    using (var connection = new SqlConnection(string.Format(@"Data Source=.\SQLEXPRESS;initial catal
    using (var command = connection.CreateCommand())
    {
        connection.Open();

        try
        {
            command.CommandText = "SELECT [state] FROM sys.databases WHERE [name] = @DatabaseName";
            command.Parameters.AddWithValue("@DatabaseName", database);

            byte databaseState = (byte)command.ExecuteScalar();
            Console.WriteLine("databaseState = {0}", databaseState);
            while (databaseState != OnlineState)
            {
                Thread.Sleep(500);
                databaseState = (byte)command.ExecuteScalar();
                Console.WriteLine("databaseState = {0}", databaseState);
            }
        }
        finally
        {
            connection.Close();
        }
    }

    Console.WriteLine("WaitForOnline exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}

I found another discussion dealing with开发者_JS百科 a similar problem. Apparently the solution is to check the sys.database_files of the database in question. But that, of course, is a chicken-and-egg problem. Any other ideas?


Service start != database start.

Service is started when the SQL Server process is running and responded to the SCM that is 'alive'. After that the server will start putting user databases online. As part of this process, it runs the recovery process on each database, to ensure transactional consistency. Recovery of a database can last anywhere from microseconds to whole days, it depends on the ammount of log to be redone and the speed of the disk(s).

After the SCM returns that the service is running, you should connect to 'master' and check your database status in sys.databases. Only when the status is ONLINE can you proceed to open it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜