Category Archives: Imported from BeyondRelational

These posts are imported from my old blog page: http://beyondrelational.com/modules/2/blogs/77/nakuls-blog.aspx

#0304 – SQL Server – Key constraints are implemented as Indexes


Today’s post is about a SQL Server design aspect that’s not intuitive, yet is quite logical when you think about it.



All Key Constraints in Microsoft SQL Server are implemented as Indexes.


What the above statement means is that whenever we create a key constraint on a table (primary/foreign or unique key), internally an index is created by Microsoft SQL Server. We can see the use of these indexes when executing queries against the table when we look at the execution plan. When we think about it, we create key constraints so that they help us in establishing referential and data integrity. These keys therefore help us in retrieving data efficiently – which is also the job of indexes on the table. Hence, it quite logical that SQL Server implements key constraints as indexes.


Demo


To demonstrate this point, let me take a small example. The script below creates a table within the AdventureWorks2012 database, and create primary, foreign and unique key constraints on the table (Do keep in mind that this particular table design is for demonstration purposes only).

USE AdventureWorks2012;
GO
–Safety Check
IF OBJECT_ID(‘dbo.KeysAsIndexTest’,’U’) IS NOT NULL
DROP TABLE dbo.KeysAsIndexTest;
GO

–Create Table
CREATE TABLE dbo.KeysAsIndexTest (RecordId INT NOT NULL,
EmployeeId INT NOT NULL,
RecordValue VARCHAR(50) NOT NULL
);

–Primary Key
ALTER TABLE dbo.KeysAsIndexTest
ADD CONSTRAINT pk_KeysAsIndexTest
PRIMARY KEY CLUSTERED (RecordId);
GO

–Foreign Key
ALTER TABLE dbo.KeysAsIndexTest
ADD CONSTRAINT fk_Employee_KeysAsIndexTest
FOREIGN KEY (EmployeeId)
REFERENCES HumanResources.Employee (BusinessEntityId);
GO

–Unique Key
ALTER TABLE dbo.KeysAsIndexTest
ADD CONSTRAINT uk_KeysAsIndexTest
UNIQUE (RecordValue);
GO


After creating the table and the constraints, let us query the sys.indexes catalog view to see how many indexes have been created against the table:

USE AdventureWorks2012;
GO
SELECT OBJECT_NAME(si.object_id) AS ObjectName,
si.name AS IndexName,
si.type_desc AS IndexType,
si.is_unique AS IsUnique,
si.is_primary_key AS IsPrimaryKey,
si.is_unique_constraint AS IsUniqueConstraint,
si.index_id AS IndexId
FROM sys.indexes AS si
WHERE si.object_id = OBJECT_ID(‘dbo.KeysAsIndexTest’,’U’);
GO

The output of the above query is shown in the screenshot below:


image


As you can see, both the primary key and the unique key are implemented as indexes on the test table – dbo.KeysAsIndexTest.


However, where’s the index for the Foreign key?


The answer is quite simple and lies in the definition of a foreign key. Per Books On Line, “FOREIGN KEY constraints can reference only columns in PRIMARY KEY or UNIQUE constraints in the referenced table or in a UNIQUE INDEX on the referenced table.” This means that if a foreign key constraint was successfully defined, it will be referring to a primary key or a unique key on the referenced table which in-turn would have been implemented as indexes.


Foreign keys help ensure that if a value exists in the table, it must exist first in the referenced table column. Hence, the real index and lookup requirements are on the referenced table and not on the referencing table. So, as far as foreign keys are concerned, SQL Server goes the smart way by not creating what would be a duplicate index.


Concluding…


To me, it is an interesting aspect on the overall SQL Server design – it tells me how SQL Server works and how to get the most advantage by defining the correct keys. Because key constraints are implemented as indexes, they should also be defined to be the most selective in a join or a real-world scenario – an design point which many designs consider when defining primary and foreign keys, but miss when creating unique keys.


Further Reading



  • Clustered Indexes may not be unique, and unique indexes may not be clustered [Link]

  • Primary Keys without clustered indexes [Link]

  • Create Table [Books On Line, Link]

  • sys.indexes Catalog View [Books On Line, Link]

Until we meet next time,


Be courteous. Drive responsibly.

#0303 – SQL Server – TRUSTWORTHY property is reset to OFF by database attach or restore activity


The databases that I work with use a considerable amount of SQL-CLR functionality implemented via assemblies registered on the SQL Server instance. Recently, as we were closing up the day, we received a phone call with the news that one of our databases was down with a permissions error similar to the one shown below.

An error occurred in the Microsoft .NET Framework while trying to load assembly id nnnnn. The server may be running out of resources, or the assembly may not be trusted with PERMISSION_SET = EXTERNAL_ACCESS or UNSAFE. Run the query again, or check documentation to see how to solve the assembly trust issues.

Over the phone itself, we were immediately able to help the support teams resolve the issue by setting the TRUSTWORTHY property of our database to ON (as was required by our design). But, this incident triggered a bigger question:

What caused the TRUSTWORTHY property to be set to OFF automatically?

Of course, none of the involved administrators admitted to anything being changed on the server or on the SQL Server instance. Upon research and hanging around on the #sqlhelp community on Twitter, I was able to confirm that the TRUSTWORTHY property is reset to OFF by the following operations:

  • Restoring a database
  • Attaching a database to a SQL Server instance

Provided below is a very simple script that does the following:

  1. Creates a database
  2. Sets the TRUSTWORTHY property to ON
  3. Backup the database
  4. Detach the database
  5. Attach the database
  6. Drop the database so that it can be restored from the backup taken earlier

After the attach and the restore process, I query the sys.databases catalog view to check the value of the is_trustworthy_on column.

--Safety Check
USE master;
GO
IF EXISTS (SELECT * FROM sys.databases AS sd WHERE sd.name = 'TrustCheck')
BEGIN
    ALTER DATABASE TrustCheck SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE TrustCheck;
END
GO

--Create the database
CREATE DATABASE TrustCheck;
GO

ALTER DATABASE TrustCheck SET TRUSTWORTHY ON;
GO

--Check the database properties
SELECT 'Check after Database Creation',
       sd.name,
       sd.database_id,
       sd.create_date,
       sd.is_trustworthy_on
FROM sys.databases AS sd 
WHERE sd.name = 'TrustCheck';
GO

--Backup the database
BACKUP DATABASE TrustCheck 
TO DISK = 'C:Database FilesTrustCheck.bak' 
WITH INIT, 
     COMPRESSION;
GO

--Detach the database
EXEC sp_detach_db @dbname = 'TrustCheck',
                  @skipchecks = 'false';
GO

--Ensure that the database is no longer "available" on this instance
SELECT 'Check after Detach',
       sd.name,
       sd.database_id,
       sd.create_date,
       sd.is_trustworthy_on
FROM sys.databases AS sd 
WHERE sd.name = 'TrustCheck';
GO

USE master;
GO
--Attach the database
CREATE DATABASE TrustCheck
    ON  PRIMARY ( NAME = N'TrustCheck_Data', 
              FILENAME = N'C:Program FilesMicrosoft SQL ServerMSSQL11.SQL2K12MSSQLDATATrustCheck.mdf')
    FOR ATTACH
GO

--Check the database properties
SELECT 'Check after Database Attach',
       sd.name,
       sd.database_id,
       sd.create_date,
       sd.is_trustworthy_on
FROM sys.databases AS sd 
WHERE sd.name = 'TrustCheck';
GO

--Now drop the database explicitly to see what happens when we restore the database
USE master;
GO
IF EXISTS (SELECT * FROM sys.databases AS sd WHERE sd.name = 'TrustCheck')
BEGIN
    ALTER DATABASE TrustCheck SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE TrustCheck;
END
GO


--Restore the database
RESTORE DATABASE TrustCheck
FROM DISK = 'C:Database FilesTrustCheck.bak'
WITH RECOVERY;

--Check the database properties
SELECT 'Check after Database Restore',
       sd.name,
       sd.database_id,
       sd.create_date,
       sd.is_trustworthy_on
FROM sys.databases AS sd 
WHERE sd.name = 'TrustCheck';
GO

--Cleanup
USE master;
GO
IF EXISTS (SELECT * FROM sys.databases AS sd WHERE sd.name = 'TrustCheck')
BEGIN
    ALTER DATABASE TrustCheck SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE TrustCheck;
END
GO

The output of the query above is shown below.

image

Conclusion

If the TRUSTWORTHY setting was mysteriously reset to OFF, check whether the database was recently restored and/or attached.

Further Reading

  • Find DMVs in SSMS/Object Explorer [Link]
  • Catalog Views [Books on Line Link]
  • sys.databases Catalog View [Books On Line Link]
  • ALTER DATABASE {Books On Line Link]

Until we meet next time,

Be courteous. Drive responsibly.

#0302 – SQL Server – Myths – ORDER BY does not impact performance


One of the most common mistakes that I see developers do is the use the ORDER BY clause in every stored procedure – irrespective of whether the application can sort the data independently or not. There have been many posts written on this topic, and I wanted to do my bit to spread awareness by busting this myth.

The truth is that the ORDER BY clause consumes precious server resources. As we know, the ORDER BY clause is applied after the select. What I mean by this is that once the result set of a query is generated, SQL Server will then perform the sort. In order to do the sort operation, SQL Server will need to use some temporary disk space from the tempdb and also consume processor time. Just imagine the amount of resources being consume to service hundreds of concurrent connections – each requiring a sorted output.

Here’s a simple example, executed by keeping the “Actual Execution plan” ON:

USE AdventureWorks2012;
GO
--Query without ORDER BY
SELECT hredh.BusinessEntityID,
       hre.JobTitle,
       hredh.DepartmentID,
       hrd.Name,
       hredh.StartDate,
       hredh.EndDate,
       hredh.ShiftID,
       hrs.Name
FROM HumanResources.Shift AS hrs 
INNER JOIN HumanResources.EmployeeDepartmentHistory AS hredh ON hredh.ShiftID = hrs.ShiftID
INNER JOIN HumanResources.Employee AS hre ON hredh.BusinessEntityID = hre.BusinessEntityID
INNER JOIN HumanResources.Department AS hrd ON hredh.DepartmentID = hrd.DepartmentID;
GO

USE AdventureWorks2012;
GO
--Query with ORDER BY
SELECT hredh.BusinessEntityID,
       hre.JobTitle,
       hredh.DepartmentID,
       hrd.Name,
       hredh.StartDate,
       hredh.EndDate,
       hredh.ShiftID,
       hrs.Name
FROM HumanResources.Shift AS hrs 
INNER JOIN HumanResources.EmployeeDepartmentHistory AS hredh ON hredh.ShiftID = hrs.ShiftID
INNER JOIN HumanResources.Employee AS hre ON hredh.BusinessEntityID = hre.BusinessEntityID
INNER JOIN HumanResources.Department AS hrd ON hredh.DepartmentID = hrd.DepartmentID
ORDER BY hredh.DepartmentID;
GO

image

As can be seen from the execution plan above, the query with the ORDER BY consumes more processing time (it increased from 46% to 54% of the batch) due to the introduction of the SORT operator.

Conclusion

Most applications only need pre-sorted output in about 20% of the cases. In all other cases, the sort operations should be avoided – SQL Server works best when processing the entire set of records, so why sort them?

Until we meet next time,

Be courteous. Drive responsibly.

#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.