As brb tea says, depends on the database implementation and the algorithm they use: MVCC or Two Phase Locking.
CUBRID (open source RDBMS) explains the idea of this two algorithms:
The first one is when the T2 transaction tries to change the A record,
it knows that the T1 transaction has already changed the A record and
waits until the T1 transaction is completed because the T2 transaction
cannot know whether the T1 transaction will be committed or rolled
back. This method is called Two-phase locking (2PL).
- Multi-version concurrency control (MVCC)
The other one is to allow each of them, T1 and T2 transactions, to
have their own changed versions. Even when the T1 transaction has
changed the A record from 1 to 2, the T1 transaction leaves the
original value 1 as it is and writes that the T1 transaction version
of the A record is 2. Then, the following T2 transaction changes the A
record from 1 to 3, not from 2 to 4, and writes that the T2
transaction version of the A record is 3.
When the T1 transaction is rolled back, it does not matter if the 2,
the T1 transaction version, is not applied to the A record. After
that, if the T2 transaction is committed, the 3, the T2 transaction
version, will be applied to the A record. If the T1 transaction is
committed prior to the T2 transaction, the A record is changed to 2,
and then to 3 at the time of committing the T2 transaction. The final
database status is identical to the status of executing each
transaction independently, without any impact on other transactions.
Therefore, it satisfies the ACID property. This method is called
Multi-version concurrency control (MVCC).
The MVCC allows concurrent modifications at the cost of increased overhead in memory (because it has to maintain different versions of the same data) and computation (in REPETEABLE_READ level you can't loose updates so it must check the versions of the data, like Hiberate does with Optimistick Locking).
In 2PL Transaction isolation levels control the following:
Whether locks are taken when data is read, and what type of locks are requested.
How long the read locks are held.
Whether a read operation referencing rows modified by another transaction:
Block until the exclusive lock on the row is freed.
Retrieve the committed version of the row that existed at the time the statement or transaction started.
Read the uncommitted data modification.
Choosing a transaction isolation level does not affect the locks that
are acquired to protect data modifications. A transaction always gets
an exclusive lock on any data it modifies and holds that lock until
the transaction completes, regardless of the isolation level set for
that transaction. For read operations, transaction isolation levels
primarily define the level of protection from the effects of
modifications made by other transactions.
A lower isolation level increases the ability of many users to access
data at the same time, but increases the number of concurrency
effects, such as dirty reads or lost updates, that users might
encounter.
Concrete examples of the relation between locks and isolation levels in SQL Server (use 2PL except on READ_COMMITED with READ_COMMITTED_SNAPSHOT=ON)
READ_UNCOMMITED: do not issue shared locks to prevent other transactions from modifying data read by the current transaction. READ UNCOMMITTED transactions are also not blocked by exclusive locks that would prevent the current transaction from reading rows that have been modified but not committed by other transactions. [...]
READ_COMMITED:
- If READ_COMMITTED_SNAPSHOT is set to OFF (the default): uses shared locks to prevent other transactions from modifying rows while the current transaction is running a read operation. The shared locks also block the statement from reading rows modified by other transactions until the other transaction is completed. [...] Row locks are released before the next row is processed. [...]
- If READ_COMMITTED_SNAPSHOT is set to ON, the Database Engine uses row versioning to present each statement with a transactionally consistent snapshot of the data as it existed at the start of the statement. Locks are not used to protect the data from updates by other transactions.
REPETEABLE_READ: Shared locks are placed on all data read by each statement in the transaction and are held until the transaction completes.
SERIALIZABLE: Range locks are placed in the range of key values that match the search conditions of each statement executed in a transaction. [...] The range locks are held until the transaction completes.