Bir tetikleyici etkinleştirildiğinde kayıtların yavaş silinmesi


17

Bunun aşağıdaki bağlantıyla çözüldüğünü düşündüm - etrafındaki çalışma - ama yama yapmaz. Çözmek için Microsoft desteği ile çalışma.

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

Tamam bu yüzden birisi bir fikri olup olmadığını görmek için StackOverflow atmak istedim bir sorun var.

Bunun SQL Server 2008 R2 ile olduğunu unutmayın

Sorun: 15000 kayıt içeren bir tablodan 3000 kayıt silmek, bir tetikleyici etkinleştirildiğinde 3-4 dakika ve tetikleyici devre dışı bırakıldığında sadece 3-5 saniye sürer.

Masa kurulumu

Ana ve İkincil diyeceğimiz iki tablo. İkincil, silmek istediğim öğelerin kayıtlarını içerir, böylece silme işlemini gerçekleştirdiğimde İkincil tabloya katılırım. İkincil tabloyu silinecek kayıtlarla doldurmak için delete deyiminden önce bir işlem çalışır.

Deyimi Sil:

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

Bu tabloda çok sayıda sütun ve yaklaşık 14 farklı NC Dizini vardır. Tetikleyicinin sorun olduğunu belirlemeden önce bir sürü farklı şey denedim.

  • Sayfa kilidini aç (varsayılan olarak kapattık)
  • İstatistikleri Manuel Olarak Topladı
  • İstatistiklerin otomatik olarak toplanması devre dışı bırakıldı
  • Doğrulanmış Endeks sağlığı ve parçalanması
  • Kümelenmiş dizini tablodan bıraktı
  • Yürütme planı incelendi (hiçbir şey eksik dizin olarak gösterilmiyordu ve maliyet, gerçek silmeye doğru yüzde 70 idi ve kayıtların birleştirilmesi / birleştirilmesi için yaklaşık yüzde 28

tetikleyiciler

Tabloda 3 tetikleyici vardır (ekleme, güncelleme ve silme işlemleri için birer tane). Silme tetikleyicisinin kodunu sadece geri dönecek şekilde değiştirdim, sonra kaç kez ateşlendiğini görmek için birini seçtim. Tüm operasyon boyunca (beklendiği gibi) sadece bir kez ateşlenir.

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

Özetle

  • Tetikleyici açıkken - ifadenin tamamlanması 3-4 dakika sürer
  • Tetikleyici kapalı - ifadenin tamamlanması 3-5 saniye sürer

Neden herhangi bir fikri olan var mı?

Ayrıca not - bu mimariyi değiştirmek, çözüm olarak kaldırma dizinleri eklemek vb. Bu tablo, bazı büyük veri işlemleri için merkezi bir parçadır ve büyük eşzamanlılık işlemlerinin kilitlenme olmadan çalışabilmesi için ince ayar yapmak ve ayarlamak zorunda kaldık (dizinler, sayfa kilitleme, vb.).

İşte xml yürütme planı (isimleri masum korumak için değiştirildi)

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

Yanıtlar:


12

SQL Server 2005'te tanıtılan satır sürüm çerçevesi, yeni işlem yalıtım düzeyleri READ_COMMITTED_SNAPSHOTve dahil olmak üzere bir dizi özelliği desteklemek için kullanılır SNAPSHOT. Bu yalıtım düzeylerinden hiçbiri etkinleştirilmediğinde bile, satır sürümleme, AFTERtetikleyiciler ( insertedve deletedsözde tabloların oluşturulmasını kolaylaştırmak için ), MARS ve (ayrı bir sürüm deposunda) çevrimiçi dizinleme için hala kullanılmaktadır .

De belgelenmiş , motor bu amaçlardan herhangi biri için sürüm bir tablodaki her satır için bir 14-bayt ekine ekleyebilir. Bu davranış, bir satır sürüm oluşturma düzeyi etkinleştirilmiş olarak çevrimiçi olarak yeniden oluşturulan bir dizinin her satırına 14 baytlık verilerin eklenmesi gibi nispeten iyi bilinmektedir . Yalıtım düzeylerinin etkin olmadığı durumlarda bile, kümelenmemiş dizinlere yalnızca yeniden oluşturulduğunda bir bayt eklenir ONLINE.

Bir AFTER tetikleyicisi varsa ve sürüm oluşturma aksi takdirde satır başına 14 bayt ekleyecekse, bunu önlemek için motorda bir optimizasyon vardır , ancak a ROW_OVERFLOWveya LOBayırma yapılamaz. Pratikte, bu, bir satırın mümkün olan maksimum boyutunun 8060 bayttan az olması gerektiği anlamına gelir. Mümkün olan maksimum satır boyutlarını hesaplarken , motor örneğin bir VARCHAR (460) sütununda 460 karakter içerebileceğini varsayar.

AFTER UPDATEAynı ilke için geçerli olsa da, davranışı bir tetikleyici ile görmek en kolaydır AFTER DELETE. Aşağıdaki komut dosyası, en fazla satır uzunluğu 8060 bayt olan bir tablo oluşturur. Veriler, o sayfada 13 bayt boş alan bulunan tek bir sayfaya sığar. Bir çalışmayan tetikleyici vardır, bu nedenle sayfa bölünür ve sürüm bilgileri eklenir:

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;

Komut dosyası, aşağıda gösterilen çıktıyı üretir. Tek sayfalık tablo iki sayfaya ayrılmıştır ve maksimum fiziksel satır uzunluğu 57'den 71 bayta çıkarılmıştır (satır sürüm bilgisi için = +14 bayt).

Güncelleme örneği

DBCC PAGERecord Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71tablodaki diğer satırların hepsinin güncellenmiş olduğunu gösterir Record Attributes = NULL_BITMAP; record Size = 57.

Aynı komut dosyası, UPDATEtek bir satırla değiştirildiğinde DELETEgösterilen çıktı oluşturulur:

DELETE dbo.Example
WHERE ID = 1;

Örneği sil

Toplamda daha az satır var (elbette!), Ancak maksimum fiziksel satır boyutu artmadı. Satır sürüm bilgileri yalnızca tetikleyici sözde tablolar için gereken satırlara eklenir ve bu satır en sonunda silinir. Bununla birlikte, sayfa ayrımı kalır. Bu sayfa bölme etkinliği, tetikleyici mevcut olduğunda gözlenen yavaş performanstan sorumludur. Tanımı ise Padding2kolondan değiştirilir varchar(8000)için varchar(7999)artık böler sayfasında.

Ayrıca , fragmantasyon üzerindeki etkisini de tartışan SQL Server MVP Dmitri Korotkevitch'in bu blog gönderisine bakın .


1
Ah, bir süre önce SO hakkında bir soru sordum ve kesin bir cevap alamadım.
Martin Smith

5

İşte Microsoft'un resmi yanıtı ... Bence büyük bir tasarım hatası.

14.11.2011 - Resmi yanıt değişti. İşlem günlüğünü daha önce belirtildiği gibi kullanmıyorlar. Değiştirilen verileri kopyalamak için dahili depoyu (satır seviyesi) kullanıyorlar. Neden bu kadar uzun sürdüğünü hala belirleyemiyorlar.

Silme sonrası tetikleyicileri yerine tetikleyiciler yerine kullanmaya karar verdik.

Tetiğin AFTER kısmı, silme işlemi tamamlandıktan sonra işlem günlüğünü okumamıza ve tetikleyici eklenen / silinen tabloyu oluşturmamıza neden olur. Burası büyük miktarda zaman harcadığımız yerdir ve tetiğin SONRA kısmı için tasarım gereğidir. INSTEAD OF tetikleyicisi, işlem günlüğünü tarama ve eklenen / silinen bir tablo oluşturma davranışını önler. Ayrıca, tüm sütunları nvarchar (max) ile düşürürsek, işlerin çok daha hızlı olduğu gözlemlenmiştir, bu da LOB verileri olarak kabul edildiğinden mantıklıdır. Satır İçi veriler hakkında daha fazla bilgi için lütfen aşağıdaki makaleye bakın:

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

Özet: Tetikten SONRA, silme işlemi bittikten sonra işlem günlüğünün yeniden taranmasını gerektirir, ardından işlem günlüğü ve zamanının daha fazla kullanılmasını gerektiren tablo oluşturup ekledik / sildik.

Bir eylem planı olarak, şu anda önerdiğimiz şey budur:

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.

2

Plana göre her şey doğru gidiyor. Silme işlemini, size farklı bir plan verecek olan bir IN yerine bir JOIN olarak yazmayı deneyebilirsiniz.

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

Ancak bunun ne kadar yardımcı olacağından emin değilim. Silme işlemi tablodaki tetikleyicilerle çalıştığında, silme işlemini gerçekleştiren oturum için bekleme türü nedir?

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.