Shaun J Stuart

Just another SQL Server weblog

Back in December, a couple of checks I wrote were included in Brent Ozar Unlimited's sp_Blitz script. I was chagrined to see that some people discovered some bugs in my code and submitted fixes. To be fair, one was a bug in SQL 2008 & 2008 R2 where DBCC DBINFO WITH TABLERESULTS returned the dbccLastKnownGood entry twice. (Microsoft has said they aren't going to fix this, unfortunately.) The other was an issue when systems were using the British date format. I guess this just goes to show how hard it is to test every possible use case to discover bugs.

Even though there were only problems with one of the three tests I submitted, I still felt the need to redeem myself and submit another check. I had recently read a blog post about monitoring the values of identity columns in tables to make sure you aren't approaching the maximum value for the data type used. Surprisingly, such a check wasn't already in sp_Blitz. I felt this was something I could tackle.

My first attempt worked, but it did so by cycling through all the tables in a database, finding any identity columns, and comparing the maximum value used to the maximum value the data type can accommodate (255 for TINYINT, 2147483648 for INT, etc.) I submitted this and the feedback from Brent was that he wasn't sure if this should be included because of the performance implications. Although I tested the code on all my servers without issue, Brent mentioned that some databases, especially those for SAP, may have 10,000 to 50,000 tables. Running code to look at each of those tables could have a severe impact on performance or, at the very least, greatly increase the time it took the script to complete. That's a valid point (and again goes to show how hard it is to test for all use cases). Apparently, he spoke to Kendra Little about this and she brought up a system table that both Brent and I didn't know about (or more likely, knew about, but forgot): sys.identity_columns. Using that, I can get the same information by querying one system table instead of each individual user table in the database.

So I re-wrote my script to use that and re-submitted it to sp_Blitz. It's in the queue for evaluation, so I don't know when (or if) it will be included, but I thought I post the test here. (And of course, once I had the name of this table, I discovered a Google search returns lots of scripts that provide the same functionality.)

A quick note: The script assumes the identity increment is positive and the identity values are positive. The script will issue a warning when the maximum value is more than 90% of the maximum value of the data type used.

  DECLARE @SchemaName sysname
  DECLARE @TableName sysname
  DECLARE @ColumnName sysname

  DECLARE @Name sysname
  DECLARE DBNameCursor CURSOR
  FOR
          SELECT    name
          FROM      sys.databases
          WHERE     source_database_id IS NULL	/* no database snapshots */
                    AND is_read_only = 0		/* no read-only dbs (log shipping) */
                    AND database_id <> 2		/* skip tempdb */
                    AND state = 0	/* online databases */
          ORDER BY  name;

  CREATE TABLE #IdentityCheck
         (
          PK INT IDENTITY(1, 1)
         ,DatabaseName sysname
         ,SchemaName sysname
         ,TableName sysname
         ,ColumnName sysname
         ,ColumnType VARCHAR(8)
         ,MaxValue BIGINT
         )

  OPEN DBNameCursor
  FETCH NEXT FROM DBNameCursor INTO @Name
  WHILE @@fetch_status = 0
        BEGIN

              EXEC('USE [' + @Name + '];  INSERT  INTO #IdentityCheck
              SELECT ''' + @Name + ''' as DatabaseName
              ,SCHEMA_NAME(o.SCHEMA_ID) AS SchemaName
              ,OBJECT_NAME(o.OBJECT_ID) AS TableName
              ,c.[name] AS ColumnName
              ,t.name AS ColumnType
              ,CAST(last_value AS bigint) AS MaxValue
              FROM    sys.identity_columns c
              ,sys.types t
              ,sys.objects o
              WHERE   c.is_identity = 1
              AND t.system_type_id = c.system_type_id
              AND o.object_id = c.object_id
              AND o.type = ''u''
              AND t.is_user_defined = 0 /* only look at system defined types */
              AND t.name IN (''int'', ''bigint'', ''smallint'', ''tinyint'')')
              FETCH NEXT FROM DBNameCursor INTO @Name
        END

  CLOSE DBNameCursor
  DEALLOCATE DBNameCursor

  SELECT    'Table [' + SchemaName + '].[' + TableName + '], column ['
            + ColumnName + '] in database [' + DatabaseName
            + '] is an identity column that has reached over 90% of the maximum value for that datatype.' AS Details
           ,'' AS ChangeRequirements
  FROM      #IdentityCheck
  WHERE     1 = CASE WHEN ColumnType = 'bigint'
                          AND (CONVERT(DECIMAL(19, 0), MaxValue)
                               / 9223372036854775807.0) > .90 THEN 1
                     WHEN ColumnType = 'int'
                          AND (CONVERT(DECIMAL(10, 0), MaxValue)
                               / 2147483648.0) > .90 THEN 1
                     WHEN ColumnType = 'smallint'
                          AND (CONVERT(DECIMAL(5, 0), MaxValue) / 32767.0) > .90
                     THEN 1
                     WHEN ColumnType = 'tinyint'
                          AND (CONVERT(DECIMAL(3, 0), MaxValue) / 255.0) > .90
                     THEN 1
                END

  DROP TABLE #IdentityCheck

So what do you do if you get this warning? Well, a quick fix that Michelle Ufford suggested, is to reseed your identity value to the lowest negative value the data type supports. For example, if your identity column is an integer, reseed the column to start at -2147483648 and increment by a positive number. (This assumes you started your initial identity value at zero and used a positive increment to begin with.)

One more note: you'll notice my script uses a cursor to cycle through the databases while many other scripts you find on the internet use ms_foreachdb. Either method works. I prefer the cursor because it's 100% supported. (For the cursors-give-sucky-performance crowd, the undocumented command uses a cursor behind the scenes anyway, so that's a moot point.) Ms_foreachdb is an unsupported command and can change at any time. There is also evidence that it can skip some databases and even completely lock up SQL 2005 servers in some situations. Given the issue I ran into above with the DBCC DBINFO command, which is also an unsupported command, and Microsoft's statement that they are not going to fix it, I am committed to not using any undocumented commands in any scripts I pan to re-use. I'll use them for any one-off work I might be doing, but for something that I want to save and re-use as part of my SQL toolkit, I will pass.

Share

My co-worker showed me a SQL Server requirements document that he found online somewhere and modified at a previous company. He would give this document to vendors who said they needed a SQL Server and they would need to select the appropriate answers before he would let them install their product.  I think this is a great idea and thought I'd share it.

SQL Server Host Requirements (OS, minimum RAM, Number of CPUs, VM support, etc.)

Version of SQL Server Supported

  • SQL 2012 SP2
  • SQL 2012 SP1
  • SQL 2012 RTM
  • SQL 2008 R2 SP2
  • SQL 2008 R2 SP1

Edition Of SQL Server Supported

  • Enterprise
  • Standard

32-bit vs. 64-bit

  • 32-bit
  • 64-bit

Exclusivity For The Hosted Database

  • Shared SQL Server Environment
  • Dedicated SQL Server required

Maximum Concurrent User Count

IOPS (I/O per second) Requirement

Initial Database Size - Please provide for each database, if multiple. Specify in MB or GB.

Projected Annual Database Growth Rate. Specify in MB or GB.

Special SAN Requirements, if any.

Dedicated Test, Development, and / or Training Environment Required (Include Details)

  • Auxiliary environments to be delete / removed post-go live
  • Auxiliary environments to remain accessible for duration of project use
  • Auxiliary environments will need to be recreated for future product upgrades / enhancements

Database Security

  • Single Windows (Active Directory) network login (connection pooling)
  • Active Directory gorup login
  • Individual or AD Group network login for each user
  • Single SQL login ID and password (Login and password must be customizable and not identical to other product installations at other clients)
  • Individual SQL login and password for each user

Maximum Level Of Permissions Required for Users / Administators / Service Account (Please provide explanation for any response greater than Database role, db_owner)

  • Database role, less than db_owner
  • Database role, db_owner
  • Aliased as dbo for database
  • Some level of server-level permissions less than system administrator (sa)
  • SA role membership
  • Local administrator rights on SQL Server host machine

Database Connection / Session Handling

  • Application maintains persisted connections / sessions to database
  • Application established and releases connections / sessions as requested during normal operation
  • Other

Vendor Access Required To Support Database Issues

  • Vendor requires local login rights to SQL Server
  • Vendor requires remote-connection via the application
  • Vendor requires VPN access to our network to connect to the database via SSMS
  • No remote support capabilities. Database backup will need to be sent via secure FTP to vendor for analysis
  • No remote support capabilities. Secure method of transferring database backup not available.

Application and Database Support Exists for the Following High Availability Architectures

  • Failover clustering
  • Mirroring
  • Log shipping
  • Replication

How Frequently Do You Certify SQL Server Patches, Service Packs, and Release Updates?

  • As released by Microsoft
  • Quarterly
  • Annually
  • Other

How Frequently Do You Issue Database Updates?

  • Quarterly
  • Annually
  • Other

Performance Tuning Requirements

  • Index tuning changes (adding or disabling indexes, etc.) must be submitted to vendor for approval
  • Index tuning changes (adding or disabling indexes, etc.) do not require vendor approval
  • Index changes by customer technical staff are not permitted

Data Retention / Archiving and Growth Management Support

  • Vendor does not support database archiving
  • Vendor has database archiving process in place or available
  • Database archiving solutions created by customer must be submitted to vendor for approval
  • Database archiving solutions created by customer do not require vendor approval
  • Vendor has tested and supports SQL Server data compression

Database Installation Process

  • TSQL scripts or database backup file to be submitted to customer DBA for installation
  • Installation accomplished via executable or other method run by vendor / customer on non-SQL Server host (e.g., application server or client workstation). Any additional permission requirements needed for installation should be noted here.
  • Other

Note any Non-Standard Collation, Server-Level, or Instance-Level Settings Required

Identify Tables with Sensitive Data (Personally Identifiable Information, Financial Data, Passwords, etc.)

Identify Any Custom SQL Agent Jobs, Proprietary Backup or Maintenance Processes, or Similar Items.

Vendor Technical Contact ____________________________
Vendor Technical Contact Email _______________________
Vendor Technical Contact Phone _______________________

I would be surprised if many vendors knew the answer to all these questions right off the bat. However, this questionnaire will at least bring these issues to the forefront where they can start being addressed in a pro-active manner, before the product is installed. It will also raise any red flags. (The application needs sa access on a shared SQL Server? I don't think so! Your maintenance plan shrinks the database weekly? I don't think so!)

 

Share

Pop Quiz, hotshot. A vendor installed a maintenance plan on one of your SQL Servers. It looks like this:

Once the free space rises above 10%, the plan is armed.

Once the free space rises above 10%, the plan is armed.

What do you do?

 

Share

An article on Yahoo Education recently ranked Database Administrator as the second fastest growing career through 2020, with a projected job growth of 31%. The piece lists a couple of reasons for this, most of which I agree with. Then I read the last line of the article: "Companies with large databases may prefer those with an MBA."

Are you serious?

Are you serious?

Database Administrators need an MBA to work with large databases?

Share

My phone rag at 4 AM last Sunday morning, which is never a good sign. It was a member of our IT team who was doing an upgrade to some software. He was concerned because a particular SQL statement had been running for 30 minutes so far and when this person performed the upgrade on the test system earlier in the week, this particular statement completed in a few minutes. Because this was a mission-critical system, he wanted me to take a look and see if I could tell why things were taking so long and to be sure everything was still ok.

My first step was to fire up SSMS and launch activity monitor. Sure enough, I saw the query being executed. There was no blocking going on, nor were there excessive waits. Occasionally, I would see an I/O wait, but they were on the order of 20 milliseconds and were somewhat rare. It looked to me like SQL was just chugging along.

My next step was to get the actual query being run. It was really, really basic:

DELETE  FROM amc_rd_ref_data_item
WHERE   reference_data_group_id = @rdg_id

I looked at the table, thinking about possible indexing issues, or maybe triggers. This is what I saw:

TableStructure

No triggers. The reference_data_group_id was column was the second field in the primary key, so that index couldn't be used. But each of the other two indexes had this as the first column, so the database engine could have used either one of them for this query. There were also no constraints. There were three foreign keys defined on this table, but the tables they linked too had a small number of rows. No cursors were involved. Data types were matched, so there were no implicit conversion issues. The table only had about 500,000 rows in it. In short, nothing was jumping out at me as to why this simple query should take so long to run.

I decided to look at the execution plan for the query. Bingo!

Click to embiggen

Click to embiggen

Wow. That's zoomed out so you can see the whole plan, but each of those vertical lines is a nested loop operator. It turns out there were more than 200 tables that referenced the table we were deleting from with foreign keys of their own.

SQL was working through this without issue, so I decided to just let the query continue. I made sure transaction log backups were being taken frequently and that the log drive had enough room for it to grow - I didn't want this thing to bomb out for any reason and have to start over. The query ended up taking just over an hour to complete and when it was done, the table was down to 9,000 rows, so there was a lot of deleting and looping going on.

If you have what looks to be a very simple query that seems to be taking an inordinately long time to run, in addition to checking for blocking, proper indexing, triggers, and the usual suspects, keep in mind foreign keys on other tables. View the execution plan to find out exactly what SQL Server is doing.

 

Share