Cet article ne vous apprendra surement rien comme souvent. Il illustre, en revanche, assez bien, ce prix à payer. Un exemple a parfois plus de vertu qu'une longue discussion.
Evidemment ça dépend de votre machine, de vos structures, des volumes manipulés et de beaucoup d'autres facteurs ; faîtes donc ces tests vous même. Ça ne vous prendra pas plus de 5'. Et, si après ça, vous arrivez à transformer une seule boucle en SELECT, vous aurez contribué, vous aussi, à améliorer ce monde...
Scripts de test
Ne pipons pas les dés dès le départ. Pour être le plus conservateur possible, vous créez une simple table sans index ni contraintes. Vous pourrez ensuite être plus méchant...create table T1 (id number, text varchar2(1000)) ; set timing onVotre premier exemple consiste à insérer 10000 lignes dans la table à partir d'un select. Evidemment, le temps d'exécution du select n'est pas ce qu'on veut mesurer. L'ordre ci-dessous est généralement assez performant pour illustrer le propos, même si pour 1 millions de lignes, il y aurait plus efficace :
insert into T1
(select rownum id,
rpad('X',1000) text
from dual
connect by level <= 10000);
rollback;
Comparons avec le même "programme" avec une boucle en PL/SQL. begin
for i in (select rownum id,
rpad('X',1000) text
from dual
connect by level <= 10000) loop
insert into T1 values (i.id, i.text);
end loop;
end;
/
rollback;
Mais c'est vrai, le PL/SQL est un langage interprété alors pourquoi ne pas le compiler ?create or replace procedure insert_5000 is
begin
for i in (select rownum id,
rpad('X',1000) text
from dual
connect by level <= 10000) loop
insert into T1 values (i.id, i.text);
end loop;
end;
/
alter procedure insert_5000
compile plsql_code_type=native;
exec insert_5000
rollback;et pour finir, juste pour fixer les idées et bien qu'elle soit souvent inutilisable, considérez l'optimisation ci-dessous :alter table T1 nologging;
insert /*+ append */ into T1
(select rownum id,
rpad('X',1000) text
from dual
connect by level <= 10000);
rollback;Résultats
Voici donc les résultats moyens que j'ai obtenu, sur un serveur Linux x86_64 en 11.2.0.3 après une dizaine d'exécution par scénario.| Scénario | Temps (secondes) |
| INSERT (SELECT) | 0.3 |
| Boucle PL/SQL | 1.0 |
| Boucle PL/SQL compilée | 1.0 |
| INSERT (SELECT /*+ APPEND */) | 0.05 |

2 commentaires:
Et à la limite, ce n'est même pas le problème... Si vous traitez des volumes de données importants, regardez la différence entre un NESTED LOOP et un HASH JOIN !
Bonjour Gregory,
pour faire le tour de la question j'ajouterai la possibilité de faire du bulk load en pl/sql
comme ceci par exemple :
CREATE OR REPLACE PROCEDURE insert_5000_bulk
IS
TYPE ARRAY IS TABLE OF T1%ROWTYPE;
l_data ARRAY;
cursor c is select rownum id,
rpad('X',1000) text
from dual
connect by level <= 10000;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_data ;
FORALL i IN 1..l_data.COUNT
INSERT INTO t1 VALUES l_data(i);
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
END insert_5000_bulk;
/
j'arrive à des temps trés proches du sql dans mon cas même si cela reste supérieure bien évidemment mais en utilisant le hint ( APPEND_VALUES) comme ceci
CREATE OR REPLACE PROCEDURE insert_5000_bulk
IS
TYPE ARRAY IS TABLE OF T1%ROWTYPE;
l_data ARRAY;
cursor c is select rownum id,
rpad('X',1000) text
from dual
connect by level <= 10000;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_data ;
FORALL i IN 1..l_data.COUNT
INSERT /*+ APPEND_VALUES*/ INTO t1 VALUES l_data(i);
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
END insert_5000_bulk;
/
j'arrive pratiquement au même Temp d'execution que la solution pure sql sans append.
Enregistrer un commentaire