4.4 Phantom Reads
Phantom reads occur when a row is
inserted into or deleted from a range of data by one transaction that
is being read by another set of data. Recall the earlier work queue
scenario. Suppose a user reads the work queue searching for new work
items and gets back 10 records. Another user inserts a new work order.
Shortly afterward, the first user refreshes the list of new work
orders. There are now 11. This additional row is a phantom row.
Often this outcome is desirable. In cases when
you need to be able to rely on the range of data previously read,
however, it is not. The following example uses the Person.Person table to demonstrate a phantom (code file Ch6PhantomReads.sql):
/*SESSION 1*/
USE AdventureWorks2012;
SET TRANSACTION ISOLATION LEVEL
READ COMMITTED;
--SERIALIZABLE;
BEGIN TRANSACTION;
SELECT TOP 5
FirstName
,MiddleName
,LastName
,Suffix
FROM Person.Person
ORDER BY LastName;
WAITFOR DELAY '00:00:05.000';
SELECT TOP 5
FirstName
,MiddleName
,LastName
,Suffix
FROM Person.Person
ORDER BY LastName;
COMMIT TRANSACTION;
In Session 1 the transaction is again going to read the top five people from the Person.Person
table twice in relatively quick succession. Session 2, however, inserts
a new person who meets the criteria in the results of the query.
/*SESSION 2*/
USE AdventureWorks2012;
BEGIN TRANSACTION;
INSERT INTO [Person].[BusinessEntity]
([rowguid]
,[ModifiedDate])
VALUES
(NEWID()
,CURRENT_TIMESTAMP);
DECLARE @Scope_Identity int;
SELECT @Scope_Identity = SCOPE_IDENTITY();
INSERT INTO [Person].[Person]
([BusinessEntityID]
,[PersonType]
,[NameStyle]
,[Title]
,[FirstName]
,[MiddleName]
,[LastName]
,[Suffix]
,[EmailPromotion]
,[AdditionalContactInfo]
,[Demographics]
,[rowguid]
,[ModifiedDate])
VALUES
(@Scope_Identity
,'EM'
,'0'
,'Mr.'
,'James'
,'Anthony'
,'A'
,Null
,0
,Null
,Null
,NEWID()
,CURRENT_TIMESTAMP
);
EXEC SP_EXECUTESQL
N'PRINT ''DELETE FROM Person.Person WHERE BusinessEntityID = '' +CAST(@Scope_
Identity as varchar(8));
PRINT ''DELETE FROM Person.BusinessEntity WHERE BusinessEntityID = ''
+CAST(@Scope_Identity as varchar(8));'
,N'@Scope_Identity int',@Scope_Identity = @Scope_Identity
SELECT @Scope_Identity as BusinessEntityID
COMMIT TRANSACTION;
Run Session 1 now before switching over and
executing Session 2. You should see in the results of the first query
from Session 1 (see Figure 6) that Syed Abbas is the first person of five returned.
However, in the result of the second query from Session 1 (see Figure 7) James Anthony A is now first. James Anthony A is a phantom.
To demonstrate how phantoms can be prevented,
first remove James Anthony A from the table. If you revert to Session 2
and look in your message tab, you should see two delete statements (see
Figure 8 for details).
Copy those two rows into a new window and execute them.
In Session 1, change the transaction isolation
level from read committed to serializable, and repeat the example by
running the code in Session 1 first, followed by that in Session 2:
SET TRANSACTION ISOLATION LEVEL
--READ COMMITTED;
SERIALIZABLE;
This time the results for selects one and two from Session 1 are the same, as shown in Figure 9. Note that the insert from Session 2 still happened, but only after the transaction in Session 1 had been committed.
Don’t forget to remove James Anthony A
from your AdventureWorks2012 database before continuing by repeating
the steps just outlined.