Tips For Minimizing Deadlocks in SQL Server
A deadlock or fatal embrace is a situation which occurs when the a process is waiting for the resource that is locked by a second process and that second process is waiting for a resource that is locked by the first process. In this article, we will learn about ways to minimise deadlocks.
Tips for Minimizing Deadlocks
Database servers are configured in such a way that they can service multiple requests from multiple users. Obviously this increases the likelihood of conflicts because multiple processes can request access to same resources at the same time. This type of conflict is also known as block or blocking. Blocking usually resolves itself after the locking process releases the resource for waiting process. Sometimes, blocking creates a more serious condition, called a deadlock or fatal embrace, which occurs when the first process is waiting for the resource that is locked by the second process and the second process, is waiting for the resource that is locked by the first process (see below):
In this situation, both processes are stuck because each process is waiting for other to release the resource. Unless one process times out, the lock won’t clear itself.
We cannot totally prevent deadlocks, but they can be minimized by following the below tips:
Bad database design is the priamry cause of deadlocks. As a Database Developer or DBA, we must ensure that our databases are properly normalized because it will reduce the likelihood of deadlock to occur.
Follow the consistent access pattern
The easiest way to avoid deadlocks is to be disciplined in your code. This can be achieved by ensuring that all the resources are accessed in the same order all the time. For example, if two concurrent transactions both started by requesting the lock for row 1 and later on requesting the lock for row 2. This will simply be a blocking situation rather than a deadlock because transaction 1 will never be deadlocking transaction 2 as resource locks will never be held out of order. This can be easily achieved by using Stored Procedures for data modifications activities because it will standardize the order of accessing the objects. It is also worth defining and implementing the programming policy when designing the application, which defines the order in which objects of the databases can be accessed. This will help you to avoid deadlocks.
Make any necessary updates for the transaction before beginning the transaction and do not allow users to enter the data during transactions.
If possible, limit the use of cursors within your code because the same locking rules will apply to a SELECT statement in a cursor definition that applies to another SELECT statement. When using cursors, ensure you have the correct isolation level or locking hint specified for your cursor SELECT statement. This is because SQL Server holds the locks for both SELECT statements within a cursor and the independent SELECT statement until both transactions is completed (This only applies if SQL Server is running in explicit or implicit transaction mode). For more information, see Cursor Locking.
Keep Transactions Small
Keep your transactions as short as possible because running several large transactions simultaneously increases the likelihood of a deadlock. If possible, breakdown the one large transaction in to several small transactions and then execute these transactions in batches. This is because exclusive or update locks are held longer for large transactions, which eventually block other activities and leads to possible deadlock situations. Executing the large transaction in batches will help to minimise the network roundtrips during the transaction, reducing the possible delays in completing the transaction and releasing the locks.
Reduce Transactions Time
Reduce the transaction time by making sure that you are not performing the same reads over and over again. If your application needs to read the same data more than once, then cache the data into variables, temporary tables or table variables. You can then reread the data from cache. This will help to reduce the lock time on actual resource. We can also reduce lock time by making sure that our application grabs the locks at the latest possible moment and release it at its earliest time.
Controlling Lock Escalation
If applicable, use ROWLOCK or PAGLOCK to control of the lock escalation. This is because transaction locks in SQL Server consumes memory resources and as the number of locks increases, the memory decreases. If the percentage of memory used for transactions locks exceeds a certain threshold, then SQL Server converts the row or page locks in to table locks. This process is known as lock escalation. Lock escalation reduces the total number of locks held on the SQL Server instance, reducing the lock memory usage. While finer grain locks do consume more memory, but also can improve concurrency. For more information, see Lock Escalation (Database Engine).
Consider Using NOLOCK Hint
As we know, If we execute SELECT against table then SQL Server default isolation level locks the entire table and any other queries that try to access the same table will have to wait for the lock to be released. This is fine if we need accurate results, but if our table’s only stores historical data and these tables are updated only once a day and queried frequently during the day, then NOLOCK would be a better option. Carefully analyse your database environment and if appropriate, consider if we can use NOLOCK hint where possible.
Choose Appropriate Isolation Level
Consider using lower isolation level such as READ COMMITED for your transactions because it will reduce the locking contention. For example, if we use READ COMMITED isolation level then our share locks will be held for shorter duration as compared to higher isolation level such as SERIALIZABLE. For more information, see SET TRANSACTION ISOLATION LEVEL (Transact-SQL).
Consider Using Bound Connections
Consider using bound connections because it allows two or more connections share the same transactions and locks. For more information, see Using Bound Connections.
Deadlocks are caused by poor database design, inappropriate isolation level, inefficient code etc. In this article, we learned about the different ways to minimise deadlocks on SQL Server.