Read Committed Snapshot uses only locks when selecting data from tables.
However, in t1 and t2 you are updating the data, which is another scenario.
When you UPDATE the counter, you go to write lock (in line), preventing another update from appearing. t2 can read, but t2 will block it UPDATE until t1 is executed, and t2 cannot commit to t1 (which contradicts your timeline). Only one of the transactions will receive a counter update, so both will correctly update the counter, taking into account the code presented. (Verified)
- counter = 0
- t1 update counter (counter => 1)
- t2 update counter (blocked)
- t1 commit (counter = 1)
- t2 unlocked (now you can update the counter) (counter => 2)
- t2 commit
Read Committed simply means that you can only read fixed values, but that does not mean that you have repeated reads. Thus, if you use and depend on a counter variable and intend to update it later, you may be performing transactions at the wrong isolation level.
You can use a repeatable read lock, or if you only occasionally update the counter, you can do it yourself using optimistic locking technology. for example, a timestamp column with a counter table or conditional update.
DECLARE @CounterInitialValue INT DECLARE @NewCounterValue INT SELECT @CounterInitialValue = SELECT counter FROM MyTable WHERE MyID = 1234
This devx article is informative, although it talks about features while they are still in beta, so it may not be entirely accurate.
update: As Justice indicates, if t2 is a nested transaction in t1, the semantics are different. Again, both will correctly update the counter (+2), because from the point t2 inside t1 the counter has already been updated once. The nested t2 does not have access to what the counter was before t1 updated it.
- counter = 0
- t1 update counter (counter => 1)
- t2 update counter (nested transaction) (counter => 2)
- t2 commit
- t1 commit (counter = 2)
In a nested transaction, if t1 returns a ROLLBACK after t1 COMMIT, the counter returns its original value, since it also cancels the commit t2.