We need to talk about the "I" in ACID (Neo4j is ACID-compliant), which stands for "isolation". The level of isolation tells you, how much of each other's data concurrently running transaction see.
The default isolation level in Neo4j is "read committed". This means A will not see the data B has written, until B commits. This is achieved by automatic locking, which works as follows:
Neo4j read-locks nodes and relationships when you read them (you can obtain many read locks), and write-locks nodes and relationships when you modify them. Read lock can't be obtained when there is a write lock, and write lock can't be obtained when there is another write lock. Locks are released when the transaction commits.
However, some anomalies can happen at this isolation level, one of which is called "lost update".
For illustration, let c be your counter value (I understand an atomic counter is what your ultimately after). Both transaction are incrementing the counter by 1.
c=0
Tx1 reads c=0 (read locks c)
Tx2 reads c=0 (read locks c)
Tx1 writes c=1 (write locks c)
Tx1 commits (unlocks c)
Tx2 writes c=1 (because it thinks c is still 0, write locks c)
Tx2 commits (unlocks c)
The update Tx1 has made is lost.
To prevent this, you need to change the isolation level to "repeatable read" by write-locking the objects you're going to modify explicitly up front, before reading their current value. This way, they will not be modifiable by any other concurrently running transaction.
c=0
Tx1 write locks c
Tx1 reads c=0
Tx2 tries to write lock c, has to wait
Tx1 writes c=1
Tx1 commits (unlocks c)
Tx2 write locks c (because it now can)
Tx2 reads c=1
Tx2 writes c=2
Tx2 commits (unlocks c)
Hope that makes things clearer.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…