|
|
|||||||||||||||||||||||||||||
|
Ускорение вставкиИсточник: ln
Том, Я бы хотел знать, как лучше всего выполнить следующие вставки. У меня есть таблицы t1, t2, t3 и x1, x2 и x3: Таблицы x1, x2, x3 первоначально пусты - данные добавляются в них каждый месяц. Сейчас для добавления данных я использую 3 вложенных цикла FOR. Основным является внешний цикл, в котором выбираются все записи из таблицы t1 и вставляются в x1. Первичный ключ таблицы x1 берется из последовательности с помощью триггера, срабатывающего перед вставкой. Значение последовательности возвращается в переменную с помощью конструкции returning into. Следующий вложенный цикл выбирает все записи из таблицы t2, у которых значение в столбце id соответствует значению id (первичному ключу) из x1, и вставляет их в таблицу x2 (снова генерируя первичный ключ в триггере и возвращая его в переменную). Значение последовательности из предыдущего цикла вставляется в x2, чтобы обеспечить связь между x1 и x2. Последний цикл во многом аналогичен - в таблицу x3 вставляются все записи из t3, у которых значение в столбце id соответствует значению id (первичному ключу) из x2, а значение последовательности из предыдущего цикла вставляется в x3, создавая связь между x2 и x3. Итак, используется следующий код: cursor c1 is select * from t1; cursor c2(v_id1 in number) is select * from t2 where t2.id = v_id1; cursor c3(v_id2 in number) is select * from t3 where t3.id = v_id2; for rec1 in c1 loop insert into x1 (col1, col2, col3) values (null, rec1.a, rec1,b) returning col1 into v_new_id; for rec2 in c2(rec1.a) loop insert into x2 (col1, col2, col3) values (null, v_new_id, rec2.a) returning col1 into v_new_id2; for rec3 in c3(rec2.a) loop insert into x3 (col1, col2, col3) values (null, v_new_id2, rec3.a); end loop; end loop; end loop; Не будет ли лучше использовать массивы и множественные вставки? Ответ Тома КайтаНет ли способа получше... Верите или нет, но лучше всего будет использовать просто три оператора insert. Вы получите потрясающее ускорение. И ресурсов потребуется намного меньше. Я создам три таблицы t1, t2, t3 следующим образом: ops$tkyte@ORA920> create table t1( id int, data char(20) ); Table created. ops$tkyte@ORA920> create table t2( id int, fk_t1 int, data char(20) ); Table created. ops$tkyte@ORA920> create table t3( id int, fk_t2 int, data char(20) ); Table created. ops$tkyte@ORA920> insert into t1 select rownum, 'x' from big_table.big_table where rownum <= 400000; 400000 rows created. ops$tkyte@ORA920> insert into t2 2 select rownum, id, 'x' 3 from ( select id from t1 4 union all 5 select id from t1 6 union all 7 select id from t1 where mod(id,2) = 0 ); 1000000 rows created. ops$tkyte@ORA920> insert into t3 2 select rownum, id, 'x' 3 from (select id from t2 where mod(id,5) = 0); 200000 rows created. ops$tkyte@ORA920> create index t2_fk_idx on t2(fk_t1); Index created. ops$tkyte@ORA920> create index t3_fk_idx on t3(fk_t2); Index created. ops$tkyte@ORA920> analyze table t1 compute statistics for table for all indexes for all indexed columns; Table analyzed. ops$tkyte@ORA920> analyze table t2 compute statistics for table for all indexes for all indexed columns; Table analyzed. ops$tkyte@ORA920> analyze table t3 compute statistics for table for all indexes for all indexed columns; Table analyzed. Затем я содаю ваши таблицы x1, x2, x3: ops$tkyte@ORA920> create table x1a ( newid int, oldid int, data char(20) ); Table created. ops$tkyte@ORA920> create table x2a ( newid int, oldid int, fk_t1 int, data char(20) ); Table created. ops$tkyte@ORA920> create table x3a ( newid int, oldid int, fk_t2 int, data char(20) ); Table created. ops$tkyte@ORA920> create index x1a_idx on x1a(newid,oldid); Index created. ops$tkyte@ORA920> create index x2a_idx on x2a(newid,oldid); Index created. Я создал таблицы x1 и x1a, x2, x2a и т.д. - таблицы x1a, x2a... отличаются наличием индексов, что позволяет продемонстрировать мой подход к решению этой задачи. По таблицам x1, x2, x3 я создаю триггеры: ops$tkyte@ORA920> create or replace trigger x1_trigger 2 before insert on x1 for each row 3 begin 4 select s.nextval into :new.newid from dual; 5 end; 6 / Trigger created. Хотя использование триггеров и является самым худшим способом... Вот как я бы загружал данные в таблицы x1, x2, x3: ops$tkyte@ORA920> exec runstats_pkg.rs_start PL/SQL procedure successfully completed. ops$tkyte@ORA920> alter sequence S cache 1000000; Sequence altered. Подобный оператор alter sequence необходимо выполнять перед интенсивным использованием последовательности, - вас удивит, НАСКОЛЬКО это повысит производительность. ops$tkyte@ORA920> column S new_val S; ops$tkyte@ORA920> select s.nextval S from dual; S ---------- 1 ops$tkyte@ORA920> insert /*+ APPEND */ into x1a 2 select s.nextval, id, data from t1; 400000 rows created. ops$tkyte@ORA920> commit; Commit complete. ops$tkyte@ORA920> insert /*+ APPEND */ into x2a 2 select s.nextval, t2.id, x1.newid, t2.data 3 from t2, x1a x1 4 where x1.newid > &S 5 and x1.oldid = t2.fk_t1; old 4: where x1.newid > &S new 4: where x1.newid > 1 1000000 rows created. ops$tkyte@ORA920> commit; Commit complete. ops$tkyte@ORA920> insert /*+ APPEND */ into x3a 2 select s.nextval, t3.id, x2.newid, t3.data 3 from t3, x2a x2 4 where x2.newid > &S old 4: where x2.newid > &S new 4: where x2.newid > 1 200000 rows created. ops$tkyte@ORA920> commit; Commit complete. ops$tkyte@ORA920> alter sequence S cache 20; Sequence altered. ops$tkyte@ORA920> exec runstats_pkg.rs_middle; PL/SQL procedure successfully completed. Вот и весь процесс загрузки -- эти операторы делают все необходимое. Теперь ваш способ: ops$tkyte@ORA920> declare 2 l_newid_t1 int; 3 l_newid_t2 int; 4 begin 5 for x in ( select * from t1 ) 6 loop 7 insert into x1 ( oldid, data ) values ( x.id, x.data ) 8 returning newid into l_newid_t1; 9 for y in ( select * from t2 where t2.fk_t1 = x.id ) 10 loop 11 insert into x2 ( oldid, fk_t1, data ) values ( y.id, l_newid_t1, y.data ) 12 return newid into l_newid_t2; 13 for z in ( select * from t3 where t3.fk_t2 = y.id ) 14 loop 15 insert into x3 ( oldid, fk_t2, data ) values ( z.id, l_newid_t2, z.data ); 16 end loop; 17 end loop; 18 end loop; 19 end; 20 / PL/SQL procedure successfully completed. ops$tkyte@ORA920> exec runstats_pkg.rs_stop(100000); Run1 ran in 12183 hsecs Run2 ran in 235576 hsecs run 1 ran in 5.17% of the time Мой вариант работает почти в 20 раз быстрее (причем приходится поддерживать два дополнительных индекса!). Но, что еще важнее: Name Run1 Run2 Diff LATCH.checkpoint queue latch 36,430 141,104 104,674 LATCH.row cache enqueue latch 3,846 163,180 159,334 LATCH.dml lock allocation 370 160,329 159,959 LATCH.sequence cache 4,800,009 4,960,014 160,005 STAT...recursive cpu usage 63 199,915 199,852 STAT...CPU used by this sessio 5,509 217,812 212,303 STAT...CPU used when call star 5,509 217,812 212,303 STAT...Elapsed Time 12,270 235,598 223,328 LATCH.undo global data 19,681 253,812 234,131 STAT...enqueue releases 5,151 242,495 237,344 STAT...enqueue requests 5,151 242,499 237,348 LATCH.library cache pin alloca 2,989 323,616 320,627 LATCH.enqueue hash chains 10,545 487,183 476,638 LATCH.row cache objects 5,286 484,151 478,865 STAT...sorts (rows) 1,402,631 2,618 -1,400,013 STAT...consistent gets - exami 407,293 1,890,245 1,482,952 STAT...table scan rows gotten 400,001 2,000,000 1,599,999 STAT...table scans (short tabl 1 1,600,000 1,599,999 STAT...table scan blocks gotte 1,757 2,000,048 1,998,291 STAT...redo entries 52,903 2,215,021 2,162,118 LATCH.redo allocation 53,998 2,222,013 2,168,015 LATCH.session allocation 31 2,401,734 2,401,703 STAT...buffer is pinned count 2,600,058 0 -2,600,058 STAT...db block gets 102,375 3,161,828 3,059,453 STAT...no work - consistent re 1,207,558 4,402,253 3,194,695 STAT...db block changes 99,734 4,401,587 4,301,853 STAT...buffer is not pinned co 1,202,995 5,598,153 4,395,158 STAT...execute count 502 4,680,368 4,679,866 STAT...recursive calls 5,033 7,323,453 7,318,420 STAT...consistent gets 3,022,880 10,894,618 7,871,738 STAT...calls to get snapshot s 12,743 8,043,676 8,030,933 LATCH.shared pool 1,604,008 9,805,723 8,201,715 STAT...session logical reads 3,125,255 14,056,446 10,931,191 LATCH.library cache pin 3,204,928 16,245,960 13,041,032 LATCH.library cache 4,807,752 19,774,939 14,967,187 LATCH.cache buffers chains 6,112,094 33,752,275 27,640,181 STAT...redo size 127,584,104 606,190,928 478,606,824 Я просто показываю, что по некоторым показателям разница огромна. Посмотрите на значение REDO SIZE, посмотрите, насколько меньше требуется внутренних блокировок (показатели LATCH)! Это принципиальное отличие... Run1 latches total versus runs -- difference and pct Run1 Run2 Diff Pct 20,739,779 91,534,813 70,795,034 22.66% PL/SQL procedure successfully completed. Просто используйте 3 оператора insert, и все. Когда приходится писать процедурный код, остановитесь и задайте себе вопрос: "Зачем я это делаю?" Вопросы читателя от 13 июня 2003 годаТом, У меня возникли два вопроса относительно твоих операторов insert: ops$tkyte@ORA920> insert /*+ APPEND */ into x2a 2 select s.nextval, t2.id, x1.newid, t2.data 3 from t2, x1a x1 4 where x1.newid > &S 5 and x1.oldid = t2.fk_t1; old 4: where x1.newid > &S new 4: where x1.newid > 1 1000000 rows created. ops$tkyte@ORA920> commit; Commit complete. ops$tkyte@ORA920> insert /*+ APPEND */ into x3a 2 select s.nextval, t3.id, x2.newid, t3.data 3 from t3, x2a x2 4 where x2.newid > &S old 4: where x2.newid > &S new 4: where x2.newid Вопрос 1: Зачем нужна конструкция "x1.newid > &S" в конструкции where при вставке в таблицу x2a? Вопрос 2: Почему конструкция where "x2.oldid = t3.fk_t2;" отсутствует при вставке в таблицу x3a? Аналогичное условие, "x1.oldid = t2.fk_t1;", указано при вставке в таблицу x2a? Ответ Тома КайтаВопрос 1: Я предположил, что эта таблица X1 накапливается со временем и, поскольку таблица X1 будет содержать данные за последний месяц, а также данные за текущий месяц, значение x1.oldid будет дублироваться. Добавление условия по x1.newid ограничивает просмотр таблицы X1 только вновь добавленными данными. Вопрос 2: Вы очень внимательны. Это была ошибка копирования (неверно выбрана граница блока :). Правильно будет так: ops$tkyte@ORA920> insert /*+ APPEND */ into x3a 2 select s.nextval, t3.id, x2.newid, t3.data 3 from t3, x2a x2 4 where x2.newid > &S 5 and x2.oldid = t3.fk_t2; old 4: where x2.newid > &S new 4: where x2.newid > 1 200000 rows created. Обратите внимание, что в исходном тексте нет ни ';', ни '/' -- я потерял последнюю строку. Прекрасный примерТом, Ты используешь в своем примере непосредственную вставку ? Можно еще ускорить выполнение, если в ходе непосредственной вставки отключить журнализацию, но придется выполнить резервное копирование после завершения вставки, иначе все результаты вставки в случае сбоя могут быть поетеряны. Правильно? Ответ Тома КайтаДа, да и еще раз - да. Комментарий читателя от 15 июня 2003 годаКажется, я чего-то не понимаю! Если это действие необходимо выполнить в пакете, что придется изменить в твоем примере - я не понимаю, что это значит &S в конструкции where? Ответ Тома Кайта&S - значение последовательности перед началом множественной вставки. В PL/SQL-процедуре это может выглядеть так: select s.nextval into l_lo_seq from dual; insert /*+ APPEND */ into x2a select s.nextval, t2.id, x1.newid, t2.data from t2, x1a x1 where x1.newid > L_LO_SEQ and x1.oldid = t2.fk_t1; А что это за пакет runstats_pkg?Нельзя ли получить исходный код пакета runstats_pkg? Ответ Тома КайтаИщите ссылку на простой набор для тестирования (simple test harness) на этой странице (http://asktom.oracle.com/~tkyte/). Ссылки по теме
|
|