Une requête utilisée pour parcourir en boucle 17 millions d'enregistrements pour supprimer les doublons a fonctionné maintenant pour environ 16 heures et je voulais savoir si la requête est arrêtée maintenant si elle finalisera les instructions de suppression ou si elle a été supprimée 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 trouvé que quand je fais un
select count(*) from myTable
Que les lignes qu'il retourne (en faisant cette requête) est environ 5 moins que le nombre de lignes de départ. De toute évidence, les ressources du serveur sont extrêmement maigres. Cela signifie-t-il qu'il a fallu 16 heures à ce processus pour trouver 5 doublons (quand il y en a en réalité des milliers), et cela pourrait durer plusieurs 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?
Ci-dessous la requête:
--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
non, le serveur SQL 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 enregistrement 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 une transaction et que vous annulez cette transaction ou que la connexion se ferme 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 essentiellement si les spécifications de votre boîte ne sont pas à la hauteur, vous pourriez être coincé à attendre.
à l'avenir, vous devriez créer un index unique sur la table pour vous éviter d'avoir à passer par là à nouveau.
Votre requête n'est pas encapsulée dans une transaction, elle n'annulera donc pas les modifications déjà effectuées par les instructions de suppression individuelles.
Je l'ai spécifiquement testé moi-même sur mon propre serveur SQL Server à l'aide de la requête suivante, et la table ApplicationLog était vide même si j'ai 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, beaucoup plus longtemps que 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 2000 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 dans transactions auto-validées mode. 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 . Cependant, étant donné qu'une instruction WHILE ne peut pas modifier directement la base de données, il semblerait logique que ne - pas lance une transaction à validation automatique.
Si aucune "transaction implicite" n'a été définie, chaque itération de votre boucle enregistre les modifications.
Il est possible que tout 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 le volet de requête> options), par défaut dans le client ou dans une instruction SET.
SET IMPLICIT_TRANSACTIONS ON;
Quoi qu'il en soit, si tel était le cas, vous auriez toujours besoin d'exécuter explicitement COMMIT/ROLLBACK, quelle que soit l'interruption de l'exécution de la requête.
Référence des transactions implicites:
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 du calendrier, nous avons calculé qu'il faudrait UN AN pour les faire correspondre à toutes.
Pour expérimenter à 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 plats dans un programme C++, créer mes propres index et y faire de la logique floue. puis réimportez les fichiers plats dans la base de données. Ce qui a pris un an en SQL a pris environ 30 secondes dans l'application C++.
Donc, mon conseil est, n'essayez même pas ce que vous faites en SQL. Exporter, traiter, réimporter.
Les DELETES exécutées jusque-là 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 propose 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 a 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.
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 Management Studio.
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.
Je pense que cette requête serait beaucoup plus efficace si elle était réécrite en utilisant un algorithme à une passe 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 considérer sérieusement votre méthodologie. Vous devez commencer à penser par jeux (bien que, pour des raisons de performances, vous ayez peut-être besoin d'un traitement par lots, mais pas ligne par ligne 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 pouvoir 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
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 déclaration, comme indiqué dans les suggestions de votre question précédente à ce sujet.
Si vous ne l'exécutez pas explicitement dans une transaction, cela annulera simplement l'instruction en cours d'exécution.