Changing SQL Server data file locations in clustered instance using service SIDs
Recently I added a second LUN to a clustered instance of SQL Server to isolate the logs files (and another for the TempDB) and I mounted them to an empty NTFS folder as I've done before, but when I started SQL Server, I recieved the following error:
CREATE FILE encountered operating system error 5 (Access is denied.) while attempting to open or create the physical file 'X:\MSSQL10_50.INSTANCENAME\TempDB\tempdb.mdf
The error means that SQL Server doesn't have NTFS rights to the location of the TempDB, but when I tried to add those rights, the permissions weren't granted to the domain proxy account as I'd expected, but were instead granted to the service SID account, MSSQL$InstanceName. I attempted to grant the permissions to this account at the new location, but couldn't get it to resolve to an actual account. What finally worked was:
- In the permissions change dialog box, change the "Location" from the domain to the local machine (even though it's a cluster and your using a domain account to run the service)
- In the text box, type "NT Service\MSSQL$INSTANCENAME" and click "Check Names"
Even though that appears to be a local account, it will resolve properly on all the cluster nodes involved. This step, as opposed to using the domain proxy account the service is running as, was necessary because (during the initial SQL Server setup process) I'd selected to use the proxy account SID to host permissions rather than a domain group. The better choice permissions-wise, but the source of some confusion!
Clustering walkthrough for SQL Server 2008 on Windows 2008
I recently stumbled across a great walk-through for clustering SQL Server on newer versions of Windows. It's really thorough - everything from setting up iSCSI (in this case, to simulate a shared disk when it's physically attached to one node - not ideal, but lets you test the walk-through), adding the required server roles, preparing the servers, and then a walkthrough of every screen in the SQL installation process. Thanks to the writer of this awesome blog!
http://dbperf.wordpress.com/2010/07/10/walkthrough-cluster-setup-sql-win-2008/
Slow SQL Server queries over a linked server from Windows Server 2008
We recently ran into an issue where linked server queries where sometimes taking significantly longer than they should have - queries that executed in under a second on the source server took two minutes when executed from a remote SQL Server over a linked server connection. We assumed it was something to do with our Windows Failover Cluster network connections, but it was happening on non-clustered servers as well.
After some digging, we discovered that it happened when the source server was Windows Server 2008 (or 2008 R2), regardless of what OS the remote linked server was running, and the issue wasn't tied to any particular version of SQL Server. As it turns out, it related to everybody's least favorite network setting, TCP Auto-Tuning - disabling it on the affected source servers resolved the problem immediately, and the queries returned instantly again.
To remedy the situation, follow these steps:
- On the source SQL Server (the one executing the query, not the linked server), open an elevated command prompt (Right click on CMD.EXE and click "Run as Administrator")
- To disable TCP Auto-tuning, run the following command:
netsh int tcp set global autotuninglevel=disabled
- To disable Windows Scaling Heuristics (another feature that can speed up clients, but is no good for servers), run:
netsh int tcp set heuristics disabled
- This will automatically take effect after Windows has the chance to refresh some network communications - you can either wait about an hour, or, if you'd prefer it take effect immediately, restart the SQL Instance. A reboot isn't necessary for this setting to take effect.
I'm not sure why these settings are on by default for servers - they seem to speed up internet browsing and other client-type activities (though there's no shortage of complaints online about them grinding Windows Vista/7 clients to a halt as well), but can cause big problems on servers.
Roll your own lightweight SQL Server source control
I've wanted to implement some kind of source control on my SQL Servers before, but the only product available at the moment is Red-Gate's SQL Source Control, and I didn't need all the functionality it offered (or want to pay for it). Also, it relies on developers checking-in their changes, and that's prone to forgetfulness anyways, as well as leaving your database prone when somebody just changes something in production, without using their development tool - ouch. Sure, you're protected against accidental drops, but what if somebody tweaks something in production without checking it back in? You're hosed.
All I wanted was a simple process that would run automatically, taking periodic snapshots of the database objects and recording any changes. I decided to roll my own - it's quick, simple, can be set up to run on a schedule, and automatically includes any new databases created on the server without any intervention.
This Stored Procedure goes through the following steps:
- If the Master.dbo.coSourceControl table (used to store the history) doesn't exist, it creates it
- For each database on the server (so new databases are added automatically), it:
- Grabs the text contents of all the user objects (not flagged as "IsMsShipped")
- Compares the contents of each to the last known copy (if there is one)
- If the object is new or has changed, add a new copy to the source control table in master
- Output the number of objects updated
- Optionally, it could email somebody to tell them about the results, but it currently does not
The history is kept in a single table - master.dbo.coSourceControl - which has the database it came from, the object_id, the object name, object contents, and the timestamp. Since it uses the object_id to track things, it will also record a name change in an object, even if the contents didn't change.
To implement it, just grab the script and run it in the master database - it will create the stored procedure coSourceControlRefresh. That's it - now either run it on demand, or you can schedule it. It will create the supporting table (if it's missing) and scan every database every time it's run. To see the history for an object, just do:
SELECT db_name(databaseid) as [Database], object_name(objectid) as [Object Name], SourceDate, ObjectText FROM master.dbo.coSourceControl WHERE object_name(objectid) LIKE '%The name of some object%' ORDER BY SourceDate DESC
Restoring a dropped or changed database object should be as simple as running the query above, grabbing the contents of ObjectText you're interested in, and then pasting it in another window and executing it. Bam - previous version of the object restored (and this stored proc should, the next time it runs, see that you've altered the object and record that there's a "new" version of it).
If you run it and like it - or don't like it - please leave a comment to let me know - nothing expected in return, but it's nice to know when people find it useful. I'm happy to make any enhancements you'd like to see. I hope you enjoy it and it's able to save you from the headache of a dropped database object to which you can't find the source!
Error TF255271 while upgrading TFS 2005 -> 2010
When upgrading TFS 2005 to TFS 2010 (using these instructions) and it worked great on my test computer, but when I went to migrate the production server, I received the following error:
Warning Message: [2011-05-12 20:12:14Z] Servicing step Register Integration Database failed. (ServicingOperation: UpgradePreTfs2010Databases; Step group: AttachPreTFS2010Databases.TfsFramework)
Warning Message: TF255271: The team project collection could not be created. The number of steps before the completion of project creation is: 216. The number of steps completed before the failure was 10.
The error message doesn't give any detail at all, so I opened the log file and found this near the bottom:
[Info @20:12:19.133] [2011-05-12 20:12:14Z][Error] BisCreateSchema.sql Line 816 Error: Incorrect syntax near ','. (10 of 216)
[Info @20:12:19.133] [2011-05-12 20:12:14Z][Informational] Microsoft.TeamFoundation.Framework.Server.CollectionServicingException: BisCreateSchema.sql Line 816 Error: Incorrect syntax near ','.
---> System.Data.SqlClient.SqlException: Incorrect syntax near ','.
Try as I might, I couldn't find the SQL file it referred to, and Google wasn't much help either - however, it seemed that the SQL file wasn't actually to blame, especially since the same upgrade process had run flawlessly on my test server a few days earlier. Then I realized that my test server was SQL 2008 and my production server was SQL 2005 - while I didn't read specifically anywhere that this was a problem, SQL 2005 isn't supported by TFS 2010.
After much digging, the cause of the error ends up being that the TFS upgrade tool (and TFS 2010 in general) doesn't support SQL Server 2005. Upgrading the database server to SQL Server 2008 and re-running the process corrected the error and allowed us to complete the migration.
However, I've read that SQL 2008 support on TFS 2005 is patchy, so this also obliterates your rollback, if you were planning on one
If you get this error, hope this helps!
Lightweight, single-row alternative to OUTPUT clause in T-SQL
SQL Server 2005 adds the option for an OUTPUT clause in your query to act upon table rows and return the old and new values. When I've done queuing in the past, I've used the clause to mark a row as processing and return the value, all in a single operation, so it's lightweight and threadsafe. For example, like this:
UPDATE TOP (1) dbo.MyQueue
SET ClaimedBy = @Server,
ClaimTime = @ClaimTime
OUTPUT INSERTED.QueueID,
INSERTED.SomeData1,
INSERTED.SomeDate2,
INSERTED.SomeData3
INTO #OutputTable (QueueID, Column1, Column2, Column3)
WHERE Some Criteria...
To do this, you'll need to create a table called #OutputTable that has the right schema, which works well if you're returning multiple rows from your query, but is a little cumbersome to work with if you're only doing one row at a time. If you're only returning a single row from your UPDATE query (as I am here), there's an alternative to OUTPUT that's easier to use - just do variable assignment inline in the UPDATE statement! The query above becomes:
UPDATE TOP (1) dbo.MyQueue
SET ClaimedBy = @Server,
ClaimTime = @ClaimTime
@QueueID = QueueID,
@OutputVar1 = SomeData1,
@OutputVar2 = SomeData2,
@OutputVar3 = SomeData3
WHERE Some Criteria...
Notice the reversed variable assignment in the second query? I've done away with my table, and my OUTPUT clause, and now I just have the relevant values from the row I'm interested in. Much easier to work with, and as an added bonus (though I hope you're not in this situation), it works just fine in SQL 2000.
The caveat is that it's only good for a single row, and it only works for UPDATE - if you're using DELETE, you'll still need the temp table and an OUTPUT clause.
Removing an arbitrary number of spaces from a string in SQL Server
When I was concatenating some fields to create an address, I ended up with a number of spaces in my address that were unnecessary. For example, I had this:
SELECT StreetNumber + ' ' + Direction + ' ' + StreetName + ' ' + StreetType as Address
However, when an address didn't have a direction, I ended up with a double-space in the middle of my address, and I wanted a way to clean it up. Enter the code I found at http://www.itjungle.com/fhg/fhg101106-story02.html:
SELECT name,
REPLACE(REPLACE(REPLACE(name,' ','<>'),'><',''),'<>',' ')
FROM SomeTable
This shortens any run of spaces in the string into a single space - sneaky! It works in any language that supports a function like REPLACE, which scans one string for instances of a second string, and swaps them out for something else.
Migrate database indexes to a new file group
I recently had to mass-migrate all the indexes from a database to a new file group since we'd added some additional storage to our database server. I found this article at SQL Server Central(unfortunately, registration required, so I've included a copy of the original script in the download at the end). While it worked okay, there were some things I didn't like about it:
- Assumed 90% index fill rate
- "Moved" indexes were all created as non-unique, regardless of original
- Fail during index creation left you without an index (drop and then create, with no rollback)
- Table was un-indexed during the move (index dropped and then created)
- Script re-created indexes without any "Included" columns, even if original index had them
To address these limitations, I rebuilt the process using that script as a starting point. The new script:
- Uses 90% fill rate by default, but if the original index had a different rate specified, it will use that
- Re-creates indexes as unique if the source index was unique
- Rollback problem resolved - new index is created with different name, old index is dropped, and then new index is renamed, all in a TRY-CATCH block
- Since the new index is created and then the old one dropped, table indexing remains "online" during the move
- Migrates "Included" columns in index
- Updated the script to use SYS views (breaks compatibility with SQL 2000, since SYS is 2005/2008/beyond only)
Download .SQL scripts (contains both Original and Modified scripts)
Finding unused tables in SQL Server 2005 and 2008
Recently, I was tasked with "cleaning up" a very large database on our network - it included hundreds of tables with cryptic names, and I wasn't able to tell which ones were still being used and which weren't. There are triggers for INSERT, UPDATE, and DELETE, but no trigger for SELECT, and that's what I wanted.
However, SQL Server 2005 and later provide something that's almost as good - the sys.dm_db_index_usage_stats system view. This view has table and index statistics for every table in the database and you can use it to determine when a table was last accessed. Though I initially thought this table only contained index stats, so would be useless against tables without indexes, that's not the case; it contains tables themselves as well, and calls them "HEAP" indexes. This way, you can see which tables are being scanned against often (a sign that a better set of indexes is needed), or which indexes aren't being accessed at all and can safely be removed.
Using this data, it's easy to determine which tables haven't been accessed since the server was last restarted:
WITH LastActivity (ObjectID, LastAction) AS
(
SELECT object_id AS TableName,
last_user_seek as LastAction
FROM sys.dm_db_index_usage_stats u
WHERE database_id = db_id(db_name())
UNION
SELECT object_id AS TableName,
last_user_scan as LastAction
FROM sys.dm_db_index_usage_stats u
WHERE database_id = db_id(db_name())
UNION
SELECT object_id AS TableName,
last_user_lookup as LastAction
FROM sys.dm_db_index_usage_stats u
WHERE database_id = db_id(db_name())
)
SELECT OBJECT_NAME(so.object_id) AS TableName,
MAX(la.LastAction) as LastSelect
FROM sys.objects so
LEFT
JOIN LastActivity la
ON so.object_id = la.ObjectID
WHERE so.type = 'U'
AND so.object_id > 100
GROUP BY OBJECT_NAME(so.object_id)
ORDER BY OBJECT_NAME(so.object_id)
Since the table is cleared when the SQL service restarts, this will only display the tables not accessed since the last time the server was restarted. Because of this, you'll need to ensure that the SQL Server has been running for sufficiently long before you rely on this query to see which tables aren't accessed by users.
Keep in mind that, even if the server has been running for months and a table is still in this list, it may not be safe to delete it. Some tables may be part of year-end or rare processes. This list should be used as a guide to help you figure out what's safe to delete, and you may even consider renaming objects for a while first, so that any processes that do end up relying on one of these tables can be easily corrected by renaming the objects back.
Moving a SQL Server database to another server on a schedule – without using replication
Recently, I had the need to copy a set of databases from a dozen remote servers to a central server, restore them, and have it happen automatically, with no intervention from me at all. Replication wouldn't work for the following reasons:
- Many tables didn't have primary keys, so merge replication was out (even though this was only one-way replication)
- The size of the databases (28GB in one instance) and the quality/speed of the WAN removed the log shipping option
- There's too much activity to consider any kind of live replication
Given our restrictions, we decided to go the following route. On the remote server, we set up a batch file that did the following:
- Use OSQL to back up the databases in question to a folder
- Run 7Zip from the command line to compress the backups into separate archives. For each auto-attaching later, each archive had the name we wanted it attached to the remote server with (for example, Site1ProdDB was backed up to Site1ProdDB.BAK, then compressed to Site1ProdDB.7z)
- Delete the BAK files
- Archives were renamed from *.7z to *.7zz (this is important - I'll explain why in the server part)
- Scripted FTP using Windows command line FTP tool to a folder on our central collection server
- Once the FTP was complete, rename the archives on the remote server back from *.7zz to *.7z
- Delete the local *.7zz files
That's it for the client - the BAT file was scheduled as a SQL Agent job so that we could kick it off remotely from any site we wanted, or so we could set them up on a schedule. Then, we put a BAT file on the server that did the following:
- Check folder for files that match *.7z
- For each one found, do the following:
- Extract it to a "Staging" folder
- Delete the 7z file for that archive
- Use OSQL to restore the file from the command line
- Use OSQL to run a script that changes the DB owner, adds some user permissions, and generally does some housework on the database
- Use an SMTP tool to send a email notice that the backup has been restored
- Repeat step 2 for every .7z file in the folder
- As a second step in the SQL Agent job, run "MoveLog.bat" (included below) to finish rotating the logs - it ensures that only logs with meaningful information are kept
The server BAT process can run as often as desired - in our case, we run it every 30 minutes, so the backup will be picked up and restored as soon as it's available. That's where the rename from the client side comes into play: If the files were named Database.7z, then the server process would attempt to pick them up while they're being uploaded via FTP, and shenanigans would ensue. By renaming them when they're done uploading, they become immediately available for restoring on the server side.
As I said before, I scheduled both the client (source) and the server (restore/destination) process as SQL Agent jobs - the Windows scheduler is too cumbersome to work with remotely, and kicking them off on demand was a pain. With the SQL Agent, they can be started on demand, and then I get an email notification as soon as they've been successfully restored.
I've attached the files below, and I welcome any feedback that you have or any improvements that can be made - I'm happy to give you credit and post a new version here. Specifically, I'm interested in any feedback about how to make this process more dynamic - I know BAT scripting supports FOR EACH and wildcards, but I was unable to make them work properly with OSQL, so I'd appreciate any input there. Enjoy!