Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming PL SQL.doc
Скачиваний:
3
Добавлен:
01.07.2025
Размер:
5.06 Mб
Скачать

19.7 Improving Application Performance

Now that we have reviewed some of the ways you can analyze your application's execution, let's consider some tips for improving the performance of that application.

19.7.1 Avoid Unnecessary Code Execution

This is "mom and apple pie" advice in the world of tuning, and may seem rather obvious. Surely, if the code is not necessary I would not include it in my program. Surely. And under the pressure of the moment, I would never take shortcuts or do bad things because I always have plenty of time to pay the necessary attention to write optimized code. Right.

The reality is that we usually rush headlong into our coding tasks, feeling just a bit panicky and overwhelmed, annoyed that management won't buy us good tools and in a frenzy to get the job done and done well. And that is why, contrary to the laws of logic, it is all too easy to end up with code that really does not need to be executed in our programs. Removing such code will usually improve performance, sometimes in a dramatic fashion.

19.7.1.1 The search for unnecessary code

In an application with thousands of lines of source, how should you look for potential problems? Here are my recommendations:

Check your loops

Code within a loop (FOR, WHILE, and simple) usually executes more than once. Therefore, any inefficiency in a loop's scope can have a multiplying effect.

Check your SQL statements

First of all, you should of course make sure that your SQL statements have been optimized. That topic is outside the scope of this chapter; there are many fine tools and books that will help you tune your SQL. There are situations, however, when pure SQL results in excessive execution—and when the judicious use of PL/SQL can improve that statement's performance.

Review heavily patched sections of code

In any complex program that has a lifespan of more than six months, you will usually find a section that has been changed again and again and again. It is very easy to allow inefficiencies to slip in with such incremental changes.

Don't take the declaration section for granted

Sure, that's the place where you declare all variables, give them their initial values, and so on. It is quite possible, however, that some actions taken in that section (the declarations themselves or the defaulting code) are not always needed and should not always be run on startup of the block.

Let's take a closer look at a number of these topics.

19.7.1.2 Check your loops

Codewithin a FOR, WHILE, or simple loop executes more than once (usually), so any inefficiency in a loop's scope therefore tends to have a multiplying effect. In one tuning exercise for a client, I discovered a 30-line function that ran in less than half a second, but was executed so frequently that its total elapsed time for a run was five hours. Focused tuning on that one program reduced its total execution time to less than twenty minutes. Always go to your loops first and make sure you are not introducing such a problem.

Here is an obvious example. My procedure accepts a single name argument, then processes each record fetched from a packaged cursor:

PROCEDURE process_data (nm_in IN VARCHAR2) IS

BEGIN

FOR rec IN my_package.my_cursor

LOOP

process_record (

UPPER (nm_in),

rec.total_production);

END LOOP;

END;

The problem with this code is that I apply the UPPER function to the nm_in argument for every iteration of the loop. That is unnecessary because the value of nm_in never changes. I can easily fix this by declaring a local variable to store the uppercased version of the name:

PROCEDURE process_data (nm_in IN VARCHAR2)

IS

v_nm some_table.some_column%TYPE := UPPER (nm_in);

BEGIN

FOR rec IN my_package.my_cursor

LOOP

process_record (v_nm, rec.total_production);

END LOOP;

END;

Of course, it is not always so easy to spot redundant code execution. In this example, one would assume that I uppercased the name either because I knew for certain that process_record would not work properly with a lower- or mixed-case string, or because I was not really sure how process_record works and therefore "took out insurance" to head off any possible problems.

If I have found the process_data procedure to be a bottleneck, it is very important that I understand how all of the code on which it depends works. An incorrect assumption can intersect in very nasty ways with the algorithms of underlying programs. It may well be the case, for example, that process_record always performs an uppercase conversion on its first parameter. That would make my UPPER unnecessary.

Here is another program with lots of processing inside the loop. Do you see any unnecessary execution?

1 CREATE OR REPLACE PROCEDURE process_data (tab_in IN VARCHAR2)

2 IS

3 cursor_id PLS_INTEGER;

4 exec_stat PLS_INTEGER;

5 BEGIN

6 FOR rec IN (SELECT ... FROM ...)

7 LOOP

8 cursor_id := DBMS_SQL.open_cursor;

9

10 DBMS_SQL.parse (cursor_id,

11 'SELECT ...

12 FROM employee E, ' || tab_in ||

13 'WHERE D.department_id = ' || rec.id ||

14 'AND ...',

15 DBMS_SQL.native

16 );

17 exec_stat := DBMS_SQL.execute (cursor_id);

18 DBMS_SQL.close_cursor (cursor_id);

19 END LOOP;

20 END;

The problem is more subtle in this case. You need to understand the workings of DBMS_SQL (the dynamic SQL built-in package described in Chapter 15) to realize that:

  • You can reusecursors allocated by the DBMS_SQL.OPEN_CURSOR function. Consequently, you do not need to open and close a cursor with each iteration of the loop.

  • More importantly, whenever you're working with dynamic SQL, you must closely analyze the dynamic SQL string. In this case, when I look more closely at lines 11 through 14, I realize that the only thing changing with each loop iteration is the value of the department ID. Currently, this is being concatenated into the string, which means that this query will be reparsed for every different value of the ID. A much more efficient approach is to use binding, as I show in the following example.

Here is a process_data replacement that executes only what is necessary within the loop:

CREATE OR REPLACE PROCEDURE process_data (tab_in IN VARCHAR2)

IS

cursor_id PLS_INTEGER;

exec_stat PLS_INTEGER;

BEGIN

cursor_id := DBMS_SQL.open_cursor;

DBMS_SQL.parse (

cursor_id,

'SELECT ... FROM employee E, '

|| tab_in

|| 'WHERE D.department_id = :my_ID AND ...',

DBMS_SQL.native

);

FOR rec IN (SELECT ... FROM ...)

LOOP

DBMS_SQL.bind_variable (cursor_id, 'my_id', rec.id);

exec_stat := DBMS_SQL.EXECUTE (cursor_id);

END LOOP;

DBMS_SQL.close_cursor (cursor_id);

END;

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]