3.5 Double Reads
Double reads can occur when scanning
data while using the default read committed isolation level, covered
later in this chapter. During a period of concurrent activity, it is
possible for one query to perform a range scan on a table and, as it is
scanning, a second transaction can come in and move a row, thus causing
it to be read twice. This can happen when the initial read during the
range scan is not repeatable. The locks taken when reading data are by
default released as soon as the data has been successfully read.
Specific action is required to prevent this; you must increase the
isolation level.
For example, the following code moves Bethany
Raheem and so reads her record twice. There are only five Raheems in
the AdventureWorks2012 database. However, in this example you will see
six.
First, Session 1 creates a blocking update midway
through the range scan of the Raheem data on a row that is further
through the index than Bethany’s row (code file Ch6DoubleReads.sql):
/* SESSION 1 PART 1 */
Use AdventureWorks2012;
BEGIN TRAN
UPDATE Person.Person
SET LastName = 'Raheem_DOUBLE_READ_BLOCK'
WHERE LastName = 'Raheem'
AND FirstName = 'Kurt';
Now Session 2 starts a scan to return
all persons whose surname begins with Raheem. This query will scan the
index and be blocked by the uncommitted update in Session 1:
/* SESSION 2 */
USE AdventureWorks2012;
SELECT FirstName
,LastName
FROM Person.Person
WHERE LastName Like 'Raheem%';
Return to Session 1 and move Bethany
Raheem, who has already been read, to a position in the index after the
row being updated in Session 1:
/* SESSION 1 PART 2 */
UPDATE Person.Person
SET LastName = 'Raheem_DOUBLE_READ_REAL'
WHERE LastName = 'Raheem'
AND FirstName = 'Bethany';
COMMIT TRAN;
The range scan query in Session 2 can now complete, and the results look like those in Figure 10.
3.6 Halloween Effect
The Halloween effect
refers to a scenario in which data moves position within the result set
and consequently could be changed multiple times. This effect is
different from the double read because it is driven by data
modification, rather than read queries.
In order to perform an update, the data must be
read first. This is performed using two cursors: one for the read and
the other for the write. If the data is updated by the write cursor
before all the data was read in, then it is possible that a row will
move position (courtesy of the update), potentially be read a second
time, and consequently be updated again. In theory, this could go on
forever. Reading the data using an index whose key is going to be
updated by the query is an example of the Halloween effect.
This scenario is obviously highly undesirable,
and thankfully the Storage Engine in SQL Server protects against it. As
mentioned, SQL Server uses two cursors during an update: one to perform
the read and another to perform the write. To ensure that the data
available to the write has been read fully, SQL Server needs to inject
a blocking operator such as a spool into the plan. It doesn’t have to
be the spool, but this operator is commonly selected because it
invariably has the lowest cost attached to it. That said, it still
isn’t very efficient, as it means all the data has to be inserted into
tempdb before it can be used by the write cursor. It does ensure that
all the data is read before any modifications take place.
To achieve a greater level of efficiency, SQL
Server actually looks out for the Halloween effect problem when
creating the plan. It introduces the blocking operator only when there
is a chance of the Halloween effect occurring. Even then it adds an
extra one only if no blocking operator is already present in the plan
performing this function.
In most update scenarios the index is used to
locate data, and other non-key columns are updated in the table. You
wouldn’t normally expect the key to be frequently updated as well, so
being able to remove the blocking operator is an important optimization.
It is worth remembering the performance
penalty of the Halloween effect when deciding on your indexing
strategy. Perhaps that index you were thinking of adding isn’t such a
great idea after all. When index or performance tuning, it is always
worthwhile to keep an eye on the impact that your changes have on
tempdb.