2

I'm implementing Non-repeatable read isolation level on WildFly 9.0.2 in a @Stateless rest resource

  1. Thread A is reading a Account entity, prints the balance and then do some other work (sleep).
  2. Thread B comes in and read the same Account entity, prints the balance and calculates the balance via calculateBalance() method and then update the entity. It reads the entity again and prints out the balance.
  3. Thread A then reads the entity and prints out the balance.

According to my understanding of the Non-Repeatable read level Thread B should block until Thread A completely finish (Exit the transaction/Stateless rest resource).

Here is the printout:

  • Thread A: printBalance=500
  • Thread B: printBalance=500
  • Thread B: printBalance=600
  • Thread A: printBalance=500

From that I can see Thread B is not blocking and is allowed to run even though Thread A is still busy.

Below is the code:

    @GET
    @Path("/{accountId}/{threadName}")
    public Response calculcateBalance(@PathParam("accountId") Long accountId, @PathParam("threadName") String threadName) {

        Account account = em.find(Account.class, accountId);
        printBalance(account,threadName);

        if ("ThreadA".equals(threadName)) {
            sleepSeconds(10);
        } else if ("ThreadB".equals(threadName)) {
            account.calculateBalance();
            em.merge(account);
        }

    account = em.find(Account.class, accountId);
    printBalance(account,threadName);

    return Response.ok().build();
}

If I change the isolation level to Serializable everything blocks.

Is my understanding of Non-repeatable read wrong? Should Thread B not be blocked until Thread A is finished?

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Rentius2407
  • 1,108
  • 2
  • 11
  • 29

1 Answers1

2

It depends on the underlying database system. If you were using SQL Server, which uses 2PL by default, the Thread A would acquire a shared lock upon reading that row and the Thread B would be preventing from writing that row (until Thread A releases the shared lock).

Oracle, PostgreSQL, and MySQL use MVCC and the Repeatable Read doesn't use locking because readers don't block writers and writers don't block readers. In MVCC, anomalies are detected and if Thread B modifies that row, then Thread A would detect that change and would abort its transactions.

So, in MVCC, anomalies are detected rather than prevented.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Hi Vlad, I think that is exactly what is happening. I'm connecting to a MySQL database. So for it to detect the entity was changed I need to add the `@Version` on the Account entity? – Rentius2407 Jan 22 '16 at 17:03
  • Do I need any configuration on MySQL to detect it? I don't think it is detecting the lost update. – Rentius2407 Jan 22 '16 at 17:09
  • 1
    You're right. I have a [test that replicates that](https://github.com/vladmihalcea/high-performance-java-persistence/blob/master/core/src/test/java/com/vladmihalcea/book/hpjp/jdbc/transaction/MySQLPhenomenaTest.java). In [my book](https://leanpub.com/high-performance-java-persistence), at page 97 you can see a table that shows that lost updates are not prevented by Repeatable Read in MySQL. You need to use optimistic locking or Serializable, but optimistic locking scales better. – Vlad Mihalcea Jan 22 '16 at 17:12