Notes on SQL

Random articles from a puzzled DBA

In the previous article it was shown that phantom reads and skipped rows were possible with the repeatable read isolation level.
Serializable is the highest isolation level and will not allow phantom reads or skipped rows.

Using the same table that demonstrated repeatable read, an initial execution of the two queries shown produces a count and SUM of 1000541.

serializable_01

Note that yet again the BEGIN/COMMIT TRANSACTION lines have been commented out. The two queries therefore run in their own independent transactions.
If I execute a script to update one of the selected values,

UPDATE dbo.Test4
SET Test_Value = 2
WHERE ID = 887;

whilst repeating the query above, the results appear to be the same as for repeatable read.
serializable_02
serializable_03

The UPDATE was blocked during the execution of the first SELECT, but the SUM query used the updated value.
Reset the data, enable the BEGIN/COMMIT TRANSACTION lines and repeat the test and it produces results that match the same test against repeatable read.
serializable_04
The UPDATE was blocked until the transaction that had acquired the locks had released those locks. Rerun the first SELECT query and the change has been completed once it was allowed to do so.
serializable_05
In this example the behaviour looks the same as that for repeatable read. However, the locks acquired are different and this results in different behaviour in relation to phantom reads and skipped rows.

Adding qualifying data (Phantom Reads)
As with the repeatable read tests, all but 100 rows of data where ‘Index_Column’ has a value of ‘B’ have been removed and a 10-second delay placed between the two queries.
serializable_06
Running the main query and executing the following query during the 10-second delay, which will attempt to add a row to the start of the index.

INSERT INTO [dbo].[Test4]
           ([ID]
           ,[Test_Value]
           ,[Index_Column])
     VALUES
           (1
           ,1
           ,'B')
GO

At this point the difference between repeatable read and serializable becomes apparent.
serializable_07

The insert statement was blocked for the entire transaction, so both SELECT statements returned consistent results. With repeatable read the second query read more rows than the first.
Serializable does not only place locks on the data it is reading but by acquiring a key-range lock it blocks any attempt to insert rows that would have matched this selection criteria, regardless of their location within the index. Because it was locking rows with the ‘Index_Column’ value of ‘B’, it prevented any insertion of rows that also have ‘Index_Column’ set to ‘B’.
Therefore phantom reads are not possible with serializable isolation.

Skipped Rows
Resetting the data and selecting the rows with a value on the ‘ID’ column < 21, produces 16 rows.

serializable_08

In a new window, a transaction is started that will potentially update the row with ‘ID’ = 10.

BEGIN TRANSACTION

UPDATE dbo.Test4
SET Test_Value = 2
WHERE ID = 10;

Notice the absence of a ROLLBACK/COMMIT command, causing a block for the query that will read that area of the index.
Repeat the execution of the ‘WHERE id < 21’ query and it is blocked by the UPDATE query.
In a third window, update the row with ‘ID’ of 20, to move it to the start of the index.

UPDATE dbo.Test4
SET id = 3 
WHERE id = 20;

With repeatable read, this last query completed and effectively moved the data to a point in the index that had already been processed, thereby skipping this data for the main query.
However, because serializable locks all of the keys it will be processing, whether before or after where it has currently reached, this update is blocked.

serializable_09

SPID 73 is

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

SELECT  *
FROM [IsolationLevels].[dbo].[Test4] 
WHERE id < 21

which is blocked by

SPID 75, which is

BEGIN TRANSACTION

UPDATE dbo.Test4
SET Test_Value = 2
WHERE ID = 10;

SPID 70 is

UPDATE dbo.Test4
SET id = 3 
WHERE id = 20;

which is blocked by SPID 73 – the serializable query.
Rolling back SPID 75 results in the server choosing a deadlock victim between SPID 70 and 73 and rolling back a transaction, but does demonstrate that serializable does not allow skipped rows, unlike repeatable read.

Conclusion
By looking at examples of serializable isolation it can be seen that the opportunities for blocking other transactions and the possibility of deadlocks is higher than the ‘lower’ isolation levels, potentially reducing the degree of concurrency of the system. This is the price paid for increased isolation of the data changes when using this isolation level.
In addition, if the field selected in the WHERE clause does not have an index then using serializable isolation will lock the entire table, so index design becomes even more important when using this isolation level.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: