Menghapus rekaman dengan lambat saat pemicu diaktifkan

17

Pikir ini diselesaikan dengan tautan di bawah ini - pekerjaan di sekitar bekerja - tetapi tambalan tidak. Bekerja dengan dukungan Microsoft untuk menyelesaikan.

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

Ok jadi saya punya masalah yang ingin saya buang ke StackOverflow untuk melihat apakah ada yang punya ide.

Perhatikan ini dengan SQL Server 2008 R2

Masalah: Menghapus 3000 catatan dari tabel dengan 15000 catatan membutuhkan waktu 3-4 menit saat pemicu diaktifkan dan hanya 3-5 detik saat pemicu dinonaktifkan.

Pengaturan meja

Dua tabel akan kita sebut Utama dan Sekunder. Sekunder berisi rekaman item yang ingin saya hapus jadi ketika saya melakukan hapus saya gabung ke tabel Sekunder. Proses berjalan sebelum pernyataan hapus untuk mengisi tabel sekunder dengan catatan yang akan dihapus.

Hapus Pernyataan:

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

Tabel ini memiliki banyak kolom dan sekitar 14 Indeks NC berbeda. Saya mencoba banyak hal yang berbeda sebelum saya menentukan pemicunya adalah masalahnya.

  • Aktifkan penguncian halaman (kami telah dimatikan secara default)
  • Statistik Berkumpul Secara Manual
  • Statistik pengumpulan otomatis dinonaktifkan
  • Kesehatan dan fragmentasi Indeks Terverifikasi
  • Menjatuhkan indeks berkerumun dari tabel
  • Memeriksa rencana pelaksanaan (tidak ada yang menunjukkan indeks yang hilang dan biayanya 70 persen terhadap penghapusan aktual dengan sekitar 28 persen untuk gabungan / penggabungan catatan

Pemicu

Tabel memiliki 3 pemicu (masing-masing untuk menyisipkan, memperbarui, dan menghapus operasi). Saya memodifikasi kode untuk memicu hapus untuk kembali, lalu untuk memilih satu untuk melihat berapa kali dipecat. Hanya menyala satu kali selama seluruh operasi (seperti yang diharapkan).

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

Untuk Rekap

  • Dengan Pemicu pada - pernyataan dibutuhkan 3-4 menit untuk menyelesaikan
  • Dengan Trigger off - statement akan selesai dalam 3-5 detik

Adakah yang tahu mengapa?

Perhatikan juga - tidak ingin mengubah arsitektur ini, menambahkan indeks hapus, dll. Sebagai solusi. Tabel ini adalah bagian utama untuk beberapa operasi data utama dan kami harus mengubah dan menyetelnya (indeks, penguncian halaman, dll.) Untuk memungkinkan operasi konkurensi utama untuk bekerja tanpa deadlock.

Berikut adalah rencana eksekusi xml (nama diubah untuk melindungi yang tidak bersalah)

<?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>
tsells
sumber

Jawaban:

12

Kerangka kerja versi baris yang diperkenalkan dalam SQL Server 2005 digunakan untuk mendukung sejumlah fitur, termasuk tingkat isolasi transaksi baru READ_COMMITTED_SNAPSHOTdan SNAPSHOT. Bahkan ketika tak satu pun dari level isolasi ini diaktifkan, AFTERpembuatan versi baris masih digunakan untuk pemicu (untuk memfasilitasi pembuatan tabel inserteddan deletedpseudo-tables), MARS, dan (dalam versi toko terpisah) pengindeksan online.

Seperti yang didokumentasikan , mesin dapat menambahkan postfix 14-byte ke setiap baris tabel yang diversi untuk tujuan-tujuan ini. Perilaku ini relatif terkenal, seperti penambahan data 14-byte ke setiap baris indeks yang dibangun kembali secara online dengan tingkat isolasi versi baris diaktifkan. Bahkan di mana level isolasi tidak diaktifkan, satu byte tambahan ditambahkan ke indeks non-cluster hanya ketika dibangun kembali ONLINE.

Ketika pemicu SETELAH hadir, dan versi lain akan menambahkan 14 byte per baris, optimasi ada di dalam mesin untuk menghindari ini, tetapi di mana alokasi ROW_OVERFLOWatau LOBtidak dapat terjadi. Dalam praktiknya, ini berarti ukuran maksimum yang mungkin dari sebuah baris harus kurang dari 8.060 byte. Dalam menghitung ukuran baris maksimum yang dimungkinkan, mesin mengasumsikan misalnya bahwa kolom VARCHAR (460) dapat berisi 460 karakter.

Perilaku ini paling mudah dilihat dengan AFTER UPDATEpemicu, meskipun prinsip yang sama berlaku untuk AFTER DELETE. Script berikut ini membuat tabel dengan panjang baris maksimum 8060 byte. Data cocok pada satu halaman, dengan 13 byte ruang kosong di halaman itu. Ada pemicu no-op, sehingga halaman terpecah dan informasi versi ditambahkan:

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;

Script menghasilkan output yang ditunjukkan di bawah ini. Tabel satu halaman dibagi menjadi dua halaman, dan panjang baris fisik maksimum telah meningkat dari 57 menjadi 71 byte (= +14 byte untuk informasi versi baris).

Perbarui contoh

DBCC PAGEmenunjukkan bahwa baris tunggal yang diperbarui memiliki Record Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71, sedangkan semua baris lain dalam tabel miliki Record Attributes = NULL_BITMAP; record Size = 57.

Script yang sama, dengan UPDATEdiganti oleh satu baris DELETEmenghasilkan output yang ditunjukkan:

DELETE dbo.Example
WHERE ID = 1;

Hapus contoh

Total ada satu baris yang lebih sedikit (tentu saja!), Tetapi ukuran baris fisik maksimum tidak bertambah. Informasi versi baris hanya ditambahkan ke baris yang diperlukan untuk tabel pseudo pemicu, dan baris itu akhirnya dihapus. Namun, pemisahan halaman tetap ada. Aktivitas pemisahan halaman ini bertanggung jawab atas kinerja lambat yang diamati ketika pemicu hadir. Jika definisi Padding2kolom diubah dari varchar(8000)menjadi varchar(7999), halaman tidak lagi terpecah.

Juga lihat posting blog ini oleh SQL Server MVP Dmitri Korotkevitch, yang juga membahas dampak pada fragmentasi.

Paul White Reinstate Monica
sumber
1
Ah, saya mengajukan pertanyaan tentang ini pada SO beberapa waktu yang lalu dan tidak pernah mendapat jawaban yang pasti.
Martin Smith
5

Nah di sini adalah tanggapan resmi dari Microsoft ... yang saya pikir merupakan cacat desain utama.

11/14/2011 - Tanggapan resmi telah berubah. Mereka tidak menggunakan log transaksi seperti yang dinyatakan sebelumnya. Menggunakan toko internal (level baris) untuk menyalin data yang diubah ke. Mereka masih belum bisa memastikan mengapa ini butuh waktu lama.

Kami memutuskan untuk menggunakan Alih-alih pemicu sebagai pengganti setelah menghapus pemicu.

Bagian SETELAH dari pemicu menyebabkan kita harus membaca log transaksi setelah penghapusan selesai dan membangun pemicu yang dimasukkan / dihapus tabel. Di sinilah kami menghabiskan banyak waktu dan dirancang untuk bagian SETELAH pemicunya. BUKAN pemicu akan mencegah perilaku memindai log transaksi dan membangun tabel yang dimasukkan / dihapus. Juga, seperti yang diamati bahwa hal-hal jauh lebih cepat jika kita drop semua kolom dengan nvarchar (maks), yang masuk akal karena fakta bahwa itu dianggap data LOB. Silakan baca artikel di bawah ini untuk informasi lebih lanjut mengenai data In-Row:

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

Ringkasan: SETELAH pemicu memerlukan pemindaian kembali melalui log transaksi setelah penghapusan selesai maka kita harus membuat dan memasukkan / menghapus tabel yang membutuhkan lebih banyak penggunaan log transaksi dan waktu.

Jadi sebagai rencana aksi, inilah yang kami sarankan saat ini:

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.
tsells
sumber
2

Menurut rencana semuanya berjalan dengan benar. Anda dapat mencoba menulis penghapusan sebagai GABUNG bukannya IN yang akan memberi Anda rencana yang berbeda.

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

Saya tidak yakin berapa banyak yang akan membantu. Ketika penghapusan berjalan dengan pemicu di atas meja, apa jenis tunggu untuk sesi melakukan penghapusan?

mrdenny
sumber