The jobs or tasks that Oracle manages are called sessions. A user session is started when you run an application program or an Oracle tool and connect to Oracle. To allow user sessions to work "simultaneously" and share computer resources, Oracle must control concurrency, the accessing of the same data by many users. Without adequate concurrency controls, there might be a loss of data integrity. That is, changes to data or structures might be made in the wrong order.
Oracle uses locks to control concurrent access to data. A lock gives you temporary ownership of a database resource such as a table or row of data. Thus, data cannot be changed by other users until you finish with it. You need never explicitly lock a resource because default locking mechanisms protect Oracle data and structures. However, you can request data locks on tables or rows when it is to your advantage to override default locking. You can choose from several modes of locking such as row share and exclusive.
A deadlock can occur when two or more users try to access the same database object. For example, two users updating the same table might wait if each tries to update a row currently locked by the other. Because each user is waiting for resources held by another user, neither can continue until Oracle breaks the deadlock by signaling an error to the last participating transaction.
When a table is being queried by one user and updated by another at the same time, Oracle generates a read-consistent view of the data for the query. That is, once a query begins and as it proceeds, the data read by the query does not change. As update activity continues, Oracle takes snapshots of the table's data and records changes in a rollback segment. Oracle uses information in the rollback segment to build read-consistent query results and to undo changes if necessary.
The first SQL statement in your program begins a transaction. When one transaction ends, the next SQL statement automatically begins another transaction. Thus, every SQL statement is part of a transaction. A distributed transaction includes at least one SQL statement that updates data at multiple nodes in a distributed database.
The COMMIT and ROLLBACK statements ensure that all database changes brought about by SQL operations are either made permanent or undone at the same time. All the SQL statements executed since the last commit or rollback make up the current transaction. The SAVEPOINT statement names and marks the current point in the processing of a transaction.
Consider a simple transaction that transfers money from one bank account to another. The transaction requires two updates because it debits the first account, then credits the second. In the example below, after crediting the second account, you issue a commit, which makes the changes permanent. Only then do other users see the changes.
BEGIN ... UPDATE accts SET bal = my_bal - debit WHERE acctno = 7715; ... UPDATE accts SET bal = my_bal + credit WHERE acctno = 7720; COMMIT WORK; END;
The COMMIT statement releases all row and table locks. It also erases any savepoints (discussed later) marked since the last commit or rollback. The optional keyword WORK has no effect other than to improve readability. The keyword END signals the end of a PL/SQL block, not the end of a transaction. Just as a block can span multiple transactions, a transaction can span multiple blocks.
The COMMENT clause lets you specify a comment to be associated with a distributed transaction. When you issue a commit, changes to each database affected by a distributed transaction are made permanent. However, if a network or machine fails during the commit, the state of the distributed transaction might be unknown or in doubt. In that case, Oracle stores the text specified by COMMENT in the data dictionary along with the transaction ID. The text must be a quoted literal up to 50 characters long. An example follows:
COMMIT COMMENT 'In-doubt order transaction; notify Order Entry';
PL/SQL does not support the FORCE clause, which, in SQL, manually commits an in-doubt distributed transaction. For example, the following COMMIT statement is illegal:
COMMIT FORCE '23.51.54'; -- illegal
Consider the example below, in which you insert information about an employee into three different database tables. All three tables have a column that holds employee numbers and is constrained by a unique index. If an INSERT statement tries to store a duplicate employee number, the predefined exception DUP_VAL_ON_INDEX is raised. In that case, you want to undo all changes. So, you issue a rollback in the exception handler.
DECLARE emp_id INTEGER; ... BEGIN SELECT empno, ... INTO emp_id, ... FROM new_emp WHERE ... ... INSERT INTO emp VALUES (emp_id, ...); INSERT INTO tax VALUES (emp_id, ...); INSERT INTO pay VALUES (emp_id, ...); ... EXCEPTION WHEN DUP_VAL_ON_INDEX THEN ROLLBACK; ... END;
Oracle can also roll back single SQL statements to break deadlocks. Oracle signals an error to one of the participating transactions and rolls back the current statement in that transaction.
Before executing a SQL statement, Oracle must parse it, that is, examine it to make sure it follows syntax rules and refers to valid database objects. Errors detected while executing a SQL statement cause a rollback, but errors detected while parsing the statement do not.
DECLARE emp_id emp.empno%TYPE; BEGIN ... UPDATE emp SET ... WHERE empno = emp_id; DELETE FROM emp WHERE ... ... SAVEPOINT do_insert; INSERT INTO emp VALUES (emp_id, ...); EXCEPTION WHEN DUP_VAL_ON_INDEX THEN ROLLBACK TO do_insert; END;
When you roll back to a savepoint, any savepoints marked after that savepoint are erased. However, the savepoint to which you roll back is not erased. For example, if you mark five savepoints, then roll back to the third, only the fourth and fifth are erased. A simple rollback or commit erases all savepoints.
If you mark a savepoint within a recursive subprogram, new instances of the SAVEPOINT statement are executed at each level in the recursive descent. However, you can only rollback to the most recently marked savepoint.
Savepoint names are undeclared identifiers and can be reused within a transaction. This moves the savepoint from its old position to the current point in the transaction. Thus, a rollback to the savepoint affects only the current part of your transaction. An example follows:
BEGIN ... SAVEPOINT my_point; UPDATE emp SET ... WHERE empno = emp_id; ... SAVEPOINT my_point; -- move my_point to current point INSERT INTO emp VALUES (emp_id, ...); ... EXCEPTION WHEN OTHERS THEN ROLLBACK TO my_point; END;
By default, the number of active savepoints per session is limited to five. An active savepoint is one marked since the last commit or rollback. You or your DBA can raise the limit (up to 255) by increasing the value of the Oracle initialization parameter SAVEPOINTS.
If you exit a stored subprogram with an unhandled exception, PL/SQL does not assign values to OUT parameters. Also, PL/SQL does not roll back database work done by the subprogram.
For example, in the SQL*Plus environment, if your PL/SQL block does not include a COMMIT or ROLLBACK statement, the final state of your transaction depends on what you do after running the block. If you execute a data definition, data control, or COMMIT statement or if you issue the EXIT, DISCONNECT, or QUIT command, Oracle commits the transaction. If you execute a ROLLBACK statement or abort the SQL*Plus session, Oracle rolls back the transaction.
In the Oracle Precompiler environment, if your program does not terminate normally, Oracle rolls back your transaction. A program terminates normally when it explicitly commits or rolls back work and disconnects from Oracle using the RELEASE parameter, as follows:
EXEC SQL COMMIT WORK RELEASE;
In the OCI environment, if you issue the OLOGOF call, Oracle automatically commits your transaction. Otherwise, Oracle rolls back the transaction.
During a read-only transaction, all queries refer to the same snapshot of the database, providing a multi-table, multi-query, read-consistent view. Other users can continue to query or update data as usual. A commit or rollback ends the transaction. In the example below, as a store manager, you use a read-only transaction to gather sales figures for the day, the past week, and the past month. The figures are unaffected by other users updating the database during the transaction.
DECLARE daily_sales REAL; weekly_sales REAL; monthly_sales REAL; BEGIN ... COMMIT; -- ends previous transaction SET TRANSACTION READ ONLY; SELECT SUM(amt) INTO daily_sales FROM sales WHERE dte = SYSDATE; SELECT SUM(amt) INTO weekly_sales FROM sales WHERE dte > SYSDATE - 7; SELECT SUM(amt) INTO monthly_sales FROM sales WHERE dte > SYSDATE - 30; COMMIT; -- ends read-only transaction ... END;
The SET TRANSACTION statement must be the first SQL statement in a read-only transaction and can only appear once in a transaction. If you set a transaction to READ ONLY, subsequent queries see only changes committed before the transaction began. The use of READ ONLY does not affect other users or transactions.
With the SELECT FOR UPDATE statement, you can explicitly lock specific rows of a table to make sure they do not change before an update or delete is executed. However, Oracle automatically obtains row-level locks at update or delete time. So, use the FOR UPDATE clause only if you want to lock the rows before the update or delete.
You can explicitly lock entire tables using the LOCK TABLE statement.
DECLARE CURSOR c1 IS SELECT empno, sal FROM emp WHERE job = 'SALESMAN' AND comm > sal FOR UPDATE NOWAIT;
The FOR UPDATE clause identifies the rows that will be updated or deleted, then locks each row in the result set. This is useful when you want to base an update on the existing values in a row. In that case, you must make sure the row is not changed by another user before the update.
The optional keyword NOWAIT tells Oracle not to wait if the table has been locked by another user. Control is immediately returned to your program so that it can do other work before trying again to acquire the lock. If you omit the keyword NOWAIT, Oracle waits until the table is available. The wait has no limit unless the table is remote, in which case the Oracle initialization parameter DISTRIBUTED_LOCK_TIMEOUT sets a limit.
All rows are locked when you open the cursor, not as they are fetched. The rows are unlocked when you commit or roll back the transaction. So, you cannot fetch from a FOR UPDATE cursor after a commit. (For a workaround, see "Fetching Across Commits" .)
When querying multiple tables, you can use the FOR UPDATE clause to confine row locking to particular tables. Rows in a table are locked only if the FOR UPDATE OF clause refers to a column in that table. For example, the following query locks rows in the emp table but not in the dept table:
DECLARE CURSOR c1 IS SELECT ename, dname FROM emp, dept WHERE emp.deptno = dept.deptno AND job = 'MANAGER' FOR UPDATE OF sal;
You use the CURRENT OF clause in an UPDATE or DELETE statement to refer to the latest row fetched from a cursor, as the following example shows:
DECLARE CURSOR c1 IS SELECT empno, job, sal FROM emp FOR UPDATE; ... BEGIN OPEN c1; LOOP FETCH c1 INTO ... ... UPDATE emp SET sal = new_sal WHERE CURRENT OF c1; END LOOP;
LOCK TABLE emp IN ROW SHARE MODE NOWAIT;
The lock mode determines what other locks can be placed on the table. For example, many users can acquire row share locks on a table at the same time, but only one user at a time can acquire an exclusive lock. While one user has an exclusive lock on a table, no other users can insert, delete, or update rows in that table. For more information about lock modes, see Oracle7 Server Application Developer's Guide.
A table lock never keeps other users from querying a table, and a query never acquires a table lock. Only if two different transactions try to modify the same row will one transaction wait for the other to complete.
Note: If your program includes SQL locking statements, make sure the Oracle users requesting locks have the privileges needed to obtain the locks.
DECLARE CURSOR c1 IS SELECT ename FROM emp FOR UPDATE OF sal; ctr NUMBER := 0; BEGIN FOR emp_rec IN c1 LOOP -- FETCHes implicitly ... ctr := ctr + 1; INSERT INTO temp VALUES (ctr, 'still going'); IF ctr >= 10 THEN COMMIT; -- releases locks END IF; END LOOP; END;
If you want to fetch across commits, do not use the FOR UPDATE and CURRENT OF clauses. Instead, use the ROWID pseudocolumn to mimic the CURRENT OF clause. Simply select the rowid of each row into a ROWID variable. Then, use the rowid to identify the current row during subsequent updates and deletes. An example follows:
DECLARE CURSOR c1 IS SELECT ename, job, rowid FROM emp; my_ename emp.ename%TYPE; my_job emp.job%TYPE; my_rowid ROWID; BEGIN OPEN c1; LOOP FETCH c1 INTO my_ename, my_job, my_rowid; EXIT WHEN c1%NOTFOUND; UPDATE emp SET sal = sal * 1.05 WHERE rowid = my_rowid; -- this mimics WHERE CURRENT OF c1 COMMIT; END LOOP; CLOSE c1; END;
Be careful. In the last example, the fetched rows are not locked because no FOR UPDATE clause is used. So, other users might unintentionally overwrite your changes. Also, the cursor must have a read-consistent view of the data, so rollback segments used in the update are not released until the cursor is closed. This can slow down processing when many rows are updated.
The next example shows that you can use the %ROWTYPE attribute with cursors that reference the ROWID pseudocolumn:
DECLARE CURSOR c1 IS SELECT ename, sal, rowid FROM emp; emp_rec c1%ROWTYPE; BEGIN OPEN c1; LOOP FETCH c1 INTO emp_rec; EXIT WHEN c1%NOTFOUND; ... IF ... THEN DELETE FROM emp WHERE rowid = emp_rec.rowid; END IF; END LOOP; CLOSE c1; END;
The best solution to this problem is to modularize your program by defining subprograms (which can be stored in an Oracle database). For more information, see Chapter 7.
Another solution is to break the block into two sub-blocks. Before the first block terminates, have it insert any data the second block needs into a database table called temp (for example). When the second block starts executing, have it select the data from temp. This approximates the passing of parameters from one procedure to another. The following example shows two "parameter passing" PL/SQL blocks in a SQL*Plus script:
DECLARE mode NUMBER; median NUMBER; BEGIN ... INSERT INTO temp (col1, col2, col3) VALUES (mode, median, 'blockA'); END; / ...
DECLARE mode NUMBER; median NUMBER; BEGIN SELECT col1, col2 INTO mode, median FROM temp WHERE col3 = 'blockA'; ... END; /
The previous method works unless you must re-execute the first block while the second block is still executing or unless two or more users must run the script concurrently. To avoid these restrictions, embed your PL/SQL blocks in a third-generation host language such as C, COBOL, or FORTRAN. That way, you can re-execute the first block using flow-of-control statements. Also, you can store data in global host variables instead of using a temporary database table. In the following example, you embed two PL/SQL blocks in a Pro*C program:
EXEC SQL BEGIN DECLARE SECTION; int my_empno; float my_sal, my_comm; short comm_ind; ... EXEC SQL END DECLARE SECTION; ... EXEC SQL EXECUTE BEGIN ... SELECT sal, comm INTO :my_sal, :my_comm:comm_ind FROM emp WHERE empno = :my_empno; IF :my_comm:comm_ind IS NULL THEN ... END IF; END; END-EXEC; ... EXEC SQL EXECUTE BEGIN ... IF :my_comm:comm_ind > 1000 THEN :my_sal := :my_sal * 1.10; UPDATE emp SET sal = :my_sal WHERE empno = :my_empno; END IF; END; END-EXEC; ...