2009-12-29 7 views
3

J'écris souvent des datascrubs qui mettent à jour des millions de lignes de données. Les données résident dans une base de données MySQL OLTP 24x7x365 utilisant InnoDB. Les mises à jour peuvent nettoyer chaque ligne de la table (dans laquelle la base de données finit par acquérir un verrou au niveau de la table) ou peuvent simplement nettoyer 10% des lignes d'une table (ce qui peut toujours être dans les millions).Mise à jour de l'instruction MySQL UPDATE pour éviter les tailles TRX massives

Pour éviter de créer des tailles de transaction massives et de minimiser les conflits, je finis généralement par essayer de décomposer mon instruction UPDATE massive en une série de transactions UPDATE plus petites. Donc, je finis par écrire une construction de boucle qui limite ma clause de UPDATE WHERE comme ceci:

(avertissement: c'est juste pseudo-code pour obtenir le point à travers)

@batch_size=10000; 
@max_primary_key_value = select max(pk) from table1 

for (int i=0; i<[email protected]_primary_key_value; [email protected]_size) 
{ 
start transaction; 

update IGNORE table1 
set col2 = "flag set" 
where col2 = "flag not set" 
and pk > i 
and pk < [email protected]; 

commit; 
} 

Cette approche juste suce simplement pour si de nombreuses raisons.

Je souhaite émettre une instruction UPDATE sans que la base de données tente de regrouper tous les enregistrements en cours de mise à jour dans une seule unité de transaction. Je ne veux pas que la mise à jour réussisse ou échoue en tant qu'unité de travail unique. Si la moitié des lignes ne parviennent pas à mettre à jour ... pas de problème, faites le moi savoir. Essentiellement, chaque ligne est sa propre unité de travail, mais le traitement par lots ou la mise en cursus est la seule façon de comprendre comment représenter cela dans le moteur de base de données.

J'ai regardé la définition des niveaux d'isolation pour ma session, mais cela ne semble pas m'aider dans ce cas particulier.

D'autres idées?

Répondre

2

Peut-être pas la réponse que vous cherchez, mais vous pouvez simplifier un peu votre code en utilisant LIMIT dans la mise à jour.

Pseudo-code:

do { 
    update table1 set col2 = 'flag set' where col2 = 'flat not set' LIMIT 10000 
} while (ROW_COUNT() > 0) 
+0

Approche intéressante Eric. J'aime l'idée, mais elle est limitée aux mises à jour qui modifient réellement les valeurs de champs définies dans la clause WHERE (comme l'exemple que j'ai donné). Cependant, j'ai le même problème avec de grandes mises à jour qui ne correspondent pas à ce modèle (par exemple, set col1 = "foo" où col2 = "bar"). –

+0

@Matthew - Si c'est le cas, vous pouvez toujours modifier votre requête pour qu'elle soit quelque chose comme "set col1 = 'foo' où col2 = 'bar' et col1! '' Foo '" ce qui éviterait la sortie d'un lot de non -modification des mises à jour vous mettant hors de la boucle. –

0

Oui Eric ... vous avez raison (pour les cas simples qui ne comprennent pas où les clauses comme "où pas"). J'ai écrit une petite procédure qui me permet de fournir une instruction de mise à jour SQL et un nombre limite en tant que paramètres.

créer la procédure mass_update (DANS LE TEXTE updatestmt, IN batchsiz INT) BEGIN - OBJET: briser les grandes déclarations de mise à jour en lots pour limiter la taille des transactions et réduire les conflits - LIMITATIONS: fonctionne uniquement avec UPDATEs qui donneraient " 0 lignes affectées "lorsqu'il est exécuté DEUX FOIS!

SET @sql = CONCAT(updatestmt," LIMIT ", batchsiz); 
-- had to use CONCAT because "PREPARE stmt FROM" cannot accept dynamic LIMIT parameter 
-- reference: http://forums.mysql.com/read.php?98,75640,75640#msg-75640 
PREPARE stmt FROM @sql; 

select @sql; --display SQL to screen 
SET @cumrowcount=0; 
SET @batchnum=0; 
SET @now := now(); -- @now is a STRING variable... not a datetime 

    increment: repeat 
     SET @[email protected]+1; 
     EXECUTE stmt; 
     set @rowcount = ROW_COUNT(); 
     set @cumrowcount = @cumrowcount + @rowcount; 
     select @batchnum as "Iteration", 
       @cumrowcount as "Cumulative Rows", 
       TIMESTAMPDIFF(SECOND,STR_TO_DATE(@now,"%Y-%m-%d %H:%i:%s"),now()) as "Cumulative Seconds", 
       now() as "Timestamp"; 
     until @rowcount <= 0 
    end repeat increment; 

    DEALLOCATE PREPARE stmt; -- REQUIRED 
END 

Cela semble assez bien fonctionner et je peux courir avec une ancienne instruction UPDATE qui adhère à la règle « en cours d'exécution résultats deux fois dans 0 lignes affectées ».

Merci pour l'idée Eric!