it-swarm.asia

لماذا تستخدم كلاً من TRUNCATE و DROP؟

في النظام الذي أعمل فيه ، هناك الكثير من الإجراءات المخزنة ونصوص SQL التي تستخدم الجداول المؤقتة. بعد استخدام هذه الجداول ، من الجيد إسقاطها.

يقوم العديد من زملائي (وجميعهم تقريبًا أكثر خبرة مني بكثير) بذلك:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

عادةً ما أستخدم DROP TABLE واحد في نصوصاتي.

هل هناك أي سبب وجيه لإجراء TRUNCATE مباشرة قبل DROP؟

102
user606723

لا.

TRUNCATE و DROP متطابقان تقريبًا في السلوك والسرعة ، لذا فإن إجراء TRUNCATE قبل DROP ليس ضروريًا ببساطة.


ملاحظة: لقد كتبت هذه الإجابة من منظور SQL Server وافترضت أنها ستنطبق بشكل متساوٍ على Sybase. يبدو أن ليس هذا هو الحال تمامًا .

ملاحظة: عندما نشرت هذه الإجابة لأول مرة ، كانت هناك العديد من الإجابات الأخرى ذات التصنيف العالي - بما في ذلك الإجابة المقبولة آنذاك - التي قدمت عدة ادعاءات كاذبة مثل: TRUNCATE لم يتم تسجيلها ؛ TRUNCATE لا يمكن التراجع عنه ؛ TRUNCATE أسرع من DROP ؛ الخ

الآن بعد أن تم تنظيف سلسلة المحادثات هذه ، قد تبدو عمليات إعادة النظر التي تليها تمادية مع السؤال الأصلي. أتركهم هنا كمرجع للآخرين الذين يتطلعون إلى فضح هذه الأساطير .


هناك نوعان من الأكاذيب الشائعة - المنتشرة حتى بين DBAs ذوي الخبرة - التي ربما حفزت هذا TRUNCATE-then-DROP النمط. هم انهم:

  • الأسطورة : TRUNCATE لم يتم تسجيله ، وبالتالي لا يمكن التراجع عنه.
  • الخرافة : TRUNCATE أسرع من DROP.

دعني أدحض هذه الأكاذيب. أنا أكتب هذا النقض من منظور SQL Server ، ولكن كل ما أقوله هنا يجب أن يكون قابلاً للتطبيق على Sybase.

[~ # ~] اقتطاع [~ # ~]هوتم تسجيله ويمكنالتراجع.

  • TRUNCATE هي عملية مسجلة ، لذا canسيتم إرجاعها . فقط قم بلفها في معاملة.

    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;
    

    لاحظ ، مع ذلك ، أن هذا ليس صحيحًا لـ Oracle . على الرغم من تسجيلها وحمايتها بواسطة وظيفة التراجع والإعادة من Oracle ، TRUNCATE وعبارات DDL الأخرىلا يمكنإرجاعها من قِبل المستخدم لأن مشكلات Oracle تلتزم ضمنيًا مباشرة قبل وبعد جميع عبارات DDL.

  • TRUNCATE يتم تسجيله بالحد الأدنى ، على عكس تسجيله بالكامل. ماذا يعني ذالك؟ لنفترض أنك TRUNCATE جدول. بدلاً من وضع كل صف محذوف في سجل المعاملات ، يضع TRUNCATE فقط صفحات البيانات التي يعيشون عليها على أنها غير مخصصة. هذا هو السبب في أنها سريعة للغاية. ولهذا أيضًا لا يمكنك استعادة صفوف جدول TRUNCATE من سجل المعاملات باستخدام قارئ سجلات. كل ما ستجده هناك مراجع لصفحات البيانات المخصصة.

    قارن هذا بـ DELETE. إذا كنت DELETE جميع الصفوف في جدول وقمت بتنفيذ المعاملة ، فلا يزال بإمكانك ، نظريًا ، العثور على الصفوف المحذوفة في سجل المعاملات واستعادتها من هناك. ذلك لأن DELETE يكتب كل صف محذوف في سجل المعاملات. للجداول الكبيرة ، سيجعلها أبطأ بكثير من TRUNCATE.

انخفاض [~ # ~] إسقاط [~ # ~] بنفس سرعة TRUNCATE.

  • مثل TRUNCATE ،DROP هي عملية مسجلة بالحد الأدنى. هذا يعني أنه يمكن إرجاع DROP أيضًا. وهذا يعني أيضًا أنه يعمل بنفس الطريقة تمامًا مثل TRUNCATE. بدلاً من حذف الصفوف الفردية ،DROP يضع علامة على صفحات البيانات المناسبة على أنها غير مخصصة ويميز أيضًا بيانات تعريف الجدول على أنها محذوفة .
  • نظرًا لأن TRUNCATE و DROP يعملان بنفس الطريقة تمامًا ، فإنهما يعملان بنفس سرعة عمل بعضهما البعض. لا يوجد أي نقطة TRUNCATE - تضمين جدول قبل DROP - تضمينه. قم بتشغيل هذا البرنامج النصي التجريبي على نسخة التطوير الخاصة بك إذا كنت لا تصدقني.

    على جهازي المحلي مع ذاكرة تخزين مؤقت دافئة ، فإن النتائج التي أحصل عليها هي كما يلي:

    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
    

    لذلك ، بالنسبة لجدول الصف 134مليونالصف ، لا يستغرق كل من DROP و TRUNCATE أي وقت على الإطلاق. (على ذاكرة التخزين المؤقت البارد ، يستغرق الأمر حوالي 2-3 ثوانٍ لأول مرة أو اثنتين.) أعتقد أيضًا أن متوسط ​​المدة الأعلى للعملية TRUNCATE ثم DROP يُعزى إلى تحميل الاختلافات على جهازي المحلي ولالأن التركيبة بطريقة ما بطريقة سحرية ترتيب حجم أسوأ من العمليات الفردية. هم ، بعد كل شيء ، نفس الشيء تقريبًا.

    إذا كنت مهتمًا بمزيد من التفاصيل حول النفقات العامة لتسجيل الدخول لهذه العمليات ، لدى مارتن شرح مباشر لذلك.

132
Nick Chammas

يُظهر الاختبار TRUNCATE ثم DROP مقابل إجراء DROP مباشرةً أن النهج الأول في الواقع يحتوي على زيادة طفيفة في تسجيل الدخول لذا فقد يؤدي إلى نتائج عكسية بشكل معتدل.

يُظهر فحص سجلات السجلات الفردية أن إصدار TRUNCATE ... DROP مطابق تقريبًا للإصدار DROP باستثناء أنه يحتوي على هذه الإدخالات الإضافية.

+-----------------+---------------+-------------------------+
|    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                    |
+-----------------+---------------+-------------------------+

لذا ينتهي الإصدار الأول TRUNCATE بإضاعة القليل من الجهد للقيام ببعض التحديثات لجداول النظام المختلفة على النحو التالي

  • تحديث rcmodified لجميع أعمدة الجدول في sys.sysrscols
  • تحديث rcrows في sysrowsets
  • صفر pgfirst ، pgroot ، pgfirstiam ، pcused ، pcdata ، pcreserved في sys.sysallocunits

يتم حذف صفوف جدول النظام هذه فقط عند إسقاط الجدول في العبارة التالية.

يوجد أدناه تفصيل كامل للتسجيل الذي قام به TRUNCATE مقابل DROP. أضفت أيضًا DELETE لأغراض المقارنة.

+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
|                   |                   |                    |                            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        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+

تم إجراء الاختبار في قاعدة بيانات مع نموذج الاسترداد الكامل مقابل جدول 1000 صف مع صف واحد لكل صفحة. يستهلك الجدول 1004 صفحة إجمالاً بسبب صفحة فهرس الجذر و 3 صفحات فهرس المستوى المتوسط.

8 من هذه الصفحات عبارة عن تخصيصات صفحة واحدة في نطاقات مختلطة مع توزيع الباقي عبر 125 نطاقًا موحدًا. تظهر عمليات إلغاء تخصيص 8 صفحات مفردة كإدخالات سجل 8 LOP_MODIFY_ROW,LCX_IAM. التخصيصات 125 مدى على أنها LOP_SET_BITS LCX_GAM,LCX_IAM. تتطلب كلتا هاتين العمليتين أيضًا تحديثًا للصفحة PFS المقترنة ، وبالتالي إدخالات 133 LOP_MODIFY_ROW, LCX_PFS المدمجة. ثم عندما يتم إسقاط الجدول فعليًا ، يجب إزالة البيانات الوصفية الخاصة به من جداول النظام المختلفة ومن ثم فإن إدخالات سجل جدول النظام 22 LOP_DELETE_ROWS (محسوبة على النحو التالي)

+----------------------+--------------+-------------------+-------------------+
|        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 |
+----------------------+--------------+-------------------+-------------------+

النص الكامل أدناه

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
52
Martin Smith

حسنًا ، اعتقدت أنني سأحاول تنفيذ بعض المعايير التي لا تعتمد على أي "ذاكرة تخزين مؤقت دافئة" بحيث نأمل أن يكونوا اختبارًا أكثر واقعية (باستخدام Postgres أيضًا ، لمعرفة ما إذا كان يتطابق مع نفس الخصائص للإجابات المنشورة الأخرى) :

مقاييسي باستخدام postgres 9.3.4 مع قاعدة بيانات كبيرة ، (نأمل أن تكون كبيرة بما يكفي لعدم احتواؤها RAM ذاكرة التخزين المؤقت):

باستخدام هذا البرنامج النصي لاختبار DB: https://Gist.github.com/rdp/8af84fbb54a430df8fc

مع 10 ملايين صف:

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

مع 100 مليون صف:

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

لذا من هذا أفترض ما يلي: الانخفاض هو "حول" بسرعة (أو أسرع) مثل اقتطاع + قطرة (على الأقل بالنسبة للإصدارات الحديثة من Postgres) ، ومع ذلك ، إذا كنت تخطط أيضًا حول الدوران وإعادة إنشاء الجدول ، فيمكنك تمسك جيدًا بعمل اقتطاع مستقيم ، وهو أسرع من إسقاط + إعادة (منطقي). مهاجم.

الملاحظة 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (تقول أن postgres 9.2 قد يكون لها اقتطاع أسرع من الإصدارات السابقة). كما هو الحال دائمًا ، ضع معيارًا لنظامك الخاص لمعرفة خصائصه.

الملاحظة 2: يمكن إرجاع truncate في postgres ، إذا كان في المعاملة: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

الملاحظة 3: يمكن أن يكون الاقتطاع ، مع الجداول الصغيرة ، أبطأ في بعض الأحيان من الحذف: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

2
rogerdpack

إضافة بعض المنظور التاريخي ...

يتطلب إسقاط جدول تحديث العديد من جداول النظام ، والتي تتطلب بدورها إجراء تغييرات جدول النظام هذه في معاملة واحدة (فكر في "بدء ترانسيد ، وحذف الأعمدة ، وحذف sysobjects ، والالتزام").

كما يتضمن "الجدول المنسدل" الحاجة إلى إلغاء تخصيص جميع صفحات البيانات/الفهرس المرتبطة بالجدول.

قبل سنوات عديدة ، ... تم تضمين عملية تخصيص الفضاء في المعاملة التي قامت أيضًا بتحديث جداول النظام ؛ وكانت النتيجة الصافية أنه كلما زاد عدد الصفحات المخصصة ، كلما استغرقت عملية إلغاء تخصيص الصفحات المذكورة وقتًا أطول ، تم ترك المعاملة (على جداول النظام) مفتوحة ، وبالتالي هناك فرصة أكبر لحظر (على جداول النظام) العمليات الأخرى التي تحاول إنشاء/إسقاط الجداول في tempdb (خاصة سيئة مع جميع الصفحات الأقدم == تأمين على مستوى الصفحة واحتمال الجدول تصعيد قفل المستوى).

كانت إحدى الطرق المبكرة المستخدمة (في ذلك الوقت) لتقليل التنازع على جداول النظام هي تقليل الوقت الذي تم فيه قفل الأقفال على جداول النظام ، وكانت إحدى الطرق السهلة (نسبيًا) للقيام بذلك هي إلغاء تخصيص صفحات البيانات/الفهرس قبل السقوط الطاولة.

في حين truncate table لا يتم إلغاء تخصيص الكل صفحات البيانات/الفهرس ، فإنه يلغي تخصيص جميع الصفحات (البيانات) باستثناء صفحة واحدة ؛ آخر 'الإختراق' كان بعد ذلك إسقاط جميع الفهارس قبل إسقاط الجدول (نعم ، فصل txn على sysindexes لكن txn أصغر للجدول المسقط).

عندما تفكر في ذلك (مرة أخرى ، منذ سنوات عديدة) ، كانت هناك فقط قاعدة بيانات واحدة "tempdb" ، وقدمت بعض التطبيقات ثقيلة استخدام تلك الأغنية " قاعدة بيانات tempdp ، أي فائدة من "الاختراقات" التي يمكن أن تقلل الخلاف على جداول النظام في "tempdb" كانت مفيدة ؛ بمرور الوقت ، تحسنت الأمور ... قواعد بيانات مؤقتة متعددة ، وتأمين على مستوى الصف على جداول النظام ، وطرق توزيع أفضل ، وما إلى ذلك.

في غضون ذلك استخدام truncate table لا يؤذي أي شيء إذا ترك في الكود.

1
markp-fuso