Requête de suppression de doublons SQL sur des millions de lignes pour des performances
-
03-07-2019 - |
Question
Cela a été une aventure. J'ai commencé avec la requête de duplication en boucle située dans la question précédente , mais chaque boucle couvrirait la totalité des 17 millions d'enregistrements , signifie que cela prendrait des semaines (exécuter simplement * select count * sur MyTable *
prend 4h30 minutes à mon serveur avec MSSQL 2005). J'ai lu des informations sur ce site et sur ce post .
Et sommes arrivés à la requête ci-dessous. La question est de savoir si le type de requête à exécuter sur 17 millions d'enregistrements est correct, quel que soit le type de performance. Si ce n’est pas le cas, qu’est-ce?
SQL QUERY:
DELETE tl_acxiomimport.dbo.tblacxiomlistings
WHERE RecordID in
(SELECT RecordID
FROM tl_acxiomimport.dbo.tblacxiomlistings
EXCEPT
SELECT RecordID
FROM (
SELECT RecordID, Rank() over (Partition BY BusinessName, latitude, longitude, Phone ORDER BY webaddress DESC, caption1 DESC, caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
) al WHERE Rank = 1)
La solution
Voir le QueryPlan aiderait.
Est-ce faisable?
SELECT m.*
into #temp
FROM tl_acxiomimport.dbo.tblacxiomlistings m
inner join (SELECT RecordID,
Rank() over (Partition BY BusinessName,
latitude,
longitude,
Phone
ORDER BY webaddress DESC,
caption1 DESC,
caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
) al on (al.RecordID = m.RecordID and al.Rank = 1)
truncate table tl_acxiomimport.dbo.tblacxiomlistings
insert into tl_acxiomimport.dbo.tblacxiomlistings
select * from #temp
Autres conseils
Quelque chose ne va pas avec votre base de données, serveur, stockage ou une combinaison de ceux-ci. 4:30 pour un compte choisi * semble TRÈS élevé.
Exécutez un DBCC_SHOWCONTIG pour voir le degré de fragmentation de votre table. Cela pourrait entraîner une baisse importante des performances sur une table de cette taille.
De plus, pour ajouter quelque chose au commentaire de RyanKeeter, exécutez le plan d’exposition et, s’il existe des analyses de table, créez un index pour le champ PK de cette table.
Ne serait-il pas plus simple de faire:
DELETE tl_acxiomimport.dbo.tblacxiomlistings
WHERE RecordID in
(SELECT RecordID
FROM (
SELECT RecordID,
Rank() over (Partition BY BusinessName,
latitude,
longitude,
Phone
ORDER BY webaddress DESC,
caption1 DESC,
caption2 DESC) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
)
WHERE Rank > 1
)
Exécuter ceci dans l'analyseur de requête:
SET SHOWPLAN_TEXT ON
Demandez ensuite à l’analyseur de requête d’exécuter votre requête. Au lieu d'exécuter la requête, SQL Server génère un plan de requête et le place dans le jeu de résultats.
Affichez-nous le plan de requête.
17 millions d’enregistrements, ce n’est rien. S'il faut 4 h 30 pour effectuer un compte sélectionné (*), il existe un problème grave, probablement lié au manque de mémoire du serveur ou à un processeur très ancien.
Pour améliorer les performances, réparez la machine. Pomper jusqu'à 2 Go. La mémoire vive est tellement bon marché de nos jours que son coût est bien inférieur à votre temps.
Est-ce que le processeur ou le disque se débat lorsque cette requête est en cours? Sinon, quelque chose bloque les appels. Dans ce cas, vous pouvez envisager de placer la base de données en mode mono-utilisateur pendant le temps nécessaire pour exécuter le nettoyage.
Vous supprimez donc tous les enregistrements qui ne sont pas classés en premier? Il serait peut-être intéressant de comparer une jointure à une première requête (ce qui pourrait également fonctionner en 2000, car le rang est 2005 et au-dessus uniquement).
Avez-vous besoin de supprimer tous les doublons en une seule opération? Je suppose que vous effectuez une sorte de tâche d’entretien, vous pourrez peut-être le faire par morceaux.
Fondamentalement, créez un curseur qui boucle tous les enregistrements (lecture incorrecte) et supprime les doublons pour chacun. Ce sera globalement beaucoup plus lent, mais chaque opération sera relativement minime. Ensuite, votre entretien devient une tâche de fond constante plutôt qu'un lot nocturne.
La suggestion ci-dessus de sélectionner d'abord une table temporaire est votre meilleur choix. Vous pouvez également utiliser quelque chose comme:
set rowcount 1000
avant d'exécuter votre suppression. Il cessera de fonctionner après la suppression des 1000 lignes. Puis exécutez-le encore et encore jusqu'à ce que vous obteniez 0 enregistrements supprimés.
si je l'obtiens correctement, votre requête est la même que
DELETE tl_acxiomimport.dbo.tblacxiomlistings
FROM
tl_acxiomimport.dbo.tblacxiomlistings allRecords
LEFT JOIN (
SELECT RecordID, Rank() over (Partition BY BusinessName, latitude, longitude, Phone ORDER BY webaddress DESC, caption1 DESC, caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
WHERE Rank = 1) myExceptions
ON allRecords.RecordID = myExceptions.RecordID
WHERE
myExceptions.RecordID IS NULL
Je pense que cela devrait fonctionner plus vite, j'ai tendance à éviter d'utiliser "IN". clause en faveur des JOINs si possible.
Vous pouvez réellement tester la vitesse et les résultats en toute sécurité en appelant simplement SELECT *
ou SELECT COUNT (*)
dans la partie FROM, comme par exemple
SELECT *
FROM
tl_acxiomimport.dbo.tblacxiomlistings allRecords
LEFT JOIN (
SELECT RecordID, Rank() over (Partition BY BusinessName, latitude, longitude, Phone ORDER BY webaddress DESC, caption1 DESC, caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
WHERE Rank = 1) myExceptions
ON allRecords.RecordID = myExceptions.RecordID
WHERE
myExceptions.RecordID IS NULL
C'est une autre raison pour laquelle je préférerais l'approche JOIN J'espère que ça aide
Cela semble bien, mais vous pouvez envisager de sélectionner vos données dans une table temporaire et de les utiliser dans votre instruction delete. J'ai constaté des gains de performances énormes en faisant cela au lieu de tout faire dans cette requête.
N'oubliez pas que lorsque vous effectuez une suppression volumineuse, il est préférable de commencer par effectuer une bonne sauvegarde (et je copie également les enregistrements supprimés dans une autre table au cas où, il faudrait que je les récupère immédiatement.)
À part l'utilisation de tronquer comme suggéré, j'ai eu la meilleure chance d'utiliser ce modèle pour supprimer de nombreuses lignes d'un tableau. Je ne me souviens plus très bien, mais je pense que l’utilisation de la transaction a permis d’empêcher le fichier journal de grandir - c’était peut-être une autre raison - je ne suis pas sûr. Et je passe généralement de la méthode de journalisation des transactions à simple avant de faire quelque chose comme ceci:
SET ROWCOUNT 5000 WHILE 1 = 1 BEGIN begin tran DELETE FROM ??? WHERE ??? IF @@rowcount = 0 BEGIN COMMIT BREAK END COMMIT END SET ROWCOUNT 0