Showing posts with label locks. Show all posts
Showing posts with label locks. Show all posts

Friday, August 19, 2011

Understanding the SQL Server NOLOCK hint

Problem

I see the use of the NOLOCK hint in existing code for my stored procedures and I am not exactly sure if this is helpful or not.  It seems like this has been a practice that was put in place and now is throughout all of the code wherever there are SELECT statements.  Can you explain the what NOLOCK does and whether this is a good practice or not?

Solution

It seems that in some SQL Server shops the use of the NOLOCK (aka READUNCOMMITED) hint is used throughout the application.  In this tip we take a closer look at how this works and what the issues maybe when using NOLOCK.

Example

Let's walk through some simple examples to see how this works. (These queries are run against the AdventureWorks database.)
Here is a query that returns all of the data from the Person.Contact table. If I run this query I can see there is only one record that has a Suffix value for ContactID = 12.
SELECT * FROM Person.Contact WHERE ContactID < 20

Let's say another user runs the below query in a transaction.  The query completes and updates the records, but it is not yet committed to the database so the records are locked.
-- run in query window 1
BEGIN TRAN
UPDATE Person.Contact SET Suffix = 'B' WHERE ContactID < 20
-- ROLLBACK or COMMIT
If I run the same query from above again you will notice that it never completes, because the UPDATE has not yet been committed.
-- run in query window 2
SELECT * FROM Person.Contact WHERE ContactID < 20
If I run sp_who2 I can see that the SELECT statement is being blocked.  I will need to either cancel this query or COMMIT or ROLLBACK the query in window one for this to complete.  For this example I am going to cancel the SELECT query.

To get around the locked records, I can use the NOLOCK hint as shown below and the query will complete even though the query in window 1 is still running and has not been committed or rolled back.
-- run in query window 2
SELECT * FROM Person.Contact WITH (NOLOCK) WHERE ContactID < 20
If you notice below the Suffix column now has "B" for all records.  This is because the UPDATE in window 1 updated these records.  Even though that transaction has not been committed, since we are using the NOLOCK hint SQL Server ignores the locks and returns the data.  If the UPDATE is rolled back the data will revert back to what it looked like before, so this is considered a Dirty Read because this data may or may not exist depending on the final outcome in query window 1.

If I rollback the UPDATE using the ROLLBACK command and rerun the SELECT query we can see the Suffix is back to what it looked like before.
-- run in query window 1
ROLLBACK

-- run in query window 2
SELECT * FROM Person.Contact WITH (NOLOCK) WHERE ContactID < 20
-- or
SELECT * FROM Person.Contact WHERE ContactID < 20

So the issue with using the NOLOCK hint is that there is the possibility of reading data that has been changed, but not yet committed to the database.  If you are running reports and do not care if the data might be off then this is not an issue, but if you are creating transactions where the data needs to be in a consistent state you can see how the NOLOCK hint could return false data.

Locks

So what kind of locking is used when the NOLOCK hint is used.
If we run our SELECT without NOLOCK we can see the locks that are taken if we use sp_lock.  (To get the lock information I ran sp_lock in another query window while this was running.)
SELECT * FROM Person.Contact WHERE ContactID < 20

If we do the same for our SELECT with the NOLOCK we can see these locks.
SELECT * FROM Person.Contact WITH (NOLOCK) WHERE ContactID < 20


The differences are that there is a "S" shared access lock that is put on the page (PAG) that we are reading for the first 19 rows of data in the table when we don't use NOLOCK.  Also, we are getting a Sch-S lock versus an IS lock for the table (TAB). 
So another thing to point out is that even when you just SELECT data SQL Server still creates a lock to make sure the data is consistent.
These are the lock types and the lock modes that are used for the above two queries.

Lock Types

  • MD - metadata lock
  • DB - database lock
  • TAB - table lock
  • PAG - page lock

Mode

  • S - Shared access
  • Sch-S - Schema stability makes sure the schema is not changed while object is in use
  • IS - Intent shared indicates intention to use S locks

READUNCOMMITED

The NOLOCK hint is the same as the READUNCOMMITED hint and can be used as follows with the same results.
SELECT * FROM Person.Contact WITH (READUNCOMMITTED)

SELECT statements only

The NOLOCK and READUNCOMMITED hints are only allowed with SELECT statements. If we try to use this for an UPDATE, DELETE or INSERT we will get an error.
UPDATE Person.Contact with (NOLOCK) SET Suffix = 'B' WHERE ContactID < 20

Msg 1065, Level 15, State 1, Line 15
The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements.

Schema Change Blocking

Since a NOLOCK hint needs to get a Sch-S (schema stability) lock, a SELECT using NOLOCK could still be blocked if a table is being altered and not committed. Here is an example.
-- run in query window 1
BEGIN TRAN
ALTER TABLE Person.Contact ADD column_b VARCHAR(20) NULL ;
If we try to run our SELECT statement it will be blocked until the above is committed or rolled back.
-- run in query window 2
SELECT * FROM Person.Contact WITH (NOLOCK) WHERE ContactID < 20

Issues

We mentioned above how you can get dirty reads using the NOLOCK hint.  These are also other terms you may encounter for this hint.
  • Dirty Reads - this occurs when updates are done, so the data you select could be different.
  • Nonrepeatable Reads - this occurs when you need to read the data more than once and the data changes during that process
  • Phantom Reads - occurs where data is inserted or deleted and the transaction is rolled back.  So for the insert you will get more records and for the delete you will get less records.
To learn more about these issues read this article: http://msdn.microsoft.com/en-us/library/ms190805.aspx

Isolation Level

You can also set the Isolation Level for all queries instead of using the NOLOCK or READUNCOMMITTED hint. The isolation level will apply the READUNCOMMITTED to all SELECT statements that are performed from when this is turned on until it is turned off.
In the example below, the two SELECT statements will use the READUNCOMMITED or NOLOCK hint and the UPDATE will still function as normal.  This way you can set a whole batch of statements instead of modifying each query.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- turn it on

   SELECT * FROM Person.Contact WHERE ContactID < 20

   UPDATE Person.Contact SET Suffix = 'B' WHERE ContactID = 1

   SELECT * FROM Person.Contact WHERE ContactID < 20

SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- turn it off

Next Steps

  • Now that you have a better understanding of how this works check your code to see if there are instances where the NOLOCK hint doesn't make sense.

Thursday, July 28, 2011

SQL Server, locking and hints

Over the past few years, SQL Server has blossomed from a small office data store to an enterprise-level database server. The number of users concurrently accessing a database also increased with this upgrade. SQL Server 2000's standard approach to locking resources often seems inefficient, but thankfully it provides features to override the standard locking. Locking hints may be used to tell the server how to lock resources, but let's examine locking before covering them.

What is a lock?
Relational database systems like SQL Server use locks to prevent users from stepping on each other's toes. That is, locks prevent users from making conflicting data changes. When one user has a particular piece of data locked, no other user may modify it. In addition, a lock prevents users from viewing uncommitted data changes. Users must wait for the changes to be saved before viewing. Data may be locked using various methods. SQL Server 2000 uses locks to implement pessimistic concurrency control among multiple users performing modifications in a database at the same time.

Deadlocks
A database deadlock can occur when there is a dependency between two or more database sessions for some set of resources. A deadlock is a condition that can occur on any system with multiple threads, not just on a relational database management system. A thread in a multithreaded system may acquire one or more resources (for example, locks). If the resource being acquired is currently owned by another thread, the first thread may have to wait for the owning thread to release the target resource. The waiting thread is said to have a dependency on the owning thread for that particular resource. The following listing shows the text of an exception where a deadlock occurred:
System.Data.SqlClient.SqlException: Transaction (Process ID 12) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

This exception was thrown when one SQL Server call conflicted with another resource that held a lock on the necessary resource. Consequently, one of the processes was terminated. This is a common error message for deadlocks with the process ID being unique to the system.

Types of locks
A database system may lock data items at one of many possible levels within the system hierarchy. The possibilities include:

Rows—an entire row from a database table
Pages—a collection of rows (usually a few kilobytes)
Extents—usually a collection of a few pages
Table—an entire database table
Database—the entire database table is locked


Unless otherwise specified, the database uses its own judgment to determine the best locking approach based upon the scenario. Locking is a resource-intensive activity (with respect to memory), so this is not always the best approach. Thankfully, SQL Server does provide a way to circumvent the default behavior. This is accomplished with locking hints.

Hints
There are times when you need to override SQL Server's locking scheme and force a particular range of locks on a table. Transact-SQL provides a set of table-level locking hints that you can use with SELECT, INSERT, UPDATE, and DELETE statements to tell SQL Server how you want it to lock the table by overriding any other system-wide or transactional isolation levels. The available hints include the following:

FASTFIRSTROW—The query is optimized to get the first row of the result set.
HOLDLOCK—Hold a shared lock until the transaction has been completed.
NOLOCK—Do not issue shared locks or recognize exclusive locks. This may result in data being returned that has been rolled back or has not been committed; therefore, working with dirty data is possible. This may only be used with the SELECT statement.
PAGLOCK—Locks the table.
READCOMMITTED—Read only data from transactions that have been committed. This is SQL Server's default behavior.
READPAST—Rows locked by other processes are skipped, so the returned data may be missing rows. This may only be used with the SELECT statement.
READUNCOMMITTED—Equivalent to NOLOCK.
REPEATABLEREAD—Locks are placed on all data used in queries. This prevents other users from updating the data, but new phantom rows can be inserted into the data set by another user and are included in later reads in the current transaction.
ROWLOCK—Locks the data at row level. SQL Server often locks at the page or table level to modify a row, so developers often override this setting when working with single rows.
SERIALIZABLE—Equivalent to HOLDLOCK.
TABLOCK—Lock at the table level. You may want to use this when performing many operations on table-level data.
UPDLOCK—Use update locks instead of shared locks while reading a table, and hold locks until the end of the transaction. This has the advantage of allowing you to read data without locking and to update that data later knowing the data has not changed.
XLOCK—Uses an exclusive lock on all resources until the end of the transaction.


Microsoft has two categories for the hints: granularity and isolation-level. Granularity hints include PAGLOCK, NOLOCK, ROWLOCK, and TABLOCK. On the other hand, isolation-level hints include HOLDLOCK, NOLOCK, READCOMMITTED, REPEATABLEREAD, and SERIALIZABLE. A maximum of one from each group may be used.

These hints allow the consultant to control the locking used by SQL Server, and they are included in the Transact-SQL statement. They are placed in the FROM portion of the statement preceded by the WITH statement. The WITH statement is an option with SQL Server 2000, but Microsoft strongly urges its inclusion. This leads many to believe that it may be mandatory in future SQL Server releases. Here is the hint syntax as it applies to the FROM clause:
[ FROM { < table_source > } [ ,...n ] ]
< table_source > ::=
table_name [ [ AS ] table_alias ] [ WITH ( < table_hint > [ ,...n ] ) ]
< table_hint > ::=
{ INDEX ( index_val [ ,...n ] )
| FASTFIRSTROW
| HOLDLOCK
| NOLOCK
| PAGLOCK
| READCOMMITTED
| READPAST
| READUNCOMMITTED
| REPEATABLEREAD
| ROWLOCK
| SERIALIZABLE
| TABLOCK
| TABLOCKX
| UPDLOCK
| XLOCK }

While this syntax does show its usage, it's easier to show a real example. The following Transact-SQL statement selects all data from the Employees table of the Northwind database:
SELECT *
FROM Employees WITH (nolock)

This gives me all data regardless of what other processes are currently doing with it, so the data may be dirty, but this is not important to my task. Another example updates all rows in a table, setting a field to a certain value:
UPDATE
Employees WITH (tablock)
SET Title='Test'

This example is updating every row in the table, so a table lock is utilized.

Alternate
At this point, I must stress the fact that even though a table-level hint is specified in code, the query optimizer may ignore the hint. Table-level hints are ignored if the query optimizer does not choose the table and used in the subsequent query plan. Also, the query optimizer will often choose an indexed view over a table. Lastly, a hint may be ignored if the table contains computed columns.

Use your discretion
Using table hints in your applications depends upon what is required. Whether you use them at all will depend upon your needs. For example, many consultants love to use the FASTFIRSTROW hint to return the first row quickly. This gives them something to work with while the rest of the query completes. When the data is unlikely to change (e.g., archived data), the NOLOCK hint is a good choice since the data is basically static. On the other hand, this approach would not be good when doing financial applications, when accuracy is a must.
Ref: http://www.techrepublic.com/article/control-sql-server-locking-with-hints/5181472