Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005

rekharaghuram
from rekharaghuram More from this publisher
05.11.2015 Views

CHAPTER 8 ■ TRANSACTIONS 267 Many people—for whatever reason—feel compelled to do it like this: ops$tkyte@ORA10G> begin 2 for x in ( select rowid rid, object_name, rownum r 3 from t2 ) 4 loop 5 update t2 6 set object_name = lower(x.object_name) 7 where rowid = x.rid; 8 if ( mod(x.r,100) = 0 ) then 9 commit; 10 end if; 11 end loop; 12 commit; 13 end; 14 / PL/SQL procedure successfully completed. Elapsed: 00:00:05.38 In this simple example, it is many times slower to commit frequently in a loop. If you can do it in a single SQL statement, do it that way, as it is almost certainly faster. Even if we “optimize” the procedural code, using bulk processing for the updates, as follows: ops$tkyte@ORA10G> declare 2 type ridArray is table of rowid; 3 type vcArray is table of t2.object_name%type; 4 5 l_rids ridArray; 6 l_names vcArray; 7 8 cursor c is select rowid, object_name from t2; 9 begin 10 open c; 11 loop 12 fetch c bulk collect into l_rids, l_names LIMIT 100; 13 forall i in 1 .. l_rids.count 14 update t2 15 set object_name = lower(l_names(i)) 16 where rowid = l_rids(i); 17 commit; 18 exit when c%notfound; 19 end loop; 20 close c; 21 end; 22 / PL/SQL procedure successfully completed. Elapsed: 00:00:02.36

268 CHAPTER 8 ■ TRANSACTIONS it is in fact much faster, but still much slower than it could be. Not only that, but you should notice that the code is getting more and more complex. From the sheer simplicity of a single UPDATE statement, to procedural code, to even more complex procedural code—we are going in the wrong direction! Now, just to supply a counterpoint to this discussion, recall in Chapter 7 when we discussed the concept of write consistency and how an UPDATE statement, for example, could be made to restart. In the event that the preceding UPDATE statement was to be performed against a subset of the rows (it had a WHERE clause), and other users were modifying the columns this UPDATE was using in the WHERE clause, then there would be a case either for using a series of smaller transactions rather than one large transaction or for locking the table prior to performing the mass update. The goal here would be to reduce the opportunity for restarts to occur. If we were to UPDATE the vast majority of the rows in the table, that would lead us toward using the LOCK TABLE command. In my experience, however, these sorts of large mass updates or mass deletes (the only statement types really that would be subject to the restart) are done in isolation. That large, one-time bulk update or the purge of old data generally is not done during a period of high activity. Indeed, the purge of data should not be affected by this at all, since you would typically use some date field to locate the information to purge, and other applications would not modify this data. Snapshot Too Old Error Let’s now look at the second reason developers are tempted to commit updates in a procedural loop, which arises from their (misguided) attempts to use a “limited resource” (undo segments) sparingly. This is a configuration issue; you need to ensure that you have enough undo space to size your transactions correctly. Committing in a loop, apart from generally being slower, is also the most common cause of the dreaded ORA-01555 error. Let’s look at this in more detail. As you will appreciate after reading Chapters 1 and 7, Oracle’s multi-versioning model uses undo segment data to reconstruct blocks as they appeared at the beginning of your statement or transaction (depending on the isolation mode). If the necessary undo information no longer exists, you will receive an ORA-01555: snapshot too old error message, and your query will not complete. So, if you are modifying the table that you are reading (as in the previous example), you are generating undo information required for your query. Your UPDATE generates undo information that your query will probably be making use of to get the read-consistent view of the data it needs to update. If you commit, you are allowing the system to reuse the undo segment space you just filled up. If it does reuse the undo, wiping out old undo data that your query subsequently needs, you are in big trouble. Your SELECT will fail and your UPDATE will stop partway through. You have a part-finished logical transaction and probably no good way to restart it (more about this in a moment). Let’s see this concept in action with a small demonstration. In a small test database, I set up a table: ops$tkyte@ORA10G> create table t as select * from all_objects; Table created. ops$tkyte@ORA10G> create index t_idx on t(object_name); Index created.

CHAPTER 8 ■ TRANSACTIONS 267<br />

Many people—for whatever reason—feel compelled to do it like this:<br />

ops$tkyte@ORA10G> begin<br />

2 for x in ( select rowid rid, object_name, rownum r<br />

3 from t2 )<br />

4 loop<br />

5 update t2<br />

6 set object_name = lower(x.object_name)<br />

7 where rowid = x.rid;<br />

8 if ( mod(x.r,100) = 0 ) then<br />

9 commit;<br />

10 end if;<br />

11 end loop;<br />

12 commit;<br />

13 end;<br />

14 /<br />

PL/SQL procedure successfully completed.<br />

Elapsed: 00:00:05.38<br />

In this simple example, it is many times slower to commit frequently in a loop. If you<br />

can do it in a single SQL statement, do it that way, as it is almost certainly faster. Even if we<br />

“optimize” the procedural code, using bulk processing for the updates, as follows:<br />

ops$tkyte@ORA10G> declare<br />

2 type ridArray is table of rowid;<br />

3 type vcArray is table of t2.object_name%type;<br />

4<br />

5 l_rids ridArray;<br />

6 l_names vcArray;<br />

7<br />

8 cursor c is select rowid, object_name from t2;<br />

9 begin<br />

10 open c;<br />

11 loop<br />

12 fetch c bulk collect into l_rids, l_names LIMIT 100;<br />

13 forall i in 1 .. l_rids.count<br />

14 update t2<br />

15 set object_name = lower(l_names(i))<br />

16 where rowid = l_rids(i);<br />

17 commit;<br />

18 exit when c%notfound;<br />

19 end loop;<br />

20 close c;<br />

21 end;<br />

22 /<br />

PL/SQL procedure successfully completed.<br />

Elapsed: 00:00:02.36

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!