May 142012
 

I was enjoying a nice cup of white tea, starting my day by going over the usual suspects and looking into errors when I could no longer access some of my servers. It was interesting to note that I could ping the server, had correct name resolution, and some I could even remote into!

After a few minutes of looking around, the SQL Server services were stopped… but why? When I went to take a look at the log files I noticed… there aren’t any drives! Where did all of my data go?! (Insert puzzled look here)

After a few more minutes and some collaboration, the problem was found – the HBAs couldn’t talk to a specific disk pool on the SAN. This affected more than just the SQL Server instances, as some of my servers are SAN boot and were offline entirely. While I hate for anyone to have to be in this situation, overall I believe it is needed every once and a while.

Right now most people would look at me and think I’m a little crazy, and it might be accurate to a degree. After all, who in their right mind likes it when a disaster happens? I don’t specifically like it, but it does allow for a few things to come into light.

1. How good is your disaster management/recovery plan
2. It shows that a SAN device isn’t the end-all-be-all-never-has-issues that everyone thinks.
3. Real world testing of your HA environment to show deficiencies
4. Your composure during an issue and your training on handling it as a DBA.
5. You have VERIFIED GOOD backups that aren’t on the same SAN… right?

The end result was the problem was fixed and systems came up without issue, no data was lost… but it could have been.

What happened was a nice mixture of different teams working together to fix the issue as fast and accurately as possible, limiting downtime and restoring service. What I took from it was my training from mock disaster testing and how it helped immensly with REAL disasters. I wasn’t shaken, I didn’t panic, I was confident we’d get through this with as little impact as I could possibly allow.

My tea had gone cold by the time we were finished, but it was a small price for the experience earned through the issue.

Apr 242012
 

When SQL Server 2012 was announced I noticed that one of the developer features added was the Sequence. It’s actually very exciting and I can stop being picked on by my Oracle friends for FINALLY having this! I wrote a quick demo to take a look and show our developers how it can be used and different aspects of this new option in SQL Server 2012. I will warn you that I submitted a connect item about sequences which was noted as a bug and will be fixed in a future update: https://connect.microsoft.com/SQLServer/feedback/details/721322/sequence-cache-size-unexpected-results

/* create a test database */
CREATE DATABASE Sequence_Test;
GO

USE Sequence_Test;
GO

/* create test tables */
CREATE TABLE Order_Header
(
OrderID		BIGINT NOT NULL,
OrderDate	DATE NOT NULL,
CustomerID	INT NOT NULL
);
GO

CREATE TABLE Order_Detail
(
OrderID	BIGINT NOT NULL,
ProductID	INT NOT NULL,
Amount	INT NOT NULL,
);
GO

CREATE TABLE Products
(
ProductID	INT IDENTITY(1,1) NOT NULL,
ProductName	VARCHAR(256) NOT NULL
);
GO

CREATE TABLE Customers
(
CustomerID	INT IDENTITY(100000, 1) NOT NULL,
FirstName	VARCHAR(60) NOT NULL,
LastName	VARCHAR(60) NOT NULL,
Company		VARCHAR(120) NULL
);
GO

INSERT INTO Customers(FirstName, LastName, Company) VALUES ('Goof', 'Ball', 'GoofyBalls LTD.')
, ('Mickey','Mouse','Disney Corp.')
, ('Agent','Smith','')
GO

INSERT INTO Products(ProductName) VALUES ('Widgets')
,('Gadgets')
,('Pixie Dust')
,('Angry Mob')
,('Grab Bag')
,('Bouncy Balls')
,('Dog Treats')
GO

/* Create the sequence to tie order headers and details together */
CREATE SEQUENCE Orders_Sequence
AS BIGINT
START WITH 1000000
INCREMENT BY 1
MINVALUE 1000000
NO MAXVALUE
NO CYCLE
NO CACHE

GO

/* inspect new sequence */
SELECT * FROM sys.sequences WHERE [name] = 'Orders_Sequence'

/* Use the sequence once and re-check */

DECLARE @SeqNum BIGINT;
SELECT @SeqNum = NEXT VALUE FOR dbo.Orders_Sequence;

INSERT INTO Order_Header(OrderID, OrderDate, CustomerID) VALUES (@SeqNum, GETDATE(), 1);
INSERT INTO Order_Detail(OrderID, ProductID, Amount) VALUES (@SeqNum, 6, 100);
INSERT INTO Order_Detail(OrderID, ProductID, Amount) VALUES (@SeqNum, 7, 250);
GO

/* Inspect Sequence Again */
SELECT * FROM sys.sequences WHERE [name] = 'Orders_Sequence';
GO

/* inspect tables */
SELECT * FROM Order_Header;
SELECT * FROM Order_Detail;
GO

/* make another order */
DECLARE @SeqNum BIGINT;
SELECT @SeqNum = NEXT VALUE FOR dbo.Orders_Sequence;

INSERT INTO Order_Header(OrderID, OrderDate, CustomerID) VALUES (@SeqNum, GETDATE(), 2);
INSERT INTO Order_Detail(OrderID, ProductID, Amount) VALUES (@SeqNum, 1, 100);
INSERT INTO Order_Detail(OrderID, ProductID, Amount) VALUES (@SeqNum, 2, 20);
INSERT INTO Order_Detail(OrderID, ProductID, Amount) VALUES (@SeqNum, 3, 10);
GO

/* Inspect Sequence Again */
SELECT * FROM sys.sequences WHERE [name] = 'Orders_Sequence';
GO

/* inspect tables */
SELECT * FROM Order_Header;
SELECT * FROM Order_Detail;
GO

/* what happens if we rollback a transaction and then run the sequence again?
	are there holes in it like identity? */

BEGIN TRAN
DECLARE @SeqNum BIGINT;
SELECT @SeqNum = NEXT VALUE FOR dbo.Orders_Sequence;

INSERT INTO Order_Header(OrderID, OrderDate, CustomerID) VALUES (@SeqNum, GETDATE(), 3);
INSERT INTO Order_Detail(OrderID, ProductID, Amount) VALUES (@SeqNum, 4, 90000);
ROLLBACK
GO

DECLARE @SeqNum BIGINT;
SELECT @SeqNum = NEXT VALUE FOR dbo.Orders_Sequence;

INSERT INTO Order_Header(OrderID, OrderDate, CustomerID) VALUES (@SeqNum, GETDATE(), 3);
INSERT INTO Order_Detail(OrderID, ProductID, Amount) VALUES (@SeqNum, 4, 500);
GO

/* Inspect Sequence Again */
SELECT * FROM sys.sequences WHERE [name] = 'Orders_Sequence';
GO

/* inspect tables */
SELECT * FROM Order_Header;
SELECT * FROM Order_Detail;
GO

/* As you can see sequences still leaves holes as identity does */
USE master;
GO

DROP DATABASE Sequence_Test;
GO
Apr 242012
 

I was responding to a MSDN post about restoring from a marked transaction and when I went to the COL entry for this, the examples given weren’t very extensive. The page had the correct information, but in the examples at the bottom the content was obscure at best and somewhat incorrect at the worst (in my opinion).

I wrote a quick demo for the response and I thought I’d share it here too.

CREATE DATABASE STOPAT_Test;
GO

USE STOPAT_Test;
GO

ALTER DATABASE STOPAT_Test SET Recovery FULL
GO

CREATE TABLE MyData
(
ID			INT IDENTITY(1,1),
DataValue	VARCHAR(50) NOT NULL
);
GO

INSERT INTO MyData(DataValue) VALUES ('The First Entry');

BACKUP DATABASE [STOPAT_Test] TO DISK = N'C:\backup\STOPAT_Test_Full.bak' WITH CHECKSUM, INIT, STATS=10

BEGIN TRAN MyTran1 WITH MARK 'Tran1'
INSERT INTO MyData(DataValue) VALUES ('The Second Entry');
COMMIT TRAN MyTran1

BACKUP LOG [STOPAT_Test] TO DISK = N'C:\backup\STOPAT_Test_Log_1.trn' WITH CHECKSUM, INIT, STATS=10

INSERT INTO MyData(DataValue) VALUES ('The Third Entry');

BEGIN TRAN MyTran2 WITH MARK 'Tran2'
INSERT INTO MyData(DataValue) VALUES ('The Fourth Entry');
COMMIT TRAN MyTran2

BACKUP LOG [STOPAT_Test] TO DISK = N'C:\backup\STOPAT_Test_Log_2.trn' WITH CHECKSUM, INIT, STATS=10

INSERT INTO MyData(DataValue) VALUES ('The Fifth Entry');

BACKUP LOG [STOPAT_Test] TO DISK = N'C:\backup\STOPAT_Test_Log_3.trn' WITH CHECKSUM, INIT, NO_TRUNCATE, STATS=10

/* check data */
SELECT * FROM dbo.MyData

/* start restore process */
ALTER DATABASE [STOPAT_Test] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE master
GO

/* check our marked transaction history, so we know where to retore to first */

SELECT * FROM msdb.dbo.logmarkhistory

/* restore to our first mark */

RESTORE DATABASE [STOPAT_Test] FROM DISK = N'C:\backup\STOPAT_Test_Full.bak' WITH REPLACE, NORECOVERY, STATS=10

RESTORE LOG [STOPAT_Test] FROM DISK = N'C:\backup\STOPAT_Test_Log_1.trn' WITH STOPBEFOREMARK = 'MyTran1', STANDBY = N'C:\backup\stndby.sbf', STATS=10

USE STOPAT_Test
GO

/* only data from before the transaction is there STOPBEFOREMARK */
SELECT * FROM dbo.MyData

USE MASTER
GO

RESTORE LOG [STOPAT_Test] FROM DISK = N'C:\backup\STOPAT_Test_Log_1.trn' WITH STOPATMARK = 'MyTran1', STANDBY = N'C:\backup\stndby.sbf', STATS=10

USE STOPAT_Test
GO
/* we stopped AT the mark now, so we have the data */
SELECT * FROM dbo.MyData

USE MASTER
GO

/* finish any transaction from the end of Log_1.trn and stop BEFORE MyTran2 */
RESTORE LOG [STOPAT_Test] FROM DISK = N'C:\backup\STOPAT_Test_Log_1.trn' WITH STANDBY = N'C:\backup\stndby.sbf', STATS=10
RESTORE LOG [STOPAT_Test] FROM DISK = N'C:\backup\STOPAT_Test_Log_2.trn' WITH STOPBEFOREMARK = 'MyTran2', RECOVERY, STATS=10
GO

USE STOPAT_Test
GO

/* everything before MyTran 2 */
SELECT * FROM dbo.MyData

ALTER DATABASE STOPAT_Test SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO

USE master
GO
DROP DATABASE [STOPAT_Test]
Apr 102012
 

This post was made due to the high number of posts on the MSDN SQL Server forums about finding the instances of SQL Server on a given machine. Most of the posts go something like this, “I installed SQL Server but how do I connect to it, nothing shows up in Server Explorer!!!”.

There are many different ways to figure out instances of SQL Server that are installed on a given machine, but one of the easiest methods I have found (especially when dealing with a large corporation) is to use powershell. It’s actually quite easy and can be automated if need be.

What we’ll use is the Get-Services command and only show those objects returned that have a display name like “SQL Server (*)”. Why the display name like that? When the services are created, the display name for SQL Server (not full text, browser, or any other services installed, just the database engine service) is always “SQL Server (InstanceName)”. This allows us to only return the database engine services which is all we are interested in. To make things a bit mor einteresting and flexible, Get-Services allows for a remote computer to be specified (-ComputerName) which means we can query all the servers we need to from our desktop.

Example:

Get-Service -ComputerName ComputerNameHere | Where-Object {$_.DisplayName -like “SQL Server (*)”}

Get-Services with the -ComputerName option used

Get-Services with the -ComputerName option used to show SQL Server database engine instances on a remote server