Я решил написать цикл небольших заметок благодаря посту Хорошие привычки в PL/SQL, советы которого и рассмотрю.
В этой части обсудим:
Cделаем тест-кейс — создадим табличку с полем типа Integer и внесем в нее тестовое значение:
Как видите, значение прекрасно вставилось. А теперь давайте попробуем его получить в pl/sql блоке:
Причина ошибки кроется в том, что INTEGER в SQL это number(*,0) — floating-point number, у которого scale = 0, а INTEGER в PL/SQL это «SUBTYPE INTEGER IS NUMBER(38,0);».
Что же насчет типа из той статьи "CREATE TYPE id_list IS TABLE OF INTEGER;"?
Естественно, это коллекция pl/sql integer'ов:
Тип id_list я создал как integer_table — это просто моя прихоть создавать типы коллекций с постфиксом _TABLE. Кроме того, я обычно не создаю по типу на каждую таблицу table of table_name%rowtype, а только на часто используемые или если эти типы используются в параметрах процедур и функций. Кстати говоря, если у вас oracle < 11g и вы создаете тип в пакете и затем используете его в параметрах или полях объектов уровня схемы, то это автоматически создает и тип на уровне схемы с «многозначными» названиями типа: SYS_PLSQL_2906526_17_1, в 11g эти типы просто скрыли.
Не секрет, что если мы хотим что-то проапдейтить в выборке, то надо использовать select for update для блокировки необходимых строк целиком или полей в них. Например, в той процедуре из обсуждаемой статьи нет кляузы for update и результаты могут быть фееричными(изменение salary не у тех работников, если в параллельной сессии изменялся отдел, потеря изменений salary в случае параллельного изменения salary другой сессией в зависимости от очередности коммита в 1-й или 2-й сессии), поэтому select следовало бы переписать так:
Учтите, что «OF e.salary» не означает, что заблокировано будет изменение только поля e.salary(такое невозможно), будут заблокированы все строки таблицы с алиасом e:
Указание здесь e.salary означает лишь к кому дополнительно применять механизм statement restart, и, поэтому в данном случае, как и во всех случаях когда вам нужно заблокировать строки всех таблиц из FROM, а изменяемые поля перечислены в списке select, можно не указывать «OF ...»
Кстати, с for update можно нарваться на ORA-00060: deadlock detected.
Рассмотрим пример:
Эмулировать задержку будем функцией:
И запустим в параллельных сессиях:
1:
2.
Иногда когда важна скорость можно воспользоваться параметрами к for update: nowait или skip locked. Кстати, следует учесть, что в случае использования skip locked оконные функции и rownum будут отрабатывать на целом наборе не исключая блокированных. Пример:
1-я сессия:
2-я сессия:
Весьма забавно было прочитать в том посте о том, что всегда надо работать с коллекциями, да и еще и обрабатывать их целиком. Про «FOR LOOP» и оптимальное количество для bulk операций Томом освещается здесь и выяснять кол-во вам нужно непосредственно в конкретной вашей ситуации.
В случае же с FORALL надо уточнить сразу несколько моментов:
В общем, это момент неоднозначный и категорически нельзя советовать во всех случаях что-то одно.
PS. Продолжение следует…
Код в статье оформлен с помощью Source Code Highlighter.
В этой части обсудим:
1. Нюансы %TYPE/%ROWTYPE
Cделаем тест-кейс — создадим табличку с полем типа Integer и внесем в нее тестовое значение:
create table test_integer(a integer);<br>create type integer_table as table of integer;<br>/<br>insert into test_integer values (1E125);<br>commit;<br>select * from test_integer;
Как видите, значение прекрасно вставилось. А теперь давайте попробуем его получить в pl/sql блоке:
declare<br> l_a test_integer.a%type;<br>begin<br> select a into l_a from test_integer;<br> dbms_output.put_line('Все ок, значение=' || l_a);<br>exception <br> when others then<br> dbms_output.put_line('Все плохо! '||sqlerrm);<br>end;<br>/
Все плохо! ORA-06502: PL/SQL: numeric or value error: number precision too large PL/SQL procedure successfully completed. Elapsed: 00:00:00.02
Причина ошибки кроется в том, что INTEGER в SQL это number(*,0) — floating-point number, у которого scale = 0, а INTEGER в PL/SQL это «SUBTYPE INTEGER IS NUMBER(38,0);».
Что же насчет типа из той статьи "CREATE TYPE id_list IS TABLE OF INTEGER;"?
Естественно, это коллекция pl/sql integer'ов:
DB1> declare 2 l_int_tab integer_table; 3 begin 4 select a 5 bulk collect into l_int_tab 6 from test_integer; 7 dbms_output.put_line('Все ок!'); 8 exception when others then 9 dbms_output.put_line('Все плохо! Exception:'||sqlerrm); 10 end; 11 / Все плохо! Exception:ORA-06502: PL/SQL: numeric or value error: Bulk bind: Error in define PL/SQL procedure successfully completed. Elapsed: 00:00:00.00
Тип id_list я создал как integer_table — это просто моя прихоть создавать типы коллекций с постфиксом _TABLE. Кроме того, я обычно не создаю по типу на каждую таблицу table of table_name%rowtype, а только на часто используемые или если эти типы используются в параметрах процедур и функций. Кстати говоря, если у вас oracle < 11g и вы создаете тип в пакете и затем используете его в параметрах или полях объектов уровня схемы, то это автоматически создает и тип на уровне схемы с «многозначными» названиями типа: SYS_PLSQL_2906526_17_1, в 11g эти типы просто скрыли.
2. Select for update
Не секрет, что если мы хотим что-то проапдейтить в выборке, то надо использовать select for update для блокировки необходимых строк целиком или полей в них. Например, в той процедуре из обсуждаемой статьи нет кляузы for update и результаты могут быть фееричными(изменение salary не у тех работников, если в параллельной сессии изменялся отдел, потеря изменений salary в случае параллельного изменения salary другой сессией в зависимости от очередности коммита в 1-й или 2-й сессии), поэтому select следовало бы переписать так:
SELECT e.employee_id,e.salary,e.hire_date<br>FROM employee e <br>WHERE department_id = dept_in<br>FOR UPDATE OF e.salary;
Учтите, что «OF e.salary» не означает, что заблокировано будет изменение только поля e.salary(такое невозможно), будут заблокированы все строки таблицы с алиасом e:
The columns in the OF clause only indicate which table or view rows are locked.
Указание здесь e.salary означает лишь к кому дополнительно применять механизм statement restart, и, поэтому в данном случае, как и во всех случаях когда вам нужно заблокировать строки всех таблиц из FROM, а изменяемые поля перечислены в списке select, можно не указывать «OF ...»
Кстати, с for update можно нарваться на ORA-00060: deadlock detected.
Рассмотрим пример:
create table test_integer<br>pctfree 99<br>pctused 1<br>as<br>select level id,<br> trunc(10*dbms_random.value) val,<br> rpad('x',100) padding<br>from dual connect by level <=100<br>/<br>create index test_indeger_idx on test_integer(id)<br>/<br>exec dbms_stats.gather_table_stats(ownname => user,tabname => 'TEST_INTEGER',cascade => true);
Эмулировать задержку будем функцией:
create or replace function integer_delay(p_i in integer,p_interval in integer)<br>return integer<br>is<br>begin<br> dbms_lock.sleep(seconds => p_interval);<br> return p_i;<br>end;<br>
И запустим в параллельных сессиях:
1:
begin<br> for rec in (<br> select --+ index(test_integer_idx)<br> id,val<br> from test_integer<br> where integer_delay(id,1) in (100,30,1)<br> order by id desc<br> for update<br> )<br> loop<br> dbms_output.put_line(rec.id);<br> end loop;<br> commit;<br>end;
2.
begin<br> for rec in (<br> select --+ index(test_integer_idx)<br> id,val<br> from test_integer<br> where integer_delay(id,1) in (100,30,1)<br> order by id desc<br> for update<br> )<br> loop<br> dbms_output.put_line(rec.id);<br> end loop;<br> commit;<br>end;
Иногда когда важна скорость можно воспользоваться параметрами к for update: nowait или skip locked. Кстати, следует учесть, что в случае использования skip locked оконные функции и rownum будут отрабатывать на целом наборе не исключая блокированных. Пример:
1-я сессия:
select * from test_integer where id in (1,3,4,8) for update;
2-я сессия:
select id<br> ,row_number()over (order by id) rn<br> ,rownum<br>from test_integer t<br>where id < 10<br>order by id <br>for update skip locked
3. Работа с коллекциями
Весьма забавно было прочитать в том посте о том, что всегда надо работать с коллекциями, да и еще и обрабатывать их целиком. Про «FOR LOOP» и оптимальное количество для bulk операций Томом освещается здесь и выяснять кол-во вам нужно непосредственно в конкретной вашей ситуации.
В случае же с FORALL надо уточнить сразу несколько моментов:
- FORALL не атомарен — то есть, если данные которые он будет видеть будут не на момент начала выполнения FORALL, а на момент выполнения конкретного его цикла получения набора, а обновление он будет проводить потом, когда получит готовый измененный набор(это для read committed, для serializable получим ora-08177).
Проведем пример снова с test_ab, только сначала снова установите b=1 для всех столбцов.
- Запускаем первую сессию:
declare <br> type number_table is table of number;<br> <br> procedure bulk_update(p_nt in number_table) is<br> begin<br> forall n in p_nt.first..p_nt.last<br> update test_ab<br> set b=b*10 <br> where a=p_nt(n)<br> and a=integer_delay(a,10);<br> end bulk_update;<br>begin<br> bulk_update(number_table(1,3,7,10));<br>end;
- А потом спустя пару секунд запускаем вторую сессию и преспокойно меняем любое b из того же набора:
update test_ab set b=10 where a=7;
Как видите, пока мы не завершим вторую транзакцию, первая будет заблокирована, и при этом в первой транзакции будут видны значения второй, хотя мы ее запустили последней. Можно, конечно, сначала заблокировать таблицу test_ab, например, через тот же select for update, но так ли это удобно и всегда ли необходимо?
- Запускаем первую сессию:
- Управление планами выполнения в FORALL усложняется.
В случае использования различных SQL-вариантов мы можем относительно легко подстраивать план под массовые update, что в случае с forall затруднительно, с другой стороны — возможно, что улучшать ничего и не надо(например, в случае index range scan).
В общем, это момент неоднозначный и категорически нельзя советовать во всех случаях что-то одно.
PS. Продолжение следует…
Код в статье оформлен с помощью Source Code Highlighter.