Si j'arrête une requête longue en cours d'exécution, est-ce que cette dernière est annulée?

StackOverflow https://stackoverflow.com/questions/161960

  •  03-07-2019
  •  | 
  •  

Question

Une requête utilisée pour parcourir 17 millions d'enregistrements afin de supprimer les doublons est en cours d'exécution depuis environ 16 heures et je voulais savoir si la requête était arrêtée à droite. Maintenant, s'il va finaliser les instructions de suppression ou s'il a été supprimé lors de l'exécution de cette requête? En effet, si je l’arrête, finit-il les suppressions ou les annulations?

J'ai constaté que lorsque je faisais une

 select count(*) from myTable

Le nombre de lignes renvoyées (lors de l'exécution de cette requête) est environ 5 fois inférieur à celui du nombre de lignes de départ. Il est évident que les ressources du serveur sont extrêmement maigres. Cela signifie-t-il donc que ce processus a pris 16 heures pour trouver 5 doublons (quand il y en a en réalité des milliers), et cela pourrait fonctionner pendant des jours?

Cette requête a pris 6 secondes sur 2 000 lignes de données de test, et fonctionne parfaitement sur cet ensemble de données. J'ai donc pensé que cela prendrait 15 heures pour l'ensemble complet.

Des idées?

La requête est la suivante:

--Declare the looping variable
DECLARE @LoopVar char(10)


    DECLARE
     --Set private variables that will be used throughout
      @long DECIMAL,
      @lat DECIMAL,
      @phoneNumber char(10),
      @businessname varchar(64),
      @winner char(10)

    SET @LoopVar = (SELECT MIN(RecordID) FROM MyTable)

    WHILE @LoopVar is not null
    BEGIN

      --initialize the private variables (essentially this is a .ctor)
      SELECT 
        @long = null,
        @lat = null,
        @businessname = null,
        @phoneNumber = null,
        @winner = null

      -- load data from the row declared when setting @LoopVar  
      SELECT
        @long = longitude,
        @lat = latitude,
        @businessname = BusinessName,
        @phoneNumber = Phone
      FROM MyTable
      WHERE RecordID = @LoopVar

      --find the winning row with that data. The winning row means 
      SELECT top 1 @Winner = RecordID
      FROM MyTable
      WHERE @long = longitude
        AND @lat = latitude
        AND @businessname = BusinessName
        AND @phoneNumber = Phone
      ORDER BY
        CASE WHEN webAddress is not null THEN 1 ELSE 2 END,
        CASE WHEN caption1 is not null THEN 1 ELSE 2 END,
        CASE WHEN caption2 is not null THEN 1 ELSE 2 END,
        RecordID

      --delete any losers.
      DELETE FROM MyTable
      WHERE @long = longitude
        AND @lat = latitude
        AND @businessname = BusinessName
        AND @phoneNumber = Phone
        AND @winner != RecordID

      -- prep the next loop value to go ahead and perform the next duplicate query.
      SET @LoopVar = (SELECT MIN(RecordID) 
    FROM MyTable
    WHERE @LoopVar < RecordID)
    END
Était-ce utile?

La solution

Non, SQL Server n'annulera pas les suppressions qu'il a déjà effectuées si vous arrêtez l'exécution de la requête. oracle nécessite un engagement explicite des requêtes d’action ou les données sont annulées, mais pas mssql.

avec SQL Server, il ne sera pas restauré sauf si vous exécutez spécifiquement dans le contexte d’une transaction et si vous annulez cette transaction ou si la connexion est fermée sans que la transaction ait été validée. mais je ne vois pas de contexte de transaction dans votre requête ci-dessus.

vous pouvez également essayer de restructurer votre requête pour rendre les suppressions un peu plus efficaces, mais si les spécifications de votre boîte ne sont pas à la hauteur, vous risquez de rester bloqué à l'attendre.

à l'avenir, vous devez créer un index unique sur la table pour ne pas avoir à le revoir.

Autres conseils

Votre requête n'est pas encapsulée dans une transaction. Par conséquent, les modifications déjà effectuées par les instructions de suppression individuelles ne seront pas annulées.

Je l'ai spécifiquement testé moi-même sur mon propre serveur SQL Server à l'aide de la requête suivante. La table ApplicationLog était vide bien que j'aie annulé la requête:

declare @count int
select @count = 5
WHILE @count > 0
BEGIN
  print @count
  delete from applicationlog;
  waitfor time '20:00';
  select @count = @count -1
END

Toutefois, votre requête risque de prendre plusieurs jours, voire plusieurs semaines, bien au-delà de 15 heures. Votre estimation selon laquelle vous pouvez traiter 2000 enregistrements toutes les 6 secondes est fausse, car chaque itération de votre boucle while prend beaucoup plus de temps avec 17 millions de lignes que de 2000 lignes. Donc, à moins que votre requête ne prenne beaucoup moins d'une seconde sur 2 000 lignes, il faudra plusieurs jours pour 17 millions.

Vous devriez poser une nouvelle question sur la manière de supprimer efficacement les doublons.

Si vous ne faites rien d’explicite sur les transactions, la connexion sera alors transactions auto-validées . Dans ce mode, chaque instruction SQL est considérée comme une transaction.

La question est de savoir si cela signifie que les instructions SQL individuelles sont des transactions et sont donc validées au fur et à mesure, ou si la boucle WHILE externe compte comme une transaction.

Il ne semble pas y avoir de discussion à ce sujet dans la description de la construction WHILE sur MSDN . Toutefois, une instruction WHILE ne pouvant pas modifier directement la base de données, il semblerait logique de ne pas démarrer une transaction à validation automatique.

Transactions implicites

Si aucune "transaction implicite" n'a été définie, chaque itération de votre boucle enregistre les modifications.

Il est possible que n'importe quel serveur SQL soit défini avec des "transactions implicites". Ceci est un paramètre de base de données (par défaut, il est désactivé). Vous pouvez également avoir des transactions implicites dans les propriétés d'une requête particulière dans Management Studio (clic droit dans les options du volet de requête >), par défaut dans le client ou dans une instruction SET.

SET IMPLICIT_TRANSACTIONS ON;

Dans tous les cas, si tel était le cas, vous auriez toujours besoin d'exécuter un COMMIT / ROLLBACK explicite, quelle que soit l'interruption de l'exécution de la requête.

Référence des transactions implicites:

http://msdn.microsoft.com/en-us/library /ms188317.aspx

http://msdn.microsoft.com/en-us/library /ms190230.aspx

J'ai hérité d'un système qui avait une logique similaire à la vôtre implémentée en SQL. Dans notre cas, nous essayions de lier des lignes à l’aide d’une correspondance floue ayant des noms / adresses similaires, etc., et cette logique s’effectuait uniquement en SQL. Au moment où j'en ai hérité, nous avions environ 300 000 lignes dans la table et, en fonction des horaires, nous avons calculé qu'il faudrait UN AN pour les comparer.

Comme expérience pour voir à quel point je pouvais le faire plus rapidement en dehors de SQL, j’ai écrit un programme pour vider la table de la base de données en fichiers plats, lire les fichiers à plat dans un programme C ++, créer mes propres index et effectuer des tâches floues. la logique là-bas, puis réimporter les fichiers à plat dans la base de données. Ce qui a pris un an en SQL a pris environ 30 secondes dans l’application C ++.

Mon conseil est donc: n'essayez même pas de faire ce que vous faites en SQL. Exporter, traiter, réimporter.

Les suppressions effectuées jusqu'à présent ne seront pas restaurées.

En tant qu'auteur du code en question , et après avoir émis l’avertissement que les performances dépendront des index, je proposerais les éléments suivants pour accélérer le processus.

RecordId vaut mieux être PRIMARY KEY. Je ne veux pas dire IDENTITÉ, je veux dire PRIMARY KEY. Confirmez cela en utilisant sp_help

Certains index doivent être utilisés pour évaluer cette requête. Déterminez laquelle de ces quatre colonnes contient le moins de répétitions et indexez ...

SELECT *
FROM MyTable
WHERE @long = longitude
  AND @lat = latitude
  AND @businessname = BusinessName
  AND @phoneNumber = Phone

Avant et après l'ajout de cet index, vérifiez le plan de requête pour voir si l'analyse de l'index a été ajoutée.

En tant que boucle, votre requête aura du mal à s’adapter, même avec les index appropriés. La requête doit être réécrite en une seule instruction, conformément aux suggestions figurant dans votre question précédente à ce sujet.

Si vous ne l'exécutez pas explicitement dans une transaction, cela annulera uniquement l'instruction en cours d'exécution.

Je pense que cette requête serait beaucoup plus efficace si elle était réécrite en utilisant un algorithme à un seul passage en utilisant un curseur. Vous voudriez commander votre table de curseur par longitude, latitude, BusinessName AND @phoneNumber. Vous parcourez les rangées une par une. Si une ligne a la même longitude, la même latitude, le même nom d’entreprise et le même numéro de téléphone que la ligne précédente, supprimez-la.

Je pense que vous devez examiner sérieusement votre méthodologie.  Vous devez commencer à penser par ensembles (bien que, pour des raisons de performances, vous ayez peut-être besoin d'un traitement par lots, mais pas d'une ligne à la fois sur une table d'enregistrements de 17 millions.)

D'abord, tous vos enregistrements ont-ils des doublons? Je suppose que non, alors la première chose que vous voulez faire est de limiter votre traitement aux enregistrements qui ont des doublons. S'agissant d'une table volumineuse, il est possible que vous deviez éventuellement supprimer les lots par lots au fil du temps, en fonction des autres traitements en cours, commencez par extraire les enregistrements que vous souhaitez traiter dans une table à indexer ensuite. Vous pouvez également utiliser une table temporaire si vous voulez le faire en même temps sans jamais l'arrêter autrement. Créez une table dans votre base de données et déposez-la à la fin.

Quelque chose comme (notez que je n'ai pas écrit les instructions de création d'index, je suppose que vous pouvez le vérifier vous-même):

SELECT min(m.RecordID), m.longitude, m.latitude, m.businessname, m.phone  
     into  #RecordsToKeep    
FROM MyTable   m
join 
(select longitude, latitude, businessname, phone
from MyTable
group by longitude, latitude, businessname, phone
having count(*) >1) a 
on a.longitude = m.longitude and a.latitude = m.latitude and
a.businessname = b.businessname and a.phone = b.phone 
group by  m.longitude, m.latitude, m.businessname, m.phone   
ORDER BY CASE WHEN m.webAddress is not null THEN 1 ELSE 2 END,        
    CASE WHEN m.caption1 is not null THEN 1 ELSE 2 END,        
    CASE WHEN m.caption2 is not null THEN 1 ELSE 2 END



while (select count(*) from #RecordsToKeep) > 0
begin
select top 1000 * 
into #Batch
from #RecordsToKeep

Delete m
from mytable m
join #Batch b 
        on b.longitude = m.longitude and b.latitude = m.latitude and
        b.businessname = b.businessname and b.phone = b.phone 
where r.recordid <> b.recordID

Delete r
from  #RecordsToKeep r
join #Batch b on r.recordid = b.recordid

end

Delete m
from mytable m
join #RecordsToKeep r 
        on r.longitude = m.longitude and r.latitude = m.latitude and
        r.businessname = b.businessname and r.phone = b.phone 
where r.recordid <> m.recordID

Essayez également de penser à une autre méthode pour supprimer les lignes en double:

delete t1 from table1 as t1 where exists (
    select * from table1 as t2 where
        t1.column1=t2.column1 and
        t1.column2=t2.column2 and
        t1.column3=t2.column3 and
        --add other colums if any
        t1.id>t2.id
)

Je suppose que vous avez une colonne id entière dans votre table.

Si votre ordinateur ne dispose pas de matériel très avancé, le serveur SQL peut mettre très longtemps à exécuter cette commande. Je ne sais pas avec certitude comment cette opération est exécutée sous le capot, mais selon mon expérience, cela pourrait être fait plus efficacement en faisant entrer les enregistrements de la base de données et en mémoire pour un programme qui utilise une structure arborescente avec une règle de suppression des doublons. pour l'insertion. Essayez de lire l'intégralité de la table sous forme de tronçons (disons 10 000 lignes à la fois) dans un programme C ++ utilisant ODBC. Une fois dans le programme C ++, utilisez et std :: map où clé est la clé unique et struct est une structure qui contient le reste des données dans des variables. Faites une boucle sur tous les enregistrements et effectuez une insertion dans la carte. La fonction d'insertion de carte gérera la suppression des doublons. Comme la recherche dans une carte est lg (n) fois beaucoup moins de temps pour trouver des doublons que d'utiliser votre boucle while. Vous pouvez ensuite supprimer la table entière et rajouter les n-uplets dans la base de données à partir de la carte en formant des requêtes d’insertion et en les exécutant via odbc ou en construisant un script de fichier texte et en l’exécutant dans le studio de gestion.

Je suis à peu près sûr que c'est une négation. Sinon, quel serait l'intérêt des transactions?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top