4

Je travaille sur une procédure qui mettra à jour un grand nombre d'éléments sur un serveur distant, en utilisant des enregistrements d'une base de données locale. Voici le pseudo-code.Transactions en boucle dans la procédure stockée

CREATE PROCEDURE UpdateRemoteServer 
    pre-processing 
    get cursor with ID's of records to be updated 
    while on cursor 
     process the item 

Peu importe combien nous optimisons, la routine va prendre un certain temps, donc nous ne voulons pas la chose à traiter comme une seule transaction. Les éléments sont marqués après avoir été traités, il devrait donc être possible de reprendre là où nous nous sommes arrêtés si le processus est interrompu.

Envelopper le contenu de la boucle (« traiter l'élément ») dans begin/commit tran ne fait pas l'affaire ... il semble que toute la déclaration

EXEC UpdateRemoteServer 

est traité comme une seule transaction . Comment puis-je faire de chaque processus d'article une transaction complète et distincte?

Notez que j'aimerais exécuter ces comme « mises à jour non-traitées », mais cette option est disponible (pour autant que je sache) en 2008.

Répondre

1

EDIT: comme souligné par Remus ci-dessous, les curseurs n'ouvrent PAS une transaction par défaut; ainsi, ce n'est pas la réponse à la question posée par le PO. Je pense toujours qu'il existe de meilleures options qu'un curseur, mais cela ne répond pas à la question.

Stu

RÉPONSE ORIGINAL:

Le symptôme spécifique que vous décrivez est dû au fait qu'un curseur ouvre une transaction par défaut, donc peu importe la façon dont vous travaillez, vous allez avoir une transaction de longue durée tant que vous utilisez un curseur (sauf si vous évitez complètement les verrous, ce qui est une autre mauvaise idée).

Comme d'autres le font remarquer, les curseurs SUCK. Vous n'en avez pas besoin pendant 99,9999% du temps.

Vous avez vraiment deux options si vous voulez le faire au niveau de la base de données avec SQL Server:

  1. Utilisez SSIS pour effectuer vos opérations; très rapide, mais peut ne pas être disponible dans votre propre saveur de SQL Server. Parce que vous avez affaire à des serveurs distants et que la connectivité vous inquiète, vous devrez peut-être utiliser un mécanisme de bouclage. Utilisez plutôt WHILE et validez les batchs à la fois. Bien que WHILE ait de nombreux problèmes identiques à ceux d'un curseur (la création de boucles aspire toujours dans SQL), vous évitez de créer la transaction externe.

Stu

+0

Merci pour l'info. Cela répond effectivement à la question. En dehors de cela, je sais que les curseurs sucent. Dans ce cas, ce n'est pas le curseur qui cause le problème de performance * en soi *, mais le travail effectué à l'intérieur de la boucle. Mais c'est une question distincte. – harpo

+0

Je ne suis pas d'accord avec "cursors suck", mais c'est une discussion différente. – Thorsten

+0

@IronGoofy: vous avez raison; Les curseurs sont des outils spécialisés qui doivent être utilisés dans une circonstance spécialisée (comme un extracteur de vis, vous n'en avez pas besoin tous les jours, si vous le faites, vous faites quelque chose de mal). Dire "ils sucent" est plus facile. :) –

0

sont-yo TOURNE, uniquement à partir de serveur SQL, ou d'une application? Si c'est le cas, obtenez la liste à traiter, puis faites une boucle dans l'application pour ne traiter que les sous-ensembles requis.

la transaction doit être traitée par votre application, et ne doit bloquer les éléments mis à jour/pages les articles sont.

+0

Malheureusement, cela se fait uniquement à partir de SQL Server, et cette exigence est hors de mon contrôle. – harpo

+0

est-il poule encore traité comme une transaction sigle si vous ne deviez pas exécuter à partir d'un sp mais plutôt simplement tsql?avaoid le sp, et utiliser les inners, avec les transactions liées à une session tsql? –

+0

Nous pensons ... le problème est que le processus doit être automatisé, il doit donc être exécutable à partir d'une seule commande. – harpo

0

processus jamais un élément à la fois dans une boucle lorsque vous faites un travail transactionnel. Vous pouvez faire défiler les groupes de traitement des enregistrements, mais ne jamais faire un seul enregistrement à la fois. Faites plutôt des inserts basés sur des sets et votre performance passera de quelques heures à quelques minutes, voire quelques secondes. Si vous utilisez un curseur pour insérer la mise à jour ou supprimer et qu'il ne gère pas au moins 1000 rowa dans chaque instruction (pas une à la fois), vous faites la mauvaise chose. Les curseurs sont une pratique extrêmement pauvre pour une telle chose.

+2

Bien sûr, j'utiliserais des inserts basés sur des sets si je le pouvais. Je traite un seul élément à la fois, car chaque élément comporte un certain nombre de tables enfant associées qui doivent être mises à jour. Chaque itération doit donc traiter des dizaines d'enregistrements et ne peut pratiquement pas être réécrite pour traiter toute la mise à jour en même temps. J'ai jusqu'ici résisté à une politique de downvoting ce type très commun de non-réponse (c.-à-d., «Votre prémisse entière est fausse, vous ne devriez pas faire ceci»), mais je me demande que leurs auteurs ne les placent pas dans les commentaires . – harpo

3

La procédure EXEC ne fait pas créer une transaction. Un test très simple montrera ceci:

create procedure usp_foo 
as 
begin 
    select @@trancount; 
end 
go 

exec usp_foo; 

La @@ trancount l'intérieur usp_foo est 0, donc l'instruction EXEC ne démarre pas une transaction implicite. Si vous avez commencé une transaction en entrant UpdateRemoteServer cela signifie que quelqu'un a commencé cette transaction, je ne peux pas dire qui. Ceci étant dit, l'utilisation de serveurs distants et de DTC pour mettre à jour des éléments va avoir de très mauvais résultats. L'autre serveur est-il également SQL Server 2005 au moins? Vous pouvez peut-être mettre en file d'attente les demandes de mise à jour et utiliser messaging entre le serveur local et le serveur distant et demander au serveur distant d'effectuer les mises à jour en fonction des informations du message. Cela fonctionnerait nettement mieux car les deux serveurs ne doivent traiter que des transactions locales et vous bénéficiez d'une meilleure disponibilité en raison du couplage lâche de la messagerie en file d'attente.

Mise à jour

ne fait Curseurs commencent pas les transactions. Le traitement par lots basé sur un curseur typique est généralement basé sur des curseurs et des mises à jour de lots dans des transactions d'une certaine taille. Ceci est assez commun pour les travaux de nuit, car il permet de meilleures performances (flux de vidage de journal en raison de la taille plus grande des transactions) et les travaux peuvent être interrompus et repris sans perdre de temps.Une version simplifiée d'une boucle de traitement par lots est généralement comme ceci:

create procedure usp_UpdateRemoteServer 
as 
begin 
    declare @id int, @batch int; 
    set nocount on; 
    set @batch = 0; 

    declare crsFoo cursor 
    forward_only static read_only 
    for 
    select object_id 
    from sys.objects; 

    open crsFoo; 

    begin transaction 
    fetch next from crsFoo into @id ; 
    while @@fetch_status = 0 
    begin 

    -- process here 

    declare @transactionId int; 
    SELECT @transactionId = transaction_id 
     FROM sys.dm_tran_current_transaction; 
    print @transactionId; 

    set @batch = @batch + 1 
    if @batch > 10 
    begin 
     commit; 
     print @@trancount; 
     set @batch = 0; 
     begin transaction; 
    end 
    fetch next from crsFoo into @id ; 
    end 
    commit; 
    close crsFoo; 

    deallocate crsFoo; 
end 
go 

exec usp_UpdateRemoteServer; 

J'omises l'erreur partie de manipulation (commencer à essayer/commencer à attraper) et la fantaisie @@ contrôles FETCH_STATUS (curseurs statiques en fait ne pas besoin eux de toute façon). Ce code de démonstration montre qu'au cours de l'exécution, plusieurs transactions différentes ont été lancées (différents ID de transaction). Plusieurs fois, les lots sont également traités pour qu'ils puissent passer en toute sécurité un élément provoquant une exception, en utilisant un modèle similaire à celui de mon lien, mais cela ne s'applique pas aux transactions distribuées puisque les points de sauvegarde et DTC ne se mélangent pas.

+0

Vous avez raison sur la performance ... J'aurais aimé le savoir quand nous avons choisi cette technique. Néanmoins, nous ne sommes pas à un endroit où nous pouvons réécrire le tout, alors résoudre ce problème rendra la routine actuelle acceptable pour le moment. – harpo

+0

Juste couru votre proc de test, et je suis d'accord; curseurs apparemment ne pas maintenir une transaction tout au long. J'ai même supprimé les options spécifiées pour obtenir un curseur pouvant être mis à jour de façon dynamique, et les transactions ont encore changé. Je vais annuler ma réponse (si ce n'est pas ma position sur l'utilisation du curseur). –

+0

Remus, corrigez-moi si je me trompe mais une fois que vous commencez à modifier les données entre les serveurs (via un serveur lié), SQL Server ne démarre pas automatiquement une transaction distribuée implicite si la modification du système distant échoue. correctement renvoyé au SQL Server appelant? – mrdenny

0

Juste une idée ..

  • Traiter seulement quelques éléments lorsque la procédure est appelée (par exemple, obtenir que les 10 TOP articles à traiter)
  • processus les

Espérons que ce sera la fin de la transaction. Ensuite, écrivez un wrapper qui appelle la procédure tant qu'il y a plus de travail à faire (soit utilisez un simple count (..) pour voir s'il y a des items ou si la procédure retourne true indiquant qu'il y a plus de travail à faire faire.

je ne sais pas si cela fonctionne, mais peut-être l'idée est utile.