Domanda

Il pensiero questo è stato risolto con il link qui sotto - il lavoro intorno opere - ma la patch non lo fa. Lavorare con il supporto Microsoft per risolvere.

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

Ok, quindi ho un problema che volevo buttare fuori per StackOverflow per vedere se qualcuno ha un'idea.

Nota questo è con SQL Server 2008 R2

Problema: l'eliminazione di 3000 record da una tabella con 15000 record richiede 3-4 minuti quando un trigger viene attivato e solo 3-5 secondi quando il grilletto è disattivato.

Imposta tabella

Due tavoli ci chiameranno principale e secondaria. Secondaria contiene i record di elementi che voglio eliminare così quando mi esibisco la cancellazione mi unisco al tavolo secondario. Un processo viene eseguito prima della istruzione delete per compilare la tabella secondaria con i record da cancellare.

DELETE:

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

Questa tabella ha un sacco di colonne e circa 14 diversi indici NC. Ho provato un sacco di cose diverse prima ho determinato il grilletto è stato il problema.

  • Attiva blocco di pagina (abbiamo disattivato per impostazione predefinita)
  • Riuniti Statistiche manualmente
  • raduno auto disabili di statistiche
  • Indice verificata la salute e la frammentazione
  • Caduto l'indice cluster della tabella
  • esaminato il piano di esecuzione (nulla mostrando come indici mancanti e il costo era 70 per cento verso l'eliminazione reale con circa il 28 per cento per il join / merge dei record

Trigger

La tabella ha 3 trigger (uno ciascuno per l'inserimento, l'aggiornamento, e le operazioni di eliminazione). Ho modificato il codice per il trigger di eliminazione di solo ritorno, quindi per selezionare uno per vedere quante volte si è sparato. Spara una sola volta durante l'intera operazione (come previsto).

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

Per ricapitolare

  • Con Trigger su - dichiarazione richiede 3-4 minuti per completare
  • Con innescare - dichiarazione prende 3-5 secondi per completare

Qualcuno ha qualche idea sul perché?

Si noti inoltre - non cercando di cambiare questa architettura, aggiungere indici rimuovere, ecc come una soluzione. Questa tabella è il pezzo centrale per alcune importanti operazioni di dati e abbiamo dovuto modificare e sintonizzare (indici, Chiusura pagina, ecc) per consentire importanti operazioni di concorrenza a lavorare senza deadlock.

Ecco il piano di esecuzione xml (i nomi sono stati cambiati per proteggere gli innocenti)

<?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>
È stato utile?

Soluzione 2

Il pozzo qui è la risposta ufficiale di Microsoft ... che credo sia un grande difetto di progettazione.

11/14/2011 - Official risposta è cambiato. Essi non si utilizza il log delle transazioni come affermato in precedenza. La sono utilizzando l'archivio interno (livello di riga) per copiare i dati modificati in. Ancora non in grado di determinare il motivo per cui c'è voluto così tanto tempo.

Abbiamo deciso di usare al posto di trigger in luogo di dopo trigger DELETE.

la parte dopo il grilletto ci fa avere per leggere il registro delle transazioni dopo l'elimina completa e costruire il grilletto inserito tavolo / cancellato. Questo è dove passiamo la grande quantità di tempo e legato alla progettazione per la parte dopo il grilletto. INVECE DI grilletto sarebbe impedire questo comportamento di scansione del log delle transazioni e la costruzione di una tabella inserita / cancellato. Inoltre, come è stato osservato che le cose sono molto più velocemente se lasciamo cadere tutte le colonne con nvarchar (max), che ha senso per il fatto che è considerato i dati LOB. Si prega di avere alook al di sotto articolo per ulteriori informaiton per quanto riguarda i dati In-Row:

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

Sommario: Trigger AFTER richiede la scansione indietro attraverso il registro delle transazioni dopo la fine eliminare, quindi dobbiamo costruire e inserito tavolo / cancellato, che richiede più l'utilizzo del log e tempo di transazione.

Quindi, come un piano d'azione, questo è quello che vi proponiamo in questo momento:

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.

Altri suggerimenti

Il quadro righe delle versioni introdotte in SQL Server 2005 è utilizzato per sostenere una serie di funzioni, tra cui il nuovo livello di isolamento delle transazioni READ_COMMITTED_SNAPSHOT e SNAPSHOT. Anche quando nessuno di questi livelli di isolamento sono abilitati, righe delle versioni viene ancora utilizzato per trigger AFTER (ad agevolare la generazione dei inserted e deleted pseudotabelle), MARS, e (in un negozio versione separata) indicizzazione linea.

documentato , il motore può aggiungere un suffisso 14 byte per ogni riga di una tabella che è di versione per ciascuno di tali scopi. Questo comportamento è relativamente ben noto, come è l'aggiunta dei dati 14 byte per ogni riga di un indice che è ricostruito in linea con un livello di isolamento row-versioni abilitato. Anche quando i livelli di isolamento non sono abilitati, un byte in più viene aggiunto a indici non cluster solo quando ricostruito ONLINE.

Dove è presente un trigger AFTER e versioni altrimenti aggiungere 14 byte per riga, un'ottimizzazione esiste all'interno del motore a evitare questo, ma dove un'allocazione ROW_OVERFLOW o LOB non può verificarsi. In pratica, ciò significa la dimensione massima possibile di una riga deve essere inferiore a 8060 byte. Nel calcolo massima dimensioni possibili di riga, il motore assume per esempio, le (460) colonna VARCHAR potrebbe contenere 460 caratteri.

Il comportamento è più semplice per vedere con un trigger AFTER UPDATE, anche se lo stesso principio vale per AFTER DELETE. Lo script seguente crea una tabella con una lunghezza massima di fila di 8060 byte. Le crisi di dati su una singola pagina, con 13 byte di spazio libero su quella pagina. Esiste un innesco no-op, in modo che la pagina è versioning informazioni aggiunte divisa e:

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;

Lo script produce l'output mostrato sotto. La tabella singola pagina viene divisa in due pagine, e la massima fisico lunghezza di riga è aumentata da 57 a 71 byte (= 14 byte per i dati della riga-versioni).

Aggiornamento esempio

mostra DBCC PAGE che la riga singolo aggiornato è Record Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71, mentre tutte le altre righe della tabella hanno Record Attributes = NULL_BITMAP; record Size = 57.

Lo stesso script, con il UPDATE sostituiti da un unico DELETE fila produce l'output mostrato:

DELETE dbo.Example
WHERE ID = 1;

esempio Delete

Non c'è uno in meno fila in totale (ovviamente!), Ma la dimensione massima delle righe fisica non è aumentato. Fila informazioni sulla versione viene aggiunto solo per righe necessarie per il trigger pseudotabelle, e tale riga è stato infine eliminato. La pagina divisa resta, comunque. Questa attività page-splitting è responsabile per il rallentamento delle prestazioni osservata quando il grilletto era presente. Se la definizione della colonna Padding2 passa da varchar(8000) al varchar(7999), la pagina si divide non è più.

Vedi anche post sul blog da SQL Server MVP Dmitri Korotkevitch, che discute anche l'impatto sulla frammentazione.

Secondo il piano tutto sta andando in modo corretto. Si può provare a scrivere la cancellazione come JOIN invece di un in cui vi darà un piano diverso.

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

Non sono sicuro di quanto che vi aiuterà comunque. Quando la cancellazione è in esecuzione con i trigger sul tavolo qual è il tipo di attesa per la sessione di fare la cancellazione?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top