Question

Pensée cela a été résolu avec le lien ci-dessous - le travail autour des œuvres - mais le patch ne fonctionne pas. Travailler avec le soutien de Microsoft à résoudre.

http://support.microsoft.com/kb/2606883

Ok j'ai donc une question que je voulais jeter à StackOverflow pour voir si quelqu'un a une idée.

Remarque est avec SQL Server 2008 R2

Numéro: Suppression de 3000 enregistrements d'une table avec 15000 enregistrements prend 3-4 minutes lorsqu'un déclencheur est activé et seulement 3-5 secondes lorsque le déclencheur est désactivé.

Réglage de la table

Deux tableaux nous appellerons principal et secondaire. Secondaire contient les enregistrements des articles que je veux supprimer quand j'effectuer une suppression je me joins à la table secondaire. Un processus est exécuté avant l'instruction delete pour remplir la table secondaire avec des enregistrements à supprimer.

Supprimer Déclaration:

DELETE FROM MAIN 
WHERE ID IN (
   SELECT Secondary.ValueInt1 
   FROM Secondary 
   WHERE SECONDARY.GUID = '9FFD2C8DD3864EA7B78DA22B2ED572D7'
);

Ce tableau a beaucoup de colonnes et environ 14 différents indices NC. J'ai essayé un tas de choses différentes avant que je déterminé le déclencheur a été la question.

  • Activer le verrouillage de la page (nous avons désactivé par défaut)
  • Statistiques manuellement froncée
  • Désactivé collecte automatique des statistiques
  • Santé vérifiée Index et de la fragmentation
  • Laissé tomber l'index ordonné en clusters de la table
  • Examiné le plan d'exécution (rien montrant que les index manquants et le coût était de 70 pour cent vers la suppression réelle avec environ 28 pour cent pour la jointure / fusion des enregistrements

Triggers

Le tableau 3 présente les déclencheurs (une pour chaque insert, mise à jour, et les opérations de suppression). J'ai modifié le code pour le déclencheur de suppression juste retour, puis de sélectionner un pour voir combien de fois il est tiré. Il tire qu'une seule fois pendant toute l'opération (comme prévu).

ALTER TRIGGER [dbo].[TR_MAIN_RD] ON [dbo].[MAIN]
            AFTER DELETE
            AS  
                SELECT 1
                RETURN

Pour récapituler

  • Avec Trigger - déclaration prend 3-4 minutes pour remplir
  • Avec Trigger off - déclaration prend 3-5 secondes pour terminer

Quelqu'un a des idées pour expliquer pourquoi?

A noter également - ne cherche pas à changer cette architecture, ajouter supprimer des index, etc. comme une solution. Ce tableau est la pièce centrale pour certaines opérations importantes de données et nous avons dû modifier et syntonisez (index, verrouillage de page, etc.) pour permettre des opérations majeures de concurrence au travail sans blocages.

Voici le plan d'exécution xml (les noms ont été changés pour protéger les innocents)

<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.1" Build="10.50.1790.0" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementCompId="1" StatementEstRows="185.624" StatementId="1" StatementOptmLevel="FULL" StatementOptmEarlyAbortReason="GoodEnoughPlanFound" StatementSubTreeCost="0.42706" StatementText="DELETE FROM MAIN WHERE ID IN (SELECT Secondary.ValueInt1 FROM Secondary WHERE Secondary.SetTMGUID = '9DDD2C8DD3864EA7B78DA22B2ED572D7')" StatementType="DELETE" QueryHash="0xAEA68D887C4092A1" QueryPlanHash="0x78164F2EEF16B857">
          <StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="false" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
          <QueryPlan CachedPlanSize="48" CompileTime="20" CompileCPU="20" CompileMemory="520">
            <RelOp AvgRowSize="9" EstimateCPU="0.00259874" EstimateIO="0.296614" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Delete" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Delete" EstimatedTotalSubtreeCost="0.42706">
              <OutputList />
              <Update WithUnorderedPrefetch="true" DMLRequestSort="false">
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_02]" IndexKind="Clustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[PK_MAIN_ID]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[UK_MAIN_01]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_03]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_04]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_05]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_06]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_07]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_08]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_09]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_10]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_11]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[UK_MAIN_12]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_13]" IndexKind="NonClustered" />
                <RelOp AvgRowSize="15" EstimateCPU="1.85624E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Top" NodeId="2" Parallel="false" PhysicalOp="Top" EstimatedTotalSubtreeCost="0.127848">
                  <OutputList>
                    <ColumnReference Column="Uniq1002" />
                    <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                  </OutputList>
                  <Top RowCount="true" IsPercent="false" WithTies="false">
                    <TopExpression>
                      <ScalarOperator ScalarString="(0)">
                        <Const ConstValue="(0)" />
                      </ScalarOperator>
                    </TopExpression>
                    <RelOp AvgRowSize="15" EstimateCPU="0.0458347" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Left Semi Join" NodeId="3" Parallel="false" PhysicalOp="Merge Join" EstimatedTotalSubtreeCost="0.12783">
                      <OutputList>
                        <ColumnReference Column="Uniq1002" />
                        <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                      </OutputList>
                      <Merge ManyToMany="false">
                        <InnerSideJoinColumns>
                          <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                        </InnerSideJoinColumns>
                        <OuterSideJoinColumns>
                          <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                        </OuterSideJoinColumns>
                        <Residual>
                          <ScalarOperator ScalarString="[MyDatabase].[dbo].[MAIN].[ID]=[MyDatabase].[dbo].[Secondary].[ValueInt1]">
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                                </Identifier>
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                        </Residual>
                        <RelOp AvgRowSize="19" EstimateCPU="0.0174567" EstimateIO="0.0305324" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="15727" LogicalOp="Index Scan" NodeId="4" Parallel="false" PhysicalOp="Index Scan" EstimatedTotalSubtreeCost="0.0479891" TableCardinality="15727">
                          <OutputList>
                            <ColumnReference Column="Uniq1002" />
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                          </OutputList>
                          <IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Column="Uniq1002" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[PK_MAIN_ID]" IndexKind="NonClustered" />
                          </IndexScan>
                        </RelOp>
                        <RelOp AvgRowSize="11" EstimateCPU="0.00392288" EstimateIO="0.03008" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="3423.53" LogicalOp="Index Seek" NodeId="5" Parallel="false" PhysicalOp="Index Seek" EstimatedTotalSubtreeCost="0.0340029" TableCardinality="171775">
                          <OutputList>
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                          </OutputList>
                          <IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Index="[IX_Secondary_01]" IndexKind="NonClustered" />
                            <SeekPredicates>
                              <SeekPredicateNew>
                                <SeekKeys>
                                  <Prefix ScanType="EQ">
                                    <RangeColumns>
                                      <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="SetTMGUID" />
                                    </RangeColumns>
                                    <RangeExpressions>
                                      <ScalarOperator ScalarString="'9DDD2C8DD3864EA7B78DA22B2ED572D7'">
                                        <Const ConstValue="'9DDD2C8DD3864EA7B78DA22B2ED572D7'" />
                                      </ScalarOperator>
                                    </RangeExpressions>
                                  </Prefix>
                                </SeekKeys>
                              </SeekPredicateNew>
                            </SeekPredicates>
                          </IndexScan>
                        </RelOp>
                      </Merge>
                    </RelOp>
                  </Top>
                </RelOp>
              </Update>
            </RelOp>
          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>
Était-ce utile?

La solution 2

Eh bien voici la réponse officielle de Microsoft ... qui je pense est un défaut de conception majeur.

11/14/2011 - Réponse officielle a changé. Ils n'utilisent pas le journal des transactions comme indiqué précédemment. Le utilisez le magasin interne (niveau ligne) pour copier les données modifiées en. Ils ne peuvent toujours pas déterminer pourquoi il a fallu si longtemps.

Nous avons décidé d'utiliser au lieu de déclencheurs au lieu de déclencheurs après suppression.

La partie APRES du déclencheur nous amène à devoir lire le journal des transactions après la supprime complète et construire le déclencheur inséré / table supprimée. C'est là que nous passons la grande quantité de temps et par la conception pour la partie APRÈS de la gâchette. Déclencheur INSTEAD OF empêcherait ce comportement de l'analyse du journal des transactions et la construction d'une insertion / table supprimée. En outre, comme il a été observé que les choses sont beaucoup plus rapides si nous laissons tomber toutes les colonnes avec nvarchar (max), ce qui est logique en raison du fait qu'il est considéré comme des données LOB. S'il vous plaît avoir alook au-dessous de l'article pour plus d'informaiton en ce qui concerne les données en ligne:

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

Résumé: Déclencheur AFTER nécessite la numérisation à travers le journal des transactions après la fin de suppression alors nous devons construire et insérer / table supprimée qui nécessite plus l'utilisation du journal et le temps transaction.

Alors qu'un plan d'action, ce que nous proposons à ce moment:

A) Limit the number of rows deleted in each transaction or
B) Increase timeout settings or
C) Don't use AFTER trigger or trigger at all or
D) Limit usage of nvarchar(max) datatypes.

Autres conseils

Le cadre de la ligne-versionnage introduit dans SQL Server 2005 est utilisé pour soutenir un certain nombre de caractéristiques, y compris les nouveaux niveaux d'isolation des transactions READ_COMMITTED_SNAPSHOT et SNAPSHOT. Même si aucun de ces niveaux d'isolation sont activés, la ligne-versioning est toujours utilisé pour les déclencheurs de AFTER (pour faciliter la génération des tables pseudo-inserted et deleted), MARS, et (dans un magasin de version séparée) l'indexation en ligne.

documenté , la moteur peut ajouter un suffixe de 14 octets pour chaque rangée d'une table qui est versionnée pour ces fins. Ce problème est relativement bien connu, de même que l'addition des données de 14 octets pour chaque rangée d'un indice qui est index non cluster uniquement lorsque ONLINE reconstruit.

Si un déclencheur AFTER est présent, et versioning autrement ajouter 14 octets par ligne, une optimisation existe dans le moteur à éviter , mais où une allocation ROW_OVERFLOW ou LOB ne peut se produire. Dans la pratique, cela signifie que la taille maximale d'une ligne doit être inférieure à 8060 octets. Dans le calcul de maximale tailles possibles de ligne, le moteur suppose par exemple qu'une colonne VARCHAR (460) peut contenir 460 caractères.

Le comportement est plus facile à voir avec un déclencheur AFTER UPDATE, bien que le même principe vaut pour AFTER DELETE. Le script suivant crée une table avec une longueur maximale en rangée de 8060 octets. Les ajustements de données une seule page, avec 13 octets d'espace libre sur cette page. Un déclencheur no-op existe, pour que la page est divisée et informations versioning ajouté:

USE Sandpit;
GO
CREATE TABLE dbo.Example
(
    ID          integer NOT NULL IDENTITY(1,1),
    Value       integer NOT NULL,
    Padding1    char(42) NULL,
    Padding2    varchar(8000) NULL,

    CONSTRAINT PK_Example_ID
    PRIMARY KEY CLUSTERED (ID)
);
GO
WITH
    N1 AS (SELECT 1 AS n UNION ALL SELECT 1),
    N2 AS (SELECT L.n FROM N1 AS L CROSS JOIN N1 AS R),
    N3 AS (SELECT L.n FROM N2 AS L CROSS JOIN N2 AS R),
    N4 AS (SELECT L.n FROM N3 AS L CROSS JOIN N3 AS R)
INSERT TOP (137) dbo.Example
    (Value)
SELECT
    ROW_NUMBER() OVER (ORDER BY (SELECT 0))
FROM N4;
GO
ALTER INDEX PK_Example_ID 
ON dbo.Example 
REBUILD WITH (FILLFACTOR = 100);
GO
SELECT
    ddips.index_type_desc,
    ddips.alloc_unit_type_desc,
    ddips.index_level,
    ddips.page_count,
    ddips.record_count,
    ddips.max_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.Example', N'U'), 1, 1, 'DETAILED') AS ddips
WHERE
    ddips.index_level = 0;
GO
CREATE TRIGGER ExampleTrigger
ON dbo.Example
AFTER DELETE, UPDATE
AS RETURN;
GO
UPDATE dbo.Example
SET Value = -Value
WHERE ID = 1;
GO
SELECT
    ddips.index_type_desc,
    ddips.alloc_unit_type_desc,
    ddips.index_level,
    ddips.page_count,
    ddips.record_count,
    ddips.max_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.Example', N'U'), 1, 1, 'DETAILED') AS ddips
WHERE
    ddips.index_level = 0;
GO
DROP TABLE dbo.Example;

Le script produit la sortie ci-dessous. Le tableau d'une page est divisée en deux pages, et un maximum physique longueur de ligne est passé de 57 à 71 octets (= 14 octets pour les données de la ligne-de version).

Exemple de mise à jour

DBCC PAGE montre que la seule ligne mise à jour a Record Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71, alors que toutes les autres lignes de la table ont Record Attributes = NULL_BITMAP; record Size = 57.

Le même script, avec le UPDATE remplacé par un DELETE unique de rangée produit la sortie affichée:

DELETE dbo.Example
WHERE ID = 1;

Supprimer exemple

Il y a une ligne moins au total (bien sûr!), Mais la taille de la ligne physique maximale n'a pas augmenté. Ligne d'information est versioning seulement ajouté aux lignes nécessaires pour les tables pseudo-déclenchement, et cette ligne a finalement été supprimée. La page divisée reste, cependant. Cette activité Page fractionnement est responsable de la lenteur observée lorsque le déclencheur était présent. Si la définition de la colonne de Padding2 passe de varchar(8000) à varchar(7999), la page se divise plus.

Voir aussi ce blog par SQL Server MVP Dmitri Korotkevitch, qui aborde également l'impact sur la fragmentation.

Selon le plan tout va bien. Vous pouvez essayer d'écrire la suppression comme REJOIGNEZ au lieu d'un dans lequel vous donnera un autre plan.

DELETE m
FROM MAIN m
JOIN Secondary s ON m.ID = s.ValueInt1
AND s.SetTMGUID = '9DDD2C8DD3864EA7B78DA22B2ED572D7'

Je ne sais pas combien cela aidera cependant. Lorsque la suppression est en cours d'exécution avec les déclencheurs sur la table ce qui est le type d'attente pour la session faire la suppression?

Licencié sous: CC-BY-SA avec attribution
Non affilié à dba.stackexchange
scroll top