package org.multiverse.api; /** * With the IsolationLevel you have a way to provide declarative control to guarantee isolation between transactions. * The transaction is free to provide a higher isolation than the one specified. * <p/> * The dirty read isn't added since atm we already have an extremely cheap read using the atomicWeakGet on the * refs. Using the atomicWeakGet you have extremely cheap access to committed data. * * <p>The following isolation anomalies have been identified: * <ol> * <li>Dirty Read</li> * <li>Unrepeatable Read</li> * <li>Inconsistent Read</li> * <li>Write Skew</li> * </ol> * * <h3>Dirty Read</h3> * * <p>The DirtyRead isolation anomaly is that one transaction could observe uncommitted changes made by another transaction. * The biggest problem with this anomaly is that eventually the updating transaction could abort and the reading transaction * sees changes that never made it into the system. * * <p>Currently the Dirty Read anomaly is not possible in Multiverse since writes to main memory are deferred until commit * and once the first write to main memory is executed, the following writes are guaranteed to succeed. * * <h3>Unrepeatable Read</h3> * * <p>The Unrepeatable Read isolation anomaly is that a transaction could see changes made by other transactions. So at one * moment it could see value X and a moment later it could see Y. This could lead to all kinds of erratic behavior like * transaction that can get stuck in an infinitive loop. * * <p>Such a transaction is called a zombie transaction and can cause serious damage since they are consuming resources * (like cpu) and are holding on to various resources (like Locks). So the unrepeatable read should be used with care. * * <h3>Inconsistent Read</h3> * * <p>With the inconsistent read it is possible that a value is read depending on a previous read value that has been updated * by another transaction. E.g. there are 100 refs all initialized with the same value and there is an updating transaction * that increments all value's by one, and there is a reading transaction that reads all refs, then it should see the same * values for all values. * * <h3>Writeskew</h3> * * <p>With the writeskew problem it is possible that 2 transactions commit.... * * <p>Example 1: there are 1 person that has 2 bank accounts and the invariant is that the sum of both bank accounts is * always equal or larger than zero. If user has has 50 euro on both accounts (so the sum is 100) and there are 2 transactions * that both subtract 100 euro from the bank accounts, and the first sees account1=50 and account2=50, the subtraction is allowed * and subtracts it 100 from the first account, and the second transaction sees exactly the same and subtracts 100 from the second * account, it could be possible that the person ends up with -50 on both accounts (so a sum of -100), clearly violating the * contract that the the sum should never be smaller than 0. * * <p>Example 2: * * <h3>Isolation Levels</h3> * * <p>The following isolation levels are currently defined in Multiverse: * <ol> * <li>Read Committed</li> * <li>Repeatable Read</li> * <li>Snapshot</li> * <li>Serialized</li> * </ol> * The read uncommitted is currently not defined since currently there is no need to do a dirty read, there will always be committed * data available for reading (the write is deferred till commit, so you won't have to see transactions in progress). * * <table> * * </table> * * * <h3>Implementation and isolation level upgrade</h3> * * <p>An implementation of the {@link Txn} is free to upgrade the isolation level to a higher one if it doesn't support that specific isolation * level. This is the same as Oracle is doing with the ReadUncommitted, which automatically is upgraded to a ReadCommitted or the RepeatableRead which is * automatically upgraded to Snapshot (Oracle calls this the Serialized isolation level). * * <h3>Isolation: pessimistic or optimistic</h3> * * <h3>Isolation through locking</h3> * * * * * @author Peter Veentjer. * @see TxnFactoryBuilder#setIsolationLevel(IsolationLevel) */ public enum IsolationLevel { /** * With the RepeatableRead isolation level you will always see committed data and next to that once a read * is done, this read is going to be repeatable (so you will see the same value every time). */ RepeatableRead(false, true, true), /** * The default isolation level that allows for the writeskew problem but not for dirty or unrepeatable * or inconsistent reads. * <p/> * This is the 'serialized' isolation level provided by MVCC databases like Oracle/Postgresql * (although Postgresql 9 is going to provide a truly serialized isolation level) and MySQL with the InnoDb. * All data read. contains committed data and all data will be consistent. * <p/> * A transaction that is readonly, gets the same isolation behavior as the Serializable isolation level * since the writeskew problem can't occur (nothing can be written). */ Snapshot(false, false, true), /** * Provides truly serialized transaction at the cost of reduced performance and concurrency. This is the highest * isolation level where no isolation anomalies are allowed to happen. So the writeSkew problem is not allowed to * happen. */ Serializable(false, false, false); private final boolean allowWriteSkew; private final boolean allowUnrepeatableRead; private final boolean allowInconsistentRead; IsolationLevel(boolean allowUnrepeatableRead, boolean allowInconsistentRead, boolean allowWriteSkew) { this.allowUnrepeatableRead = allowUnrepeatableRead; this.allowInconsistentRead = allowInconsistentRead; this.allowWriteSkew = allowWriteSkew; } /** * Checks if the writeskew problem is allowed to happen. * * @return true if the writeSkew is allowed to happen. */ public final boolean doesAllowWriteSkew() { return allowWriteSkew; } /** * Checks if the dirty read is allowed to happen (so reading data that has not been committed). * * @return true if the dirty read is allowed to happen. */ public boolean doesAllowUnrepeatableRead() { return allowUnrepeatableRead; } /** * Checks if the inconsistent read is allowed to happen. * * @return true if the inconsistent read is allowed to happen. */ public boolean doesAllowInconsistentRead() { return allowInconsistentRead; } @Override public String toString() { return "IsolationLevel." + name(); } }