Category Archives: #SQLServer

All about Microsoft SQL Server

#0301 – SQL Server – SSMS – Identify disabled indexes


As I was going through the forums and the #sqlhelp Twitter feed yesterday, I came across a very interesting question:

Is there a way to verify whether an index is disabled in SSMS?

The short and simple answer is: No direct indicator exists in the SQL Server Management Studio (SSMS) to indicate whether an index is enabled or disabled.

To cross-check, I used the disabled index scripts (refer Further Reading Section below) to disable a non-clustered and the clustered index on the Sales.Store table of the AdventureWorks2012 sample database.

The Index Properties window confirms that the indexes are indeed disabled.
image
image
This is what the Object Explorer looks like:

As can be seen from the screenshot below, the icons for the indexes are the same – before and after the indexes were disabled.

image
Even the Object Explorer Details window does not show any indication and/or column to indicate that the indexes are disabled
image

This is one of the many cases where T-SQL scripts prove to be way more powerful than the SSMS UI. While I do not know the real reason why this particular discrepancy exists, I believe it is for performance reasons (the more meta-data that is accessed and refreshed on the UI, the more resources it would take resulting into an overall decline in performance).

Further Reading

  • ALTER INDEX – Enable & Disable Indexes [Link]
  • Disable Clustered Index and Data Insert [Link]
  • Disable Index & Update Statistics [Link]

(*All the above mentioned links are from Pinal Dave, a.k.a. “SQLAuthority” [B|T]’s blog)

Until we meet next time,

Be courteous. Drive responsibly.

#0300 – SQL Server – Table Hints – WITH (NOLOCK) does not propagate through computed column expressions


There are many myths related to performance tuning in SQL Server. One of the top-ranking myths is that using the WITH (NOLOCK) table hint is a silver bullet to 80% of the performance problems that an application might be facing. However, the WITH (NOLOCK) may not even be in effect in all situations.


If a table contains a computed column, then the table hints on the table do not propagate through to the functions and expressions used to define that computed column. Let’s study this statement with the help of an example.


Demo


For the purposes of this demo, let us first create a table with a computed column. We will be using the [dbo].[ufnGetProductDealerPrice] scalar function available in the AdventureWorks2012 sample database to populate the computed column used in this demo.

USE AdventureWorks2012;
GO
SET NOCOUNT ON;

IF OBJECT_ID(‘dbo.ProductDealerInformationByOrder’,’U’) IS NOT NULL
DROP TABLE dbo.ProductDealerInformationByOrder;
GO

CREATE TABLE dbo.ProductDealerInformationByOrder
(ProductId INT,
OrderDate DATETIME,
DealerListPrice AS (dbo.[ufnGetProductDealerPrice](ProductId, OrderDate))
);
GO

–Populate some test data
INSERT INTO dbo.ProductDealerInformationByOrder (ProductId, OrderDate)
SELECT sod.ProductID,
soh.OrderDate
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderID = sod.SalesOrderID
WHERE soh.SalesOrderID = 51721;
GO


The scenario:


The scalar function – [dbo].[ufnGetProductDealerPrice] references two tables:



  • Production.Product
  • Production.ProductListPriceHistory

To check whether the NOLOCK hint is propagated through the computed column and affects the tables referred inside the function, we will be updating some of the values in the table: Production.ProductListPriceHistory  inside a transaction and then attempting to select data from our test table – dbo.ProductDealerInformationByOrder by using the WITH (NOLOCK) table hint. If the table hint does propagate through the scalar function to the Production.ProductListPriceHistory table, then the query should return some data (it would be uncommitted data).


Step 01: Run the update to Production.ProductListPriceHistory table


The script below performs the update to the Production.ProductListPriceHistory and lists out all the locks that have been acquired by the transaction. Notice that we have neither committed nor rolled back the transaction, so the locks continue to remain in effect.

USE AdventureWorks2012;
GO
— Note the session number!
BEGIN TRANSACTION LockProductListPriceHistory
UPDATE plph
SET plph.ListPrice = (plph.ListPrice + 10)
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderID = sod.SalesOrderID
INNER JOIN Production.ProductListPriceHistory AS plph ON sod.ProductID = plph.ProductID
WHERE soh.SalesOrderID = 51721;

–Check the nature of the locks applied
SELECT sdtl.request_type AS LockRequeust,
sdtl.request_status AS LockRequestStatus,
sdtl.request_mode AS LockMode,
sdtl.resource_type AS ObjectType,
DB_NAME(sdtl.resource_database_id) AS DatabaseName,
SCHEMA_NAME(so.schema_id) AS SchemaName,
so.name AS ObjectName,
sdtl.request_session_id AS SessionNumber
FROM sys.dm_tran_locks AS sdtl
INNER JOIN sys.objects AS so ON sdtl.resource_associated_entity_id = so.object_id;
–ROLLBACK TRANSACTION LockProductListPriceHistory


The output on my test server is shown below.


image


Step 02: Select Data from test table


The next step now is to select data from our test table, from a new query editor window in SSMS (so that we begin a new session).

/* Run this in a new SSMS query editor window. */
USE AdventureWorks2012;
GO
BEGIN TRANSACTION SelectWithNoLock
;WITH ProductInfoCTE (ProductId, OrderDate, DealerListPrice)
AS (SELECT pdio.ProductId,
pdio.OrderDate,
pdio.DealerListPrice
FROM dbo.ProductDealerInformationByOrder AS pdio WITH (NOLOCK)
)
SELECT DISTINCT sdtl.request_type AS LockRequest,
sdtl.request_status AS LockRequestStatus,
sdtl.request_mode AS LockMode,
sdtl.resource_type AS ObjecType,
DB_NAME(sdtl.resource_database_id) AS DatabaseName,
SCHEMA_NAME(so.schema_id) AS SchemaName,
so.name AS ObjectName,
sdtl.request_session_id AS SessionNumber,
pcte.ProductId,
pcte.OrderDate,
pcte.DealerListPrice
FROM sys.dm_tran_locks AS sdtl
INNER JOIN sys.objects AS so ON sdtl.resource_associated_entity_id = so.object_id
CROSS JOIN ProductInfoCTE AS pcte;
ROLLBACK TRANSACTION SelectWithNoLock
GO

What we observe is that the query continues to execute without any results being returned (Note down the session Id, in this case, 55):


image


Step 03: Examine the locks acquired


Clearly, the query is being locked by another process or task. Hence, we will open up another query editor window in SSMS and execute the query provided below:

/* Run this in a new SSMS query editor window (window #3). */
USE AdventureWorks2012;
GO
SELECT sdtl.request_type AS LockRequeust,
sdtl.request_status AS LockRequestStatus,
sdtl.request_mode AS LockMode,
sdtl.resource_type AS ObjectType,
DB_NAME(sdtl.resource_database_id) AS DatabaseName,
SCHEMA_NAME(so.schema_id) AS SchemaName,
so.name AS ObjectName,
sdtl.request_session_id AS SessionNumber,
sr.blocking_session_id AS BlockingSession
FROM sys.dm_tran_locks AS sdtl
INNER JOIN sys.objects AS so ON sdtl.resource_associated_entity_id = so.object_id
LEFT OUTER JOIN sys.dm_exec_requests AS sr ON sdtl.request_session_id = sr.session_id
ORDER BY sr.session_id;
GO

Here is the output from my test server:


image


As can be seen from the screenshot, we can see that the WITH (NOLOCK) query from the select statement from session #55 has requested Sch-S (Schema stability) locks on the dbo.ProductDealerInformationByOrder and the scalar function – dbo.ufnGetProductDealerPrice  indicating that the hint is in effect for the objects directly referenced on the query.


However, it is requesting IS (Intent Shared) locks on the Production.Product and Production.ProductListPriceHistory tables. Unfortunately, the Production.ProductListPriceHistory is being locked by an IX (Intent Exclusive) lock from the session #52 thereby blocking session #55.


Running the ROLLBACK TRANSACTION statement in session #52 unblocks session #55 and results are finally returned.


Hence proving that,



If a table has computed columns that are computed by functions and expressions that refer other objects, the table hints on the parent table are not propagated to the objects referred by these functions and expressions.


Further Reading



  • Myths – Using WITH (NOLOCK) also works on INSERT/UPDATE/DELETE statements [Link]
  • Books-On-Line/MSDN: Table Hints in SQL Server [Link]
  • Pinal Dave, a.k.a. “SQLAuthority” (B|T): What Kind of Lock WITH (NOLOCK) Hint Takes on Object? [Link]

Until we meet next time,


Be courteous. Drive responsibly.

#0299 – SQL Server – Myths – Using WITH (NOLOCK) also works on INSERT/UPDATE/DELETE statements


Today’s post was prompted by a recent code review experience. I was called upon to code review a data cleanup script. Coming directly to the interesting part, I noticed that the developer had put the WITH (NOLOCK) table hint when consuming the tables in an UPDATE statement. The resulting conversation is transcribed below:


Me: We will need to remove the WITH (NOLOCK) table hint from the UPDATE statements in this script.


Developer: Why? Isn’t the WITH (NOLOCK) supposed to acquire no locks on the database?


Me: That’s not true. For the SQL Server to create a query plan and execute it, SQL Server will have to request at least a Schema stability (Sch-S) lock. You can read about this from Pinal Dave, a.k.a. “SQLAuthority” (B|T)’s post on “What Kind of Lock WITH (NOLOCK) Hint Takes on Object?


Developer: Okay, I get it now. So, we can use to reduce the locking in our database with the WITH (NOLOCK) statement. Wouldn’t we want to reduce locking as much as possible always?


Me: Yes, we may want to keep locks to a minimum, but that’s only during SELECT statements. This is because systems are typically 80% read, 20% write systems. An update would require an exclusive lock, and hence the WITH (NOLOCK) would be ignored by SQL Server. Let me show you an example:


Below are two UPDATE queries – one which uses a NOLOCK statement, and the other which does not. To allow us to track the nature of the locks acquired, I have wrapped them inside of a transaction.

USE AdventureWorks2012;
GO
–Check the locks acquired during a normal UPDATE
BEGIN TRANSACTION NormalUpdate
UPDATE hredh
SET hredh.ShiftID = 2
FROM HumanResources.EmployeeDepartmentHistory AS hredh
WHERE hredh.BusinessEntityID = 215;

–Check the nature of the locks applied
SELECT sdtl.request_type,
sdtl.request_status,
sdtl.request_mode,
sdtl.resource_type,
DB_NAME(sdtl.resource_database_id) AS DatabaseName
FROM sys.dm_tran_locks AS sdtl;
ROLLBACK TRANSACTION NormalUpdate
GO

USE AdventureWorks2012;
GO
–Check the locks acquired during an UPDATE WITH (NOLOCK)
BEGIN TRANSACTION UpdateWithNolock
UPDATE hredh
SET hredh.ShiftID = 2
FROM HumanResources.EmployeeDepartmentHistory AS hredh WITH (NOLOCK)
WHERE hredh.BusinessEntityID = 215;

–Check the nature of the locks applied
SELECT sdtl.request_type,
sdtl.request_status,
sdtl.request_mode,
sdtl.resource_type,
DB_NAME(sdtl.resource_database_id) AS DatabaseName
FROM sys.dm_tran_locks AS sdtl;
ROLLBACK TRANSACTION UpdateWithNolock
GO


Comparing the result sets of both these queries shows us that the same number and nature of locks were acquired by SQL Server for both queries during the UPDATE.











































































































request_type request_status Qry1
request_mode
Qry2
request_mode
Qry1
resource_type
Qry2
resource_type
DatabaseName
LOCK GRANT S S DATABASE DATABASE AdventureWorks2012
LOCK GRANT S S DATABASE DATABASE AdventureWorks2012
LOCK GRANT X X KEY KEY AdventureWorks2012
LOCK GRANT IX IX PAGE PAGE AdventureWorks2012
LOCK GRANT IX IX PAGE PAGE AdventureWorks2012
LOCK GRANT X X KEY KEY AdventureWorks2012
LOCK GRANT X X KEY KEY AdventureWorks2012
LOCK GRANT X X KEY KEY AdventureWorks2012
LOCK GRANT X X KEY KEY AdventureWorks2012
LOCK GRANT IX IX PAGE PAGE AdventureWorks2012
LOCK GRANT X X KEY KEY AdventureWorks2012
LOCK GRANT IX IX OBJECT OBJECT AdventureWorks2012

In fact, the official MSDN/Books-On-Line page for the UPDATE statement [Link] clearly states:



“NOLOCK and READUNCOMMITTED are not allowed”


If you do specify the WITH (NOLOCK) table hint, it is conveniently ignored by the database engine.


Developer: I guess the understanding of the entire team was incorrect. Thank-you, Nakul for explaining the detailed reason behind the code review comment.


Me: You are most welcome! Please note that Microsoft has already marked the use of NOLOCK in INSERT, UPDATE and DELETE statements as a deprecated feature. Hence, while today the database engine ignores the hint, it will produce an error in one of the future versions.


Further Reading:



  • Pinal Dave, a.k.a. “SQLAuthority” (B|T): What Kind of Lock WITH (NOLOCK) Hint Takes on Object? [Link]
  • Books-On-Line/MSDN: Table Hints in SQL Server [Link]
  • Books-On-Line/MSDN: Update Statement [Link]

Important Note


[Added: 10/01/2013, 10:15AM IST]


Please note that using WITH (NOLOCK) on the target table in a complex UPDATE statement may cause corruption in the non-clustered indexes in SQL Server 2008. Please refer https://support.microsoft.com/kb/2878968/en-us for details. Thank-you, Chintak for drawing attention to this KB article.


Until we meet next time,


Be courteous. Drive responsibly.


[Edit: 09/30/2013, 10:55AM IST – A schema-stability lock is abbreviated as Sch-S, not Sch-M.]

#0298 – SQL Server – complex CHECK constraints


We were recently working on an implementation and had to introduce a couple of CHECK constraints to maintain logical integrity of the database with respect to the business rules. As we were going through the table design, one of my colleagues asked a very interesting question:

Most of our constraints are quite simple, mostly range checks which involve a maximum of two columns. Is it possible to have a complex CHECK constraint which involves more than two columns?

The answer, quite simply is Yes! CHECK constraints can be complex, provided they continue to be an expression that evaluates to a Boolean value.

Demo

I will take a sample table derived from my post earlier in the week:

USE tempdb ;
GO
--Safety Check
IF OBJECT_ID('dbo.ComplexCheckConstraint', 'U') IS NOT NULL 
    DROP TABLE dbo.ComplexCheckConstraint ;
GO

--Create the temporary table
CREATE TABLE dbo.ComplexCheckConstraint
    (
      StartDate DATE,
      StartTime TIME,
      EndDate DATE,
      EndTime TIME,
    ) ;
GO

Now, let us create a complex CHECK constraint on the table:

USE tempdb;
GO
--Add the complex constraint
ALTER TABLE dbo.ComplexCheckConstraint
    ADD CONSTRAINT chk_EndDate CHECK ( (EndDate > StartDate) 
                                       OR (EndDate = StartDate AND 
                                           EndTime > StartTime) 
                                     );
GO

Note that we have five operators (3 comparison operators and 2 logical operators) and four columns (EndDate, StartDate, EndTime and StartTime). To check if the constraint works or not, let us attempt to insert some invalid data into the test table:

USE tempdb;
GO
--Attempt to insert some invalid data
INSERT  INTO dbo.ComplexCheckConstraint
        (
          StartDate,
          StartTime,
          EndDate,
          EndTime
        )
VALUES  (
          '2013-09-19',
          '08:00',
          '2013-09-19',
          '07:00'
        ) ;
GO

We get the following error, proving that the complex table constraint was created and is in effect.

Msg 547, Level 16, State 0, Line 2

The INSERT statement conflicted with the CHECK constraint "chk_EndDate". The conflict occurred in database "tempdb", table "dbo.ComplexCheckConstraint".

The statement has been terminated.

Attempting to insert valid data works fine and does not produce a violation.

Summary

CHECK constraints help to maintain logical consistencies in the database. They can help validate data with respect to a given pattern and also help in ensuring that the data in a column is well within the acceptable limits as defined by the business – irrespective of the complexity of the requirement.

Further Reading:

  • Using Regular Expressions with CHECK constraints [Link]
  • Defining CHECK constraints on computed columns [Link]

Until we meet next time,

Be courteous. Drive responsibly.

#0297 – SQL Server – Defining CHECK constraints on computed columns


Most enterprise applications have something a module similar to a calendar or a planner which tracks information like the start and end date/times of a particular task and/or activity. Over the years, I have seen logical data inconsistencies (especially if the application has been around for a while) wherein the end date/time is sometime earlier than the start end/time. For example, a task may start on September 23, 2013 10AM and finish on September 23, 2013 9AM – which clearly, is an issue.

If these systems were re-engineered after the advent of SQL Server 2008, the date/time value is actually stored in separate fields – a DATE column and a TIME column and a computed column is available that contains the date/time representation. Here’s an example:

USE tempdb ;
GO
--Safety Check
IF OBJECT_ID('dbo.ComputedColumnConstraintCheck', 'U') IS NOT NULL 
    DROP TABLE dbo.ComputedColumnConstraintCheck ;
GO

--Create the temporary table
CREATE TABLE dbo.ComputedColumnConstraintCheck
    (
      StartDate DATE,
      StartTime TIME,
      StartDateTime AS ( CAST(StartDate AS DATETIME)
                         + CAST(StartTime AS DATETIME) ),
      EndDate DATE,
      EndTime TIME,
      EndDateTime AS ( CAST(EndDate AS DATETIME) 
                       + CAST(EndTime AS DATETIME) )
    ) ;
GO

Normally, the solution to prevent logical data inconsistencies is to have a CHECK constraint on the column. But, can a CHECK constraint be defined on a computed column?

Let us attempt to create the required CHECK constraint for our example:

USE tempdb ;
GO
--Attempt to add the Check Constraint    
ALTER TABLE dbo.ComputedColumnConstraintCheck
ADD CONSTRAINT chk_EndDateTime CHECK ( EndDateTime > StartDateTime ) ;
GO

The following error message is encountered and the constraint is not created when we attempt to execute the SQL statement provided above:

Msg 1764, Level 16, State 1, Line 2

Computed Column ‘StartDateTime’ in table ‘ComputedColumnConstraintCheck’ is invalid for use in ‘CHECK CONSTRAINT’ because it is not persisted.

Msg 1750, Level 16, State 0, Line 2

Could not create constraint. See previous errors.

The first error message is when the error actually occurred and hence that is the message of interest. The message quite clearly tells us that the CHECK constraint definition was invalid because the underlying column was not persisted. Because constraints are in-turn implemented as indexes, it makes sense to have the column as computed.

So, let us convert the column to a computed column. We have at least two methods to convert a non-persisted computed column to a persisted one:

  • Create an index using the computed column
  • Drop the column and re-create it as persisted

For the purposes of this demo, we will be using the 2nd method (drop-and-recreate the column). Once the persisted columns are created, we will attempt to create the CHECK constraint again:

USE tempdb;
GO
--Now, alter the computed column to make it persisted
ALTER TABLE dbo.ComputedColumnConstraintCheck
    DROP COLUMN StartDateTime;
ALTER TABLE dbo.ComputedColumnConstraintCheck
    ADD StartDateTime AS ( CAST(StartDate AS DATETIME) 
                           + CAST(StartTime AS DATETIME) ) PERSISTED;
GO

ALTER TABLE dbo.ComputedColumnConstraintCheck
    DROP COLUMN EndDateTime;
ALTER TABLE dbo.ComputedColumnConstraintCheck
    ADD EndDateTime AS ( CAST(EndDate AS DATETIME) 
                         + CAST(EndTime AS DATETIME) ) PERSISTED;
GO

--Attempt to add the Check Constraint    
ALTER TABLE dbo.ComputedColumnConstraintCheck
ADD CONSTRAINT chk_EndDateTime CHECK ( EndDateTime > StartDateTime ) ;
GO

Now that the constraint has been created, let us attempt to insert some data which violates the constraints.

USE tempdb;
GO
--Attempt to insert some invalid data
INSERT  INTO dbo.ComputedColumnConstraintCheck
        (
          StartDate,
          StartTime,
          EndDate,
          EndTime
        )
VALUES  (
          '2013-09-19',
          '08:00',
          '2013-09-19',
          '07:00'
        ) ;
GO

We immediately encounter the following error and the INSERT fails:

Msg 547, Level 16, State 0, Line 2

The INSERT statement conflicted with the CHECK constraint "chk_EndDateTime". The conflict occurred in database "tempdb", table "dbo.ComputedColumnConstraintCheck".

The statement has been terminated.

Summary

CHECK constraints help to maintain logical consistencies in the database. They can help validate data with respect to a given pattern and also help in ensuring that the data in a column is well within the acceptable limits as defined by the business. Having the ability to enforce business validations via the use of CHECK constraints on computed columns is an added advantage in making the database design robust.

Further Reading:

  • Can a Computed Column be used as a Primary Key? [Link]
  • Using Regular Expressions with CHECK constraints [Link]

Until we meet next time,

Be courteous. Drive responsibly.