Pregunta

Pensé que esto se resolvió con el siguiente enlace, el trabajo en torno a funciona, pero el parche no. Trabajando con Soporte de Microsoft para resolver.

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

Ok, tengo un problema que quería lanzar a Stackoverflow para ver si alguien tiene una idea.

Tenga en cuenta que esto es con SQL Server 2008 R2

Problema: Eliminar 3000 registros de una tabla con 15000 registros lleva 3-4 minutos cuando un disparador está habilitado y solo 3-5 segundos cuando el disparador está deshabilitado.

Configuración de mesa

Dos tablas llamaremos principal y secundaria. Secundario contiene registros de elementos que quiero eliminar, así que cuando realice el eliminación, me uniré a la tabla secundaria. Un proceso se ejecuta antes de la instrucción Eliminar para llenar la tabla secundaria con los registros que se eliminarán.

Declaración de eliminación:

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

Esta tabla tiene muchas columnas y aproximadamente 14 índices de NC diferentes. Probé un montón de cosas diferentes antes de determinar que el desencadenante era el problema.

  • Encienda el bloqueo de la página (hemos apagado de forma predeterminada)
  • Estadísticas reunidas manualmente
  • Recopilación de autos discapacitados de estadísticas
  • Salud y fragmentación del índice verificado
  • Dejó caer el índice agrupado de la tabla
  • Examinó el plan de ejecución (nada que muestra los índices faltantes y el costo fue del 70 por ciento hacia la eliminación real con aproximadamente el 28 por ciento para la unión / fusión de los registros

Desencadenantes

La tabla tiene 3 desencadenantes (uno para insertar, actualizar y eliminar operaciones). Modifiqué el código para el disparador de eliminación para que solo regrese, luego seleccione uno para ver cuántas veces se dispara. Solo dispara una vez durante toda la operación (como se esperaba).

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

Recordar

  • Con disparador encendido - La declaración tarda 3-4 minutos en completarse
  • Con disparador apagado - la declaración tarda 3-5 segundos en completarse

¿Alguien tiene alguna idea de por qué?

Tenga en cuenta también: no buscar cambiar esta arquitectura, agregar los índices de eliminación, etc. como solución. Esta tabla es la pieza central para algunas operaciones de datos importantes y tuvimos que ajustarla y ajustarla (índices, bloqueo de páginas, etc.) para permitir que las operaciones de concurrencia importantes funcionen sin punto muerto.

Aquí está el plan de ejecución XML (los nombres se cambiaron para proteger al inocente)

<?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>
¿Fue útil?

Solución 2

Bueno, aquí está la respuesta oficial de Microsoft ... que creo que es un defecto de diseño importante.

14/11/2011 - La respuesta oficial ha cambiado. No están utilizando el registro de transacciones como se indicó anteriormente. Están utilizando el almacén interno (nivel de fila) para copiar los datos cambiados. Todavía no pueden determinar por qué se ha llevado tanto tiempo.

Decidimos usar en lugar de desencadenantes en lugar de después de eliminar los desencadenantes.

La parte posterior del desencadenante hace que tengamos que leer el registro de transacciones después de que las eliminaciones se completen y construyan la tabla insertada/eliminada del activador. Aquí es donde pasamos la gran cantidad de tiempo y es por diseño para la parte posterior del gatillo. En lugar de disparar, evitaría este comportamiento de escanear el registro de transacciones y construir una tabla insertada/eliminada. Además, como se observó que las cosas son mucho más rápidas si eliminamos todas las columnas con NVARCHAR (MAX), lo que tiene sentido debido al hecho de que se considera datos LOB. Tenga a Alook en el artículo a continuación para obtener más información sobre datos en la fila:

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

Resumen: después del disparador requiere escanear a través del registro de transacciones después de los acabados de eliminación, entonces tenemos que construir e insertar/eliminar tabla que requiere más uso del registro de transacciones y el tiempo.

Entonces, como plan de acción, esto es lo que sugerimos en este 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.

Otros consejos

El marco de versión de fila introducido en SQL Server 2005 se utiliza para admitir una serie de características, incluidos los nuevos niveles de aislamiento de transacciones READ_COMMITTED_SNAPSHOT y SNAPSHOT. Incluso cuando ninguno de estos niveles de aislamiento está habilitado, la versión de fila todavía se usa para AFTER desencadenantes (para facilitar la generación de la inserted y deleted Pseudo-mesas), Marte y (en una tienda de versiones separada) indexación en línea.

Como documentado, el motor puede agregar un postfix de 14 bytes a cada fila de una tabla que esté versión para cualquiera de estos fines. Este comportamiento es relativamente conocido, al igual que la adición de los datos de 14 bytes a cada fila de un índice que es reconstruido en línea con un nivel de aislamiento de inversión de filas habilitado. Incluso donde los niveles de aislamiento no están habilitados, se agrega un byte adicional a Índices no agrupados solamente Cuando se reconstruye ONLINE.

Cuando está presente un desencadenante posterior, y los versiones agregarían 14 bytes por fila, existe una optimización dentro del motor a evitar esto, pero donde un ROW_OVERFLOW o LOB La asignación no puede ocurrir. En la práctica, esto significa que el tamaño máximo posible de una fila debe ser inferior a 8060 bytes. En calcular máximo Posibles tamaños de fila, el motor supone, por ejemplo, que una columna Varchar (460) podría contener 460 caracteres.

El comportamiento es más fácil de ver con un AFTER UPDATE disparador, aunque el mismo principio se aplica a AFTER DELETE. El siguiente script crea una tabla con una longitud máxima en la fila de 8060 bytes. Los datos se ajustan en una sola página, con 13 bytes de espacio libre en esa página. Existe un disparador de no opción, por lo que la página está dividida y se agregó información de versiones:

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;

El script produce la salida que se muestra a continuación. La tabla de una sola página se divide en dos páginas y el máximo físico La longitud de la fila ha aumentado de 57 a 71 bytes (= +14 bytes para la información de versión de fila).

Update example

DBCC PAGE muestra que la fila actualizada única tiene Record Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71, mientras que todas las demás filas en la mesa tienen Record Attributes = NULL_BITMAP; record Size = 57.

El mismo guión, con el UPDATE reemplazado por una sola fila DELETE produce la salida que se muestra:

DELETE dbo.Example
WHERE ID = 1;

Delete example

Hay una fila menos en total (¡por supuesto!), Pero el tamaño máximo de la fila física no ha aumentado. La información de versiones de fila solo se agrega a las filas necesarias para las pseudo-tablas de activación, y esa fila finalmente se eliminó. La división de la página permanece, sin embargo. Esta actividad de división de página es responsable del rendimiento lento observado cuando el disparador estaba presente. Si la definición del Padding2 La columna se cambia de varchar(8000) a varchar(7999), la página ya no se divide.

También vea esto entrada en el blog por SQL Server MVP Dmitri Korotkevitch, que también analiza el impacto en la fragmentación.

Según el plan, todo va correctamente. Puede intentar escribir el eliminación como un unión en lugar de un en el que le dará un plan diferente.

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

Sin embargo, no estoy seguro de cuánto ayudará. Cuando el eliminación se ejecuta con los desencadenantes en la mesa, ¿cuál es el tipo de espera para la sesión que realiza la eliminación?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a dba.stackexchange
scroll top