8.3. Isolation and Database LockingTransaction isolation (the "I" in ACID) is a critical part of any transactional system. This section explains isolation conditions, database locking, and transaction isolation levels. These concepts are important when deploying any transactional system. 8.3.1. Dirty, Repeatable, and Phantom ReadsTransaction isolation is defined in terms of isolation conditions called dirty reads, repeatable reads, and phantom reads . These conditions describe what can happen when two or more transactions operate on the same data.[2]
To illustrate these conditions, let's think about two separate client applications using their own instances of the TravelAgent to access the same data--specifically, a cabin record with the primary key of 99. These examples revolve around the RESERVATION table, which is accessed by both the bookPassage() method (through the Reservation bean) and the listAvailableCabins() method (through JDBC). It might be a good idea to go back to Chapter 7, "Session Beans" and review how the RESERVATION table is accessed through these methods. This will help you to understand how two transactions executed by two different clients can impact each other. Assume that both methods have a transaction attribute of Required. 8.3.1.1. Dirty readsA dirty read occurs when the first transaction reads uncommitted changes made by a second transaction. If the second transaction is rolled back, the data read by the first transaction becomes invalid because the rollback undoes the changes. The first transaction won't be aware that the data it has read has become invalid. Here's a scenario showing how a dirty read can occur (illustrated in Figure 8-9):
Figure 8-9. A dirty readClient 2 is now using an invalid list of available cabins because Cabin 99 is available but is not included in the list. This would be serious if Cabin 99 was the last available cabin because Client 2 would inaccurately report that the cruise was booked. The customer would presumably try to book a cruise on a competing cruise line. 8.3.1.2. Repeatable readsA repeatable read is when the data read is guaranteed to look the same if read again during the same transaction. Repeatable reads are guaranteed in one of two ways: either the data read is locked against changes or the data read is a snapshot that doesn't reflect changes. If the data is locked, then it cannot be changed by any other transaction until this transaction ends. If the data is a snapshot, then other transactions can change the data, but these changes won't be seen by this transaction if the read is repeated. Here's an example of a repeatable read (illustrated in Figure 8-10):
Figure 8-10. Repeatable readThis example is somewhat unusual because it uses javax.transaction.UserTransaction. This class is covered in more detail later in this chapter; essentially, it allows a client application to control the scope of a transaction explicitly. In this case, Client 1 places transaction boundaries around both calls to listAvailableCabins(), so that they are a part of the same transaction. If Client 1 didn't do this, the two listAvailableCabins() methods would have executed as separate transactions and our repeatable read condition would not have occurred. Although Client 2 attempted to change the bed count for Cabin 99 to 3, Cabin 99 still shows up in the Client 1 call to listAvailableCabins() when a bed count of 2 is requested. This is because either Client 2 was prevented from making the change (because of a lock), or Client 2 was able to make the change, but Client 1 is working with a snapshot of the data that doesn't reflect that change. A nonrepeatable read is when the data retrieved in a subsequent read within the same transaction can return different results. In other words, the subsequent read can see the changes made by other transactions. 8.3.1.3. Phantom readsPhantom reads occur when new records added to the database are detectable by transactions that started prior to the insert. Queries will include records added by other transactions after their transaction has started. Here's a scenario that includes a phantom read (illustrated in Figure 8-11):
Figure 8-11. Phantom readClient 1 places transaction boundaries around both calls to listAvailableCabins(), so that they are a part of the same transaction. In this case, the reservation was made between the listAvailableCabins() queries in the same transaction. Therefore, the record inserted in the RESERVATION table didn't exist when the first listAvailableCabins() method is invoked, but it does exist and is visible when the second listAvailableCabins() method is invoked. The record inserted is a phantom record. 8.3.2. Database LocksDatabases, especially relational databases, normally use several different locking techniques. The most common are read locks, write locks, and exclusive write locks. (I've taken the liberty of adding "snapshots," although this isn't a formal term.) These locking mechanisms control how transactions access data concurrently. Locking mechanisms impact the read conditions that were just described. These types of locks are simple concepts that are not directly addressed in the EJB specification. Database vendors implement these locks differently, so you should understand how your database addresses these locking mechanisms to best predict how the isolation levels described in this section will work.
8.3.3. Transaction Isolation LevelsTransaction isolation is defined in terms of the isolation conditions (dirty reads, repeatable reads, and phantom reads). Isolation levels are commonly used in database systems to describe how locking is applied to data within a transaction.[3] The following terms are usually used to discuss isolation levels:
These isolation levels are the same as those defined for JDBC. Specifically, they map to the static final variables in the java.sql.Connection class. The behavior modeled by the isolation levels in the connection class is the same as the behavior described here. The exact behavior of these isolation levels depends largely on the locking mechanism used by the underlying database or resource. How the isolation levels work depends in large part on how your database supports them. 8.3.3.1. EJB 1.1 transaction isolation controlIn EJB 1.1, isolation levels are not controlled through declarative attributes, as was the case in EJB 1.0. In EJB 1.1, the deployer sets transaction isolation levels if the container manages the transaction. The bean developer sets the transaction isolation level if the bean manages the transaction. Up to this point we have only discussed container-managed transactions; bean-managed transactions are discussed later in this chapter. 8.3.3.2. EJB 1.0 transaction isolation controlEJB 1.0 describes four isolation levels that can be assigned to the methods of a bean in the ControlDescriptor. We did this several times when we created control descriptors for all the beans we developed in this book. Here is a snippet of code from the MakeDD class used to create the TravelAgentDD.ser in Chapter 7, "Session Beans", showing how we set the isolation level: ControlDescriptor cd = new ControlDescriptor(); cd.setIsolationLevel (ControlDescriptor.TRANSACTION_SERIALIZABLE); cd.setMethod(null); ControlDescriptor [] cdArray = {cd}; sd.setControlDescriptors(cdArray); In our example so far, we have always used the isolation level ControlDescriptor.TRANSACTION_SERIALIZABLE, the most restrictive isolation level. Table 8-2 shows the transaction isolation levels and their corresponding attribute in the ControlDescriptor class.
Table 8-2. Isolation Level Attributes in EJB 1.0
You are allowed to specify isolation levels on a per-method basis, but this flexibility comes with an important restriction: all methods invoked in the same transaction must have the same isolation level. You can't mix isolation levels within transactions at runtime. 8.3.4. Balancing Performance Against ConsistencyGenerally speaking, as the isolation levels become more restrictive, the performance of the system decreases because more restrictive isolation levels prevent transactions from accessing the same data. If isolation levels are very restrictive, like Serializable, then all transactions, even simple reads, must wait in line to execute. This can result in a system that is very slow. EJB systems that process a large number of concurrent transactions and need to be very fast will therefore avoid the Serializableisolation level where it is not necessary, since it will be prohibitively slow. Isolation levels, however, also enforce consistency of data. More restrictive isolation levels help ensure that invalid data is not used for performing updates. The old adage "garbage in, garbage out" applies here. The Serializable isolation level ensures that data is never accessed concurrently by transactions, thus ensuring that the data is always consistent. Choosing the correct isolation level requires some research about the database you are using and how it handles locking. You must also balance the performance needs of your system against consistency. This is not a cut-and-dried process, because different applications use data differently. Although there are only three ships in Titan's system, the beans that represent them are included in most of Titan's transactions. This means that many, possibly hundreds, of transactions will be accessing these Ship beans at the same time. Access to Ship beans needs to be fast or it becomes a bottleneck, so we do not want to use very restrictive isolation levels. At the same time, the ship data also needs to be consistent; otherwise, hundreds of transactions will be using invalid data. Therefore, we need to use a strong isolation level when making changes to ship information. To accommodate these conflicting requirements, we can apply different isolation levels to different methods. Most transactions use the Ship bean's get methods to obtain information. This is read-only behavior, so the isolation level for the get methods can be very low, such as Read Uncommitted. The set methods of the ship bean are almost never used; the name of the ship probably wouldn't change for years. However, the data changed by the set methods must be isolated to prevent dirty reads by other transactions, so we will use the most restrictive isolation level, Serializable, on the ship's set methods. By using different isolation levels on different business methods, we can balance consistency against performance. 8.3.4.1. EJB 1.1: Controlling isolation levelsDifferent EJB servers allow different levels of granularity for setting isolation levels; some servers defer this responsibility to the database. In some servers, you may be able to set different isolation levels for different methods, while other products may require the same isolation level for all methods in a bean, or possibly even all beans in the container. You will need to consult your vendor's documentation to find out the level of control your server offers. Bean-managed transactions in stateful session beans, however, allow the bean developer to specify the transaction isolation level using the API of the resource providing persistent storage. The JDBC API, for example, provides a mechanism for specifying the isolation level of the database connection. The following code shows how this is done. Bean-managed transactions are covered in more detail later in this chapter. ... DataSource source = (javax.sql.DataSource) jndiCntxt.lookup("java:comp/env/jdbc/titanDB"); Connection con = source.getConnection(); con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); ... You can set the isolation level to be different for different databases within the same transaction, but all beans that use the same database in a transaction should use the same isolation level. 8.3.4.2. EJB 1.0: Controlling isolation levelsThe following code, taken from a deployment descriptor for a Ship bean, shows one way to assign these isolation levels: Method [] methods = new Method[6]; Class [] parameters = new Class[0]; methods[ 0 ] = ShipBean.class.getDeclaredMethod("getName",parameters); methods[ 1 ] = ShipBean.class.getDeclaredMethod("getTonnage",parameters); methods[ 2 ] = ShipBean.class.getDeclaredMethod("getCapacity",parameters); parameters = new Class[1]; parameters[0] = String.class; methods[ 3 ] = ShipBean.class.getDeclaredMethod("setName",parameters); parameters[0] = Double.TYPE; methods[ 4 ] = ShipBean.class.getDeclaredMethod("setTonnage",parameters); parameters[0] = Integer.TYPE; methods[ 5 ] = ShipBean.class.getDeclaredMethod("setCapacity",parameters); ControlDescriptor [] cds = new ControlDescriptor[methods.length]; for (int i = 0; i < methods.length; i++) { cds[i] = new ControlDescriptor(methods[i]); if (methods[i].getReturnType() == Void.TYPE) { // Set methods all return void. cds[i].setIsolationLevel( ControlDescriptor.TRANSACTION_SERIALIZABLE); } else { // Get methods don't return void. cds[i].setIsolationLevel( ControlDescriptor.TRANSACTION_READ_UNCOMMITTED); } cds[i].setRunAsMode(ControlDescriptor.CLIENT_IDENTITY); cds[i].setTransactionAttribute(ControlDescriptor.TX_REQUIRED); } shipDD.setControlDescriptors(cds); This code takes all the set methods in the Ship interface that are used to make updates (setName(), setCapacity(), setTonnage()) and gives them an isolation level of TRANSACTION_SERIALIZABLE. For the get methods (getName(), getCapacity(), getTonnage()), which are used for reading data, the isolation level is set to TRANSACTION_READ_UNCOMMITTED.
Understanding the effect of isolation levels on your code's behavior is crucial to balancing performance against consistency. In EJB 1.0, all the bean methods invoked within the same transaction must have the same isolation level. In the TravelAgent bean, for example, every method invoked on every bean within the scope of the bookPassage() method must have the same transaction isolation level. Any method invoked with a different isolation level will throw a java.rmi.RemoteException. Therefore, mixing isolation levels across beans (specifying different isolation levels for different beans within your application) must be done with care and only in those circumstances when methods with different isolation levels will never need to be executed in the same transaction. Copyright © 2001 O'Reilly & Associates. All rights reserved. | ||||||||||
|