Yes the statement is atomic but yes the data can change between the 2 reads.
Read Committed
only guarantees that you don't read dirty data it promises nothing else about consistency of reads for that you would need a higher isolation level.
As you said that you would accept a SQL Server Example...
Connection 1
(Assumes under pessimistic read committed isolation level)
CREATE TABLE employee
(
name VARCHAR(50),
DepartmentID INT
)
CREATE TABLE department
(
DepartmentID INT
)
INSERT INTO department VALUES (1)
INSERT INTO employee VALUES ('bob',1)
declare @employee TABLE
(
name VARCHAR(50),
DepartmentID INT
)
WHILE ((SELECT COUNT(*) FROM @employee) < 2)
BEGIN
DELETE FROM @employee
INSERT INTO @employee
SELECT employee.*
FROM employee
LEFT JOIN department
ON employee.DepartmentID = department.DepartmentID
UNION
SELECT employee.*
FROM employee
RIGHT JOIN department
ON employee.DepartmentID = department.DepartmentID
END;
SELECT * FROM @employee
Connection 2
while (1=1)
UPDATE employee SET name = CASE WHEN name = 'bob' THEN 'bill' else 'bob' END
Now go back to connection 1
name DepartmentID
-------------------------------------------------- ------------
bill 1
bob 1
(Remember to switch back to Connection 2 to kill it!)
The specific documentation covering this READ COMMITED
behaviour is here
The shared lock type determines when
it will be released. Row locks are
released before the next row is
processed. Page locks are released
when the next page is read, and table
locks are released when the statement
finishes.