Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Exécution de requêtes volumineuses en arrière-plan MS SQL

De mon point de vue, votre serveur a un sérieux problème de performances. Même si nous supposons qu'aucun des enregistrements de la requête

select some_col with (nolock) where id_col between 57000000 and 57001000

était en mémoire, cela ne devrait pas prendre 21 secondes pour lire séquentiellement les quelques pages du disque (votre index clusterisé sur l'id_col ne devrait pas être fragmenté s'il s'agit d'une auto-identité et que vous n'avez pas fait quelque chose de stupide comme ajouter un "desc" à la définition de l'index).

Mais si vous ne pouvez pas/ne voulez pas résoudre ce problème, mon conseil serait de faire la mise à jour en petits paquets comme 100-1000 enregistrements à la fois (selon le temps que la fonction de recherche consomme). Une mise à jour/transaction ne devrait pas prendre plus de 30 secondes.

Vous voyez que chaque mise à jour conserve un verrou exclusif sur tous les enregistrements qu'elle a modifiés jusqu'à ce que la transaction soit terminée. Si vous n'utilisez pas de transaction explicite, chaque instruction est exécutée dans un seul contexte de transaction automatique, de sorte que les verrous sont libérés lorsque l'instruction de mise à jour est terminée.

Mais vous pouvez toujours rencontrer des blocages de cette façon, en fonction de ce que font les autres processus. S'ils modifient également plus d'un enregistrement à la fois, ou même s'ils rassemblent et maintiennent des verrous en lecture sur plusieurs lignes, vous pouvez obtenir des interblocages.

Pour éviter les interblocages, votre instruction de mise à jour doit prendre un verrou sur tous les enregistrements qu'elle modifiera à la fois. Pour ce faire, placez l'instruction de mise à jour unique (avec seulement les quelques lignes limitées par id_col) dans une transaction sérialisable telle que

IF @@TRANCOUNT > 0
  -- Error: You are in a transaction context already

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

-- Insert Loop here to work "x" through the id range
  BEGIN TRANSACTION
    UPDATE SOMETABLE
      SET [some_col] = dbo.ufn_SomeFunction(CONVERT(NVARCHAR(500), another_column))
      WHERE [some_col] = 243 AND id_col BETWEEN x AND x+500 -- or whatever keeps the update in the small timerange
  COMMIT
-- Next loop

-- Get all new records while you where running the loop. If these are too many you may have to paginate this also:
BEGIN TRANSACTION
  UPDATE SOMETABLE
    SET [some_col] = dbo.ufn_SomeFunction(CONVERT(NVARCHAR(500), another_column))
    WHERE [some_col] = 243 AND id_col >= x
COMMIT

Pour chaque mise à jour, cela prendra un verrou de mise à jour/de plage de clés exclusive sur les enregistrements donnés (mais seulement eux, car vous limitez la mise à jour via la clé d'index en cluster). Il attendra la fin de toute autre mise à jour sur les mêmes enregistrements, puis obtiendra son verrou (provoquant le blocage de toutes les autres transactions, mais toujours uniquement pour les enregistrements donnés), puis mettra à jour les enregistrements et libérera le verrou.

La dernière instruction supplémentaire est importante, car elle prendra un verrou de plage de clés jusqu'à "l'infini" et empêchera ainsi même les insertions à la fin de la plage pendant l'exécution de l'instruction de mise à jour.