2010-04-14 4 views
28

J'ai une table avec beaucoup d'enregistrements (pourrait être plus de 500 000 ou 1 000 000). J'ai ajouté une nouvelle colonne dans ce tableau et j'ai besoin de remplir une valeur pour chaque ligne de la colonne, en utilisant la valeur de ligne correspondante d'une autre colonne dans cette table.Manière efficace de mettre à jour toutes les lignes d'un tableau

J'ai essayé d'utiliser des transactions distinctes pour sélectionner chaque morceau suivant de 100 enregistrements et mettre à jour la valeur pour eux, mais cela prend des heures pour mettre à jour tous les enregistrements Oracle10 par exemple.

Quel est le moyen le plus efficace de le faire en SQL, sans utiliser certaines fonctionnalités spécifiques au dialecte, pour qu'il fonctionne partout (Oracle, MSSQL, MySQL, PostGre, etc.)?

INFORMATIONS SUPPLÉMENTAIRES: Il n'y a pas de champs calculés. Il y a des index. Les instructions SQL générées utilisées mettent à jour la table ligne par ligne.

+0

Peu de gens GOTO, l'extrême disabiling 'INDEX' es si elles sont présentes sur la mise à jour/colonne instered, et laisser le travail de nuit, il analyse . – Guru

+1

Nous avons besoin de plus d'informations. Parlez-nous du schéma de la table ... des colonnes "calculées"? Des index? 500k - 1m lignes n'est pas un grand nombre de dossiers par tout étirement. – Timothy

+0

Merci à tous pour la réponse rapide. J'ai ignoré la partie que j'utilise les instructions SQL générées. Maintenant, j'ai regardé en profondeur et il semble que les mises à jour SQL générées rangée par rangée! Donc, toute tentative de séparer en blocs de 100 enregistrements était vide de sens ... Je vais changer le code pour générer une instruction SQL UPDATE correcte, comme dans la réponse acceptée. –

Répondre

42

La manière habituelle est d'utiliser MISE À JOUR:

UPDATE mytable 
    SET new_column = <expr containing old_column> 

Vous devriez être en mesure de le faire est une seule transaction.

+0

Il semble que l'OP sache comment faire cela en une seule transaction, mais il y a un problème de performance, alors il a essayé de le traiter en plusieurs transactions. – Timothy

+1

C'est possible, mais il est extraordinaire que 1 M lignes prennent autant de temps pour mettre à jour une seule colonne. Il est également possible que l'OP mette à jour un enregistrement à la fois, soit parce qu'il ne comprend pas les opérations, soit parce qu'il essaie de calculer la nouvelle valeur dans le code client (soit par nécessité, soit encore par manque de compréhension) . Quoi qu'il en soit, je pourrai mettre à jour ma réponse si le PO indique lequel des cas précédents s'applique à eux. –

+0

Assez juste. Je suis d'accord que plus d'informations sont nécessaires. – Timothy

2

Vous pouvez déposer tous les index sur la table, puis faites votre insert, puis recréer les index.

+0

+1. Il était temps de suggérer cela, mais oui, pour 10M ou plus de lignes, vous pouvez faire aussi longtemps que vous les faites rapidement et rapidement. – Guru

+4

Pour l'amour de tous les dieux que vous vénérer, _do this at a quiet time_. Sinon, vos utilisateurs vous traqueront, vous tortureront, vous tueront, vous écraseront, goudronneront et plumeront les restes puis les brûleront et cracheront sur vos parties carbonisées. Au minimum. Ils vont probablement faire pire. – paxdiablo

+1

Cela ressemble aux gémissements d'un DBA qui a été sacrifié sur l'autel de la performance ... – Timothy

0

Vous ne pourriez pas travailler pour vous, mais une technique que j'ai utilisée plusieurs fois dans le passé pour des circonstances similaires.

créé updated_ {nom_table}, puis sélectionnez l'insertion dans ce tableau par lots. Une fois terminé, cela dépend d'Oracle (que je ne connais pas ou n'utilise pas) qui supporte la possibilité de renommer les tables de manière atomique. updated_ {table_name} devient {table_name} tandis que {table_name} devient original_ {table_name}.

La dernière fois que je devais faire était pour une très table indexée avec plusieurs millions de lignes qui ne pouvaient pas être verrouillés absolument positive pendant toute la durée nécessaire pour faire des changements importants à elle.

8

Comme Marcelo suggère:

UPDATE mytable 
SET new_column = <expr containing old_column>; 

Si cela prend trop de temps et échoue en raison d'erreurs « de l'instantané trop vieux » (par exemple, si l'expression interroge une autre table hautement actif), et si la nouvelle valeur pour la colonne est toujours NOT NULL, vous pouvez mettre à jour la table en lots:

UPDATE mytable 
SET new_column = <expr containing old_column> 
WHERE new_column IS NULL 
AND ROWNUM <= 100000; 

il suffit de lancer cette déclaration, COMMIT, puis exécutez à nouveau; rincer, répétez jusqu'à ce qu'il rapporte "0 lignes mises à jour". Cela prendra plus de temps mais chaque mise à jour est moins susceptible d'échouer.

EDIT:

Une meilleure alternative qui devrait être plus efficace est d'utiliser l'API DBMS_PARALLEL_EXECUTE.

Exemple de code (de docs Oracle):

DECLARE 
    l_sql_stmt VARCHAR2(1000); 
    l_try NUMBER; 
    l_status NUMBER; 
BEGIN 

    -- Create the TASK 
    DBMS_PARALLEL_EXECUTE.CREATE_TASK ('mytask'); 

    -- Chunk the table by ROWID 
    DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_ROWID('mytask', 'HR', 'EMPLOYEES', true, 100); 

    -- Execute the DML in parallel 
    l_sql_stmt := 'update EMPLOYEES e 
     SET e.salary = e.salary + 10 
     WHERE rowid BETWEEN :start_id AND :end_id'; 
    DBMS_PARALLEL_EXECUTE.RUN_TASK('mytask', l_sql_stmt, DBMS_SQL.NATIVE, 
           parallel_level => 10); 

    -- If there is an error, RESUME it for at most 2 times. 
    l_try := 0; 
    l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask'); 
    WHILE(l_try < 2 and l_status != DBMS_PARALLEL_EXECUTE.FINISHED) 
    LOOP 
    l_try := l_try + 1; 
    DBMS_PARALLEL_EXECUTE.RESUME_TASK('mytask'); 
    l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask'); 
    END LOOP; 

    -- Done with processing; drop the task 
    DBMS_PARALLEL_EXECUTE.DROP_TASK('mytask'); 

END; 
/

Docs Oracle: https://docs.oracle.com/database/121/ARPLS/d_parallel_ex.htm#ARPLS67333

+0

Je pense que c'est une bonne idée pour des tables très grandes et très utilisées! Je n'ai pas encore eu de tels échecs, mais vous avez +1 :) –

+1

Quelque chose me dit que cela évite également de verrouiller toute la table pendant longtemps. Avec cela, seul un petit morceau sera verrouillé pour la durée d'un seul passage. –

Questions connexes