¿Por qué usar TRUNCATE y DROP?


100

En el sistema en el que trabajo hay muchos procedimientos almacenados y scripts SQL que hacen uso de tablas temporales. Después de usar estas tablas, es una buena práctica dejarlas caer.

Muchos de mis colegas (casi todos con mucha más experiencia que yo) suelen hacer esto:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Normalmente uso uno solo DROP TABLEen mis scripts.

¿Hay alguna buena razón para hacer un TRUNCATEinmediatamente antes de un DROP?

Respuestas:


130

No.

TRUNCATEy DROPson casi idénticos en comportamiento y velocidad, por lo que hacer un TRUNCATEderecho antes de a DROPes simplemente innecesario.


Nota: Escribí esta respuesta desde una perspectiva de SQL Server y asumí que se aplicaría igualmente a Sybase. Parece que este no es del todo el caso .

Nota: Cuando publiqué esta respuesta por primera vez, hubo varias otras respuestas altamente calificadas, incluida la respuesta entonces aceptada, que hicieron varias afirmaciones falsas como: TRUNCATEno está registrado; TRUNCATEno se puede revertir; TRUNCATEes más rápido que DROP; etc.

Ahora que este hilo se ha limpiado, las refutaciones que siguen pueden parecer tangenciales a la pregunta original. Los dejo aquí como referencia para otros que buscan desacreditar estos mitos.


Hay un par de falsedades populares, generalizadas incluso entre los DBA experimentados, que pueden haber motivado este TRUNCATE-then-DROPpatrón. Son:

  • Mito : TRUNCATEno está registrado, por lo tanto, no se puede revertir.
  • Mito : TRUNCATEes más rápido que DROP.

Déjame refutar estas falsedades. Estoy escribiendo esta refutación desde una perspectiva de SQL Server, pero todo lo que digo aquí debería ser igualmente aplicable a Sybase.

TRUNCATE se registra y se puede revertir.

  • TRUNCATEes una operación registrada, por lo que puede revertirse . Solo envuélvelo en una transacción.

    USE [tempdb];
    SET NOCOUNT ON;
    
    CREATE TABLE truncate_demo (
        whatever    VARCHAR(10)
    );
    
    INSERT INTO truncate_demo (whatever)
    VALUES ('log this');
    
    BEGIN TRANSACTION;
        TRUNCATE TABLE truncate_demo;
    ROLLBACK TRANSACTION;
    
    SELECT *
    FROM truncate_demo;
    
    DROP TABLE truncate_demo;

    Sin embargo, tenga en cuenta que esto no es cierto para Oracle . Aunque registrado y protegido por la funcionalidad de deshacer y rehacer de Oracle, el usuario no puede revertir TRUNCATEotras declaraciones DDL porque Oracle emite confirmaciones implícitas inmediatamente antes y después de todas las declaraciones DDL.

  • TRUNCATEestá mínimamente registrado , en lugar de estar completamente registrado. Qué significa eso? Dile TRUNCATEuna mesa. En lugar de poner cada fila eliminada en el registro de transacciones, TRUNCATEsolo marca las páginas de datos en las que viven como no asignadas. Por eso es tan rápido. Es por eso que no puede recuperar las filas de una TRUNCATEtabla ed del registro de transacciones utilizando un lector de registro. Todo lo que encontrará allí son referencias a las páginas de datos desasignadas.

    Compara esto con DELETE. Si DELETEtodas las filas de una tabla y confirma la transacción aún puede, en teoría, encontrar las filas eliminadas en el registro de transacciones y recuperarlas desde allí. Eso es porque DELETEescribe cada fila eliminada en el registro de transacciones. Para tablas grandes, esto lo hará mucho más lento que TRUNCATE.

DROP es tan rápido como TRUNCATE.

  • Como TRUNCATE, DROPes una operación mínimamente registrada. Eso significa que también DROPse puede revertir. Eso también significa que funciona exactamente de la misma manera que TRUNCATE. En lugar de eliminar filas individuales, DROPmarca las páginas de datos apropiadas como no asignadas y, además, marca los metadatos de la tabla como eliminados .
  • Porque TRUNCATEy DROPfuncionan exactamente de la misma manera, corren tan rápido como los demás. No tiene sentido hacer TRUNCATEuna tabla antes de DROPhacerla. Ejecute este script de demostración en su instancia de desarrollo si no me cree.

    En mi máquina local con un caché cálido, los resultados que obtengo son los siguientes:

    table row count: 134,217,728
    
    run#        transaction duration (ms)
          TRUNCATE   TRUNCATE then DROP   DROP
    ==========================================
    01       0               1             4
    02       0              39             1
    03       0               1             1
    04       0               2             1
    05       0               1             1
    06       0              25             1
    07       0               1             1
    08       0               1             1
    09       0               1             1
    10       0              12             1
    ------------------------------------------
    avg      0              8.4           1.3

    Así, por A 134 millones de mesa de primera fila tanto DROPy TRUNCATEtener efectivamente en ningún momento a todos. (En un caché frío, tardan entre 2 y 3 segundos en la primera o dos ejecuciones). También creo que la mayor duración promedio de la operación en TRUNCATEese momento DROPes atribuible a las variaciones de carga en mi máquina local y no porque la combinación sea mágicamente un orden de magnitud peor que las operaciones individuales. Después de todo, son casi exactamente lo mismo.

    Si está interesado en obtener más detalles sobre la sobrecarga de registro de estas operaciones, Martin tiene una explicación directa de eso.


52

Probando TRUNCATEa continuación, DROPfrente a sólo haciendo la DROPmuestra directamente que el primer enfoque en realidad tiene un ligero aumento de la sobrecarga de registro así que puede incluso ser ligeramente contraproducente.

Si observa los registros de registro individuales, la TRUNCATE ... DROPversión es casi idéntica a la DROPversión, excepto que tiene estas entradas adicionales.

+-----------------+---------------+-------------------------+
|    Operation    |    Context    |      AllocUnitName      |
+-----------------+---------------+-------------------------+
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysallocunits.clust |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrowsets.clust    |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrscols.clst      |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrscols.clst      |
| LOP_HOBT_DDL    | LCX_NULL      | NULL                    |
| LOP_MODIFY_ROW  | LCX_CLUSTERED | sys.sysallocunits.clust |
| LOP_HOBT_DDL    | LCX_NULL      | NULL                    |
| LOP_MODIFY_ROW  | LCX_CLUSTERED | sys.sysrowsets.clust    |
| LOP_LOCK_XACT   | LCX_NULL      | NULL                    |
+-----------------+---------------+-------------------------+

Entonces, la TRUNCATEprimera versión termina desperdiciando un poco de esfuerzo haciendo algunas actualizaciones en varias tablas del sistema de la siguiente manera

  • Actualización rcmodifiedpara todas las columnas de la tabla ensys.sysrscols
  • Actualizar rcrowsensysrowsets
  • Ajustar a cero pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreservedensys.sysallocunits

Estas filas de la tabla del sistema solo se eliminan cuando la tabla se descarta en la siguiente instrucción.

A continuación se muestra un desglose completo del registro realizado por TRUNCATEvs. DROPTambién he agregado DELETEpara fines de comparación.

+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
|                   |                   |                    |                            Bytes                           |                            Count                           |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| Operation         | Context           | AllocUnitName      | Truncate / Drop  | Drop Only | Truncate Only | Delete Only | Truncate / Drop  | Drop Only | Truncate Only | Delete Only |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| LOP_BEGIN_XACT    | LCX_NULL          |                    | 132              | 132       | 132           | 132         | 1                | 1         | 1             | 1           |
| LOP_COMMIT_XACT   | LCX_NULL          |                    | 52               | 52        | 52            | 52          | 1                | 1         | 1             | 1           |
| LOP_COUNT_DELTA   | LCX_CLUSTERED     | System Table       | 832              |           | 832           |             | 4                |           | 4             |             |
| LOP_DELETE_ROWS   | LCX_MARK_AS_GHOST | System Table       | 2864             | 2864      |               |             | 22               | 22        |               |             |
| LOP_DELETE_ROWS   | LCX_MARK_AS_GHOST | T                  |                  |           |               | 8108000     |                  |           |               | 1000        |
| LOP_HOBT_DDL      | LCX_NULL          |                    | 108              | 36        | 72            |             | 3                | 1         | 2             |             |
| LOP_LOCK_XACT     | LCX_NULL          |                    | 336              | 296       | 40            |             | 8                | 7         | 1             |             |
| LOP_MODIFY_HEADER | LCX_PFS           | Unknown Alloc Unit | 76               | 76        |               | 76          | 1                | 1         |               | 1           |
| LOP_MODIFY_ROW    | LCX_CLUSTERED     | System Table       | 644              | 348       | 296           |             | 5                | 3         | 2             |             |
| LOP_MODIFY_ROW    | LCX_IAM           | T                  | 800              | 800       | 800           |             | 8                | 8         | 8             |             |
| LOP_MODIFY_ROW    | LCX_PFS           | T                  | 11736            | 11736     | 11736         |             | 133              | 133       | 133           |             |
| LOP_MODIFY_ROW    | LCX_PFS           | Unknown Alloc Unit | 92               | 92        | 92            |             | 1                | 1         | 1             |             |
| LOP_SET_BITS      | LCX_GAM           | T                  | 9000             | 9000      | 9000          |             | 125              | 125       | 125           |             |
| LOP_SET_BITS      | LCX_IAM           | T                  | 9000             | 9000      | 9000          |             | 125              | 125       | 125           |             |
| LOP_SET_BITS      | LCX_PFS           | System Table       | 896              | 896       |               |             | 16               | 16        |               |             |
| LOP_SET_BITS      | LCX_PFS           | T                  |                  |           |               | 56000       |                  |           |               | 1000        |
| LOP_SET_BITS      | LCX_SGAM          | Unknown Alloc Unit | 168              | 224       | 168           |             | 3                | 4         | 3             |             |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| Total             |                   |                    | 36736            | 35552     | 32220         | 8164260     | 456              | 448       | 406           | 2003        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+

La prueba se llevó a cabo en una base de datos con modelo de recuperación completa contra una tabla de 1,000 filas con una fila por página. La tabla consume 1.004 páginas en total debido a la página de índice raíz y 3 páginas de índice de nivel intermedio.

8 de estas páginas son asignaciones de una sola página en extensiones mixtas con el resto distribuido en 125 extensiones uniformes. Las 8 desasignaciones de una sola página se muestran como las 8 LOP_MODIFY_ROW,LCX_IAMentradas de registro. Los 125 repartos de extensión como LOP_SET_BITS LCX_GAM,LCX_IAM. Ambas operaciones también requieren una actualización de la PFSpágina asociada , por lo tanto, las 133 LOP_MODIFY_ROW, LCX_PFSentradas combinadas . Luego, cuando la tabla se descarta, los metadatos sobre ella deben eliminarse de varias tablas del sistema, por lo tanto, las 22 LOP_DELETE_ROWSentradas de registro de la tabla del sistema (se explican a continuación)

+----------------------+--------------+-------------------+-------------------+
|        Object        | Rows Deleted | Number of Indexes | Delete Operations |
+----------------------+--------------+-------------------+-------------------+
| sys.sysallocunits    |            1 |                 2 |                 2 |
| sys.syscolpars       |            2 |                 2 |                 4 |
| sys.sysidxstats      |            1 |                 2 |                 2 |
| sys.sysiscols        |            1 |                 2 |                 2 |
| sys.sysobjvalues     |            1 |                 1 |                 1 |
| sys.sysrowsets       |            1 |                 1 |                 1 |
| sys.sysrscols        |            2 |                 1 |                 2 |
| sys.sysschobjs       |            2 |                 4 |                 8 |
+----------------------+--------------+-------------------+-------------------+
|                      |              |                   |                22 |
+----------------------+--------------+-------------------+-------------------+

Guión completo a continuación

DECLARE @Results TABLE
(
    Testing int NOT NULL,
    Operation nvarchar(31) NOT NULL,
    Context nvarchar(31)  NULL,
    AllocUnitName nvarchar(1000) NULL,
    SumLen int NULL,
    Cnt int NULL
)

DECLARE @I INT = 1

WHILE @I <= 4
BEGIN
IF OBJECT_ID('T','U') IS NULL
     CREATE TABLE T(N INT PRIMARY KEY,Filler char(8000) NULL)

INSERT INTO T(N)
SELECT DISTINCT TOP 1000 number
FROM master..spt_values


CHECKPOINT

DECLARE @allocation_unit_id BIGINT

SELECT @allocation_unit_id = allocation_unit_id
FROM   sys.partitions AS p
       INNER JOIN sys.allocation_units AS a
         ON p.hobt_id = a.container_id
WHERE  p.object_id = object_id('T')  

DECLARE @LSN NVARCHAR(25)
DECLARE @LSN_HEX NVARCHAR(25)

SELECT @LSN = MAX([Current LSN])
FROM fn_dblog(null, null)


SELECT @LSN_HEX=
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 1, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 10, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 19, 4),2) AS INT) AS VARCHAR)

  BEGIN TRAN
    IF @I = 1
      BEGIN
          TRUNCATE TABLE T

          DROP TABLE T
      END
    ELSE
      IF @I = 2
        BEGIN
            DROP TABLE T
        END
      ELSE
        IF @I = 3
          BEGIN
              TRUNCATE TABLE T
          END  
      ELSE
        IF @I = 4
          BEGIN
              DELETE FROM T
          END                
  COMMIT

INSERT INTO @Results
SELECT @I,
       CASE
         WHEN GROUPING(Operation) = 1 THEN 'Total'
         ELSE Operation
       END,
       Context,
       CASE
         WHEN AllocUnitId = @allocation_unit_id THEN 'T'
         WHEN AllocUnitName LIKE 'sys.%' THEN 'System Table'
         ELSE AllocUnitName
       END,
       COALESCE(SUM([Log Record Length]), 0) AS [Size in Bytes],
       COUNT(*)                              AS Cnt
FROM   fn_dblog(@LSN_HEX, null) AS D
WHERE  [Current LSN] > @LSN  
GROUP BY GROUPING SETS((Operation, Context,
       CASE
         WHEN AllocUnitId = @allocation_unit_id THEN 'T'
         WHEN AllocUnitName LIKE 'sys.%' THEN 'System Table'
         ELSE AllocUnitName
       END),())


SET @I+=1
END 

SELECT Operation,
       Context,
       AllocUnitName,
       AVG(CASE WHEN Testing = 1 THEN SumLen END) AS [Truncate / Drop Bytes],
       AVG(CASE WHEN Testing = 2 THEN SumLen END) AS [Drop Bytes],
       AVG(CASE WHEN Testing = 3 THEN SumLen END) AS [Truncate Bytes],
       AVG(CASE WHEN Testing = 4 THEN SumLen END) AS [Delete Bytes],
       AVG(CASE WHEN Testing = 1 THEN Cnt END) AS [Truncate / Drop Count],
       AVG(CASE WHEN Testing = 2 THEN Cnt END) AS [Drop Count],
       AVG(CASE WHEN Testing = 3 THEN Cnt END) AS [Truncate Count],
       AVG(CASE WHEN Testing = 4 THEN Cnt END) AS [Delete Count]              
FROM   @Results
GROUP  BY Operation,
          Context,
          AllocUnitName   
ORDER BY Operation, Context,AllocUnitName        

DROP TABLE T

2

OK pensó que intentaría hacer algunos puntos de referencia que no se basaran en ningún "caché en caliente" para que con suerte fueran una prueba más realista (también usando Postgres, para ver si coincide con las mismas características de otras respuestas publicadas) :

Mis puntos de referencia usando postgres 9.3.4 con una base de datos de gran tamaño (con suerte lo suficientemente grande como para no caber en la memoria caché de RAM):

Usando este script de prueba DB: https://gist.github.com/rdp/8af84fbb54a430df8fc0

con 10M filas:

truncate: 1763ms
drop: 2091ms
truncate + drop: 1763ms (truncate) + 300ms (drop) (2063ms total)
drop + recreate: 2063ms (drop) + 242ms (recreate)

con 100 millones de filas:

truncate: 5516ms
truncate + drop: 5592ms
drop: 5680ms (basically, the exact same ballpark)

Entonces, a partir de esto, supongo lo siguiente: drop es "aproximadamente" tan rápido (o más rápido) como truncate + drop (al menos para las versiones modernas de Postgres), sin embargo, si planeas dar la vuelta y recrear la mesa, puedes quédate con hacer un truncado directo, que es más rápido que un drop + recrear (tiene sentido). FWIW

nota 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (dice que postgres 9.2 puede tener un truncamiento más rápido que las versiones anteriores). Como siempre, compare con su propio sistema para ver sus características.

nota 2: truncar puede revertirse en postgres, si está en una transacción: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

nota 3: truncar puede, con tablas pequeñas, a veces ser más lento que eliminar: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886


1

Agregando alguna perspectiva histórica ...

Eliminar una tabla requiere actualizar varias tablas del sistema, lo que a su vez generalmente requiere hacer estos cambios en la tabla del sistema en una sola transacción (piense "comenzar tran, eliminar syscolumns, eliminar sysobjects, commit").

También se incluye en la 'tabla desplegable' la necesidad de desasignar todas las páginas de datos / índices asociadas con la tabla.

Hace muchos, muchos, muchos años ... el proceso de desasignación espacial se incluyó en la transacción que también actualizó las tablas del sistema; el resultado neto fue que cuanto mayor era el número de páginas asignadas, más tardaba en desasignar dichas páginas, más tiempo la transacción (en las tablas del sistema) se dejó abierta y, por lo tanto, una mayor posibilidad de bloquear (en las tablas del sistema) otros procesos que intentan crear / soltar tablas en tempdb (especialmente desagradable con el bloqueo de nivel de página allpages == más antiguo y potencial para la tabla -escala de bloqueo de escala).

Uno de los primeros métodos utilizados (en ese entonces) para reducir la contención en las tablas del sistema era reducir el tiempo que se mantenían los bloqueos en las tablas del sistema, y ​​una forma (relativamente) fácil de hacerlo era desasignar las páginas de datos / índice antes de soltarlas. la mesa.

Aunque truncate tableno desasigna todas las páginas de datos / índices, desasigna todas las extensiones de 8 páginas (datos) excepto una; otro 'truco' fue eliminar todos los índices antes de abandonar la tabla (sí, separar txn en sysindexes pero un txn más pequeño para soltar tabla).

Cuando se considera que (nuevamente, hace muchos, muchos años) solo existía la única base de datos 'tempdb', y algunas aplicaciones hicieron uso PESADO de esa única base de datos 'tempdb', cualquier 'pirateo' que podría reducir la contención en las tablas del sistema en 'tempdb' fueron de beneficio; con el tiempo las cosas han mejorado ... múltiples bases de datos temporales, bloqueo de nivel de fila en tablas del sistema, mejores métodos de desasignación, etc.

Mientras tanto, el uso de la truncate tableno hace daño si se deja en el código.


-2

Tiene sentido hacer TRUNCATE para tablas que tienen claves foráneas. Sin embargo, para tablas temporales solo DROP es suficiente


TRUNCATE de alguna manera evitaría un conflicto de clave extranjera? ¿Cómo?
user259412

1
Escribirá un error de que hay una clave externa
Evgeniy Gribkov

-8

El objetivo de esto truncatees eliminar de forma simple e irrevocable todo lo que está en la tabla (algunos detalles técnicos basados ​​en los motores de almacenamiento de datos pueden diferir ligeramente): omitir el registro pesado, etc.

drop tableregistra todos los cambios a medida que se realizan los cambios. Por lo tanto, para tener un registro mínimo y reducir la rotación del sistema inútil, sospecharía que una tabla muy grande podría truncarse primero y luego descartarse.

truncate puede estar envuelto en una transacción (que sería la opción más segura) que, por supuesto, le permitirá revertir la operación.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.