Hash Match innerer Join in einfacher Abfrage mit in-Anweisung

7

Ich führe den Ausführungsplan für die folgende Abfrage aus:

select m_uid from EmpTaxAudit 
where clientid = 91682 
and  empuid = 42100176452603
and newvalue in('Deleted','DB-Deleted','Added')

Hier ist der Ausführungsplan:

Ausführungsplan

Ich habe einen nicht gruppierten Index für die EmpTaxAudit-Tabelle für ClientId- und NewValue-Spalten, der oben als 14,9% der Ausführung angezeigt wird:

CREATE NONCLUSTERED INDEX [idx_EmpTaxAudit_clientid_newvalue] ON [dbo].

[EmpTaxAudit]
(
    [ClientID] ASC,
    [NewValue] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

Ich habe auch eine nicht gruppierte eindeutige Index-PK wie folgt:

ALTER TABLE [dbo].[EmpTaxAudit] ADD  CONSTRAINT [PK_EmpTaxAudit] PRIMARY KEY NONCLUSTERED 
(
    [ClientID] ASC,
    [EmpUID] ASC,
    [m_uid] ASC,
    [m_eff_start_date] ASC,
    [ReplacedOn] ASC,
    [ColumnName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

Triggercode in der Quelltabelle EmpTax:

CREATE                trigger [dbo].[trins_EmpTax]
on [dbo].[emptax]
for insert
as
begin
  declare 
    @intRowCount  int,
      @user          varchar(30)

  select @intRowCount = @@RowCount
  IF @intRowCount > 0 
  begin
     select @user = suser_sname()

     insert EmpTaxAudit (Clientid, empuid,m_uid,m_eff_start_date, ColumnName, ReplacedOn, ReplacedBy, OldValue,dblogin,newvalue)
     select Clientid, empuid,m_uid,m_eff_start_date,'taxcode', getdate(),IsNull(userid,@user), '', Left(@user,15),'Added'
        from inserted i
         where m_uid not in (select m_uid from EmpTaxAudit 
                    where clientid = i.clientid  and (newvalue = 'Deleted'
            or newvalue = 'DB-Deleted'
                        or newvalue = 'Added') and  empuid = i.empuid)
       and i.m_eff_end_date is null 

     insert EmpTaxAudit (Clientid, empuid,m_uid,m_eff_start_date, ColumnName, ReplacedOn, ReplacedBy, OldValue,dblogin,newvalue)
     select Clientid, empuid,m_uid,m_eff_start_date,'taxcode', getdate(),IsNull(userid,@user), '', Left(@user,15),'Deleted'
        from inserted i
         where m_uid not in (select m_uid from EmpTaxAudit 
                    where clientid = i.clientid  and (newvalue = 'Deleted'
            or newvalue = 'DB-Deleted'
                        or newvalue = 'Added') and  empuid = i.empuid)
       and i.m_eff_end_date is not null 


  end
end

Was kann ich tun, um die hohen Kosten für Hash Match (Inner Join) zu vermeiden?

Vielen Dank!

Adolfo Perez
quelle
1
Aber ist die Abfrage wirklich langsam? Diese Zahlen (74%, 11%, 14,9%) sind nicht sehr nützlich.
Ypercubeᵀᴹ
Es ist nicht langsam für einen einzelnen Datensatz, aber dies wird von einem mehrzeiligen Insert-Trigger aufgerufen, der sehr langsam ist
Adolfo Perez
Einfach den Auslöser hinzugefügt. Benötigen Sie die Tabelle zum Erstellen für EmpTaxAudit?
Adolfo Perez
Gibt es einen bestimmten Grund, warum Sie keinen Clustering-Index definiert haben (oder haben Sie ihn möglicherweise nicht in Ihr Beispiel aufgenommen)? Kein Clustering-Index erstellt einen Heap und sie können häufig eine suboptimale Leistung erbringen. Sie können versuchen, Ihren Primärschlüssel zu gruppieren, um festzustellen, ob dies einen Unterschied macht. Wenn Sie die XML-Datei für den Ausführungsplan veröffentlichen, können Sie möglicherweise weitere Schritte ausführen.
Scott Hodgin

Antworten:

3

Für die erste Abfrage wäre ein Index, der alle drei Spalten aus der WHEREKlausel verwendet und die Spalte aus der SELECTListe enthält, viel nützlicher:

-- index suggestion A
(clientid, empuid, newvalue) INCLUDE (m_uid)

oder ein Index, der speziell für diese Abfrage bestimmt ist:

-- index suggestion B
(clientid, empuid, m_uid)
WHERE newvalue in ('Deleted', 'DB-Deleted', 'Added')

In Bezug auf den Auslöser einige Kommentare:

  • Die erste angezeigte Abfrage wird nicht im Trigger angezeigt. Was erscheint, ist eine Verknüpfung von dieser Tabelle mit den insertedZeilen zu einer anderen Tabelle (die den Auslöser hat).
  • Mein Vorschlag B oben scheint besser geeignet zu sein, um vom Auslöser verwendet zu werden.
  • Der Trigger hat 2 fast identische insertAnweisungen. Warum? Ich denke, sie könnten in einer - und einfacheren - Einfügung und Verwendung kombiniert werden, NOT EXISTSanstatt NOT IN:

    insert EmpTaxAudit 
      ( Clientid, empuid, m_uid, m_eff_start_date, ColumnName, 
        ReplacedOn, ReplacedBy, OldValue, dblogin, 
        newvalue
      )
    select 
        Clientid, empuid, m_uid, m_eff_start_date, 'taxcode',
        getdate(), IsNull(userid,@user), '', Left(@user,15),
        case when m_eff_end_date is null 
            then 'Added' else 'Deleted'
        end
    from inserted i
    where not exists 
          ( select 1 
            from EmpTaxAudit 
            where m_uid = i.m_uid 
              and clientid = i.clientid  
              and empuid = i.empuid
              and newvalue in ('Deleted', 'DB-Deleted', 'Added')
          ) ;
ypercubeᵀᴹ
quelle
Schöne Erklärung mit Vorschlag A & B. Für +1
Md Haidar Ali Khan
1

Grund für Hash Match (innerer Join):

"Ein Hash-Match-Join tritt auf, wenn SQL Server zwei Tabellen verbindet, indem die Zeilen aus der kleineren der beiden zu verbindenden Tabellen gehasht und dann in eine Hash-Tabelle eingefügt werden. Anschließend wird die größere Tabelle zeilenweise gegen den kleineren Hash verarbeitet Tabelle, die nach Übereinstimmungen sucht, bei denen Zeilen verknüpft werden müssen. Da die kleinere der Tabellen die Werte in der Hash-Tabelle enthält, wird die Tabellengröße auf ein Minimum beschränkt, und da Hash-Werte anstelle von realen Werten verwendet werden, können Vergleiche durchgeführt werden Sehr schnell. Solange die gehashte Tabelle relativ klein ist, kann dies ein schneller Vorgang sein. Wenn andererseits beide Tabellen sehr groß sind, kann ein Hash-Match-Join im Vergleich zu anderen Join-Typen sehr ineffizient sein. ""

Ihre kleinere Tabelle gibt also eindeutig 16 Zeilen zurück, und Ihre größere Tabelle gibt 1,41.000 Zeilen zurück. Hier ist eine kleinere Tabelle ein Hash, was ziemlich gut ist. Jetzt wird sie mit 1,41.000 Zeilen verbunden, sodass die Kosten höher und die Ausführungszeit langsam sein müssen.

Sie müssen die Anzahl der Zeilen, die in einer großen Tabelle zurückgegeben werden, minimieren. In beiden Fällen erhalten Sie eine Indexsuche, damit der Index verwendet wird.

Ich denke, Sie müssen nur die Trigger-Abfrage neu schreiben.

CREATE  trigger [dbo].[trins_EmpTax]
on [dbo].[emptax]
for insert
as
begin

if @@rowcount = 0 
        return
    set nocount on

  declare 
    @intRowCount  int,
      @user          varchar(30)

  --select @intRowCount = @@RowCount
  if exists(select * from inserted)
  begin
     select @user = suser_sname()

     insert EmpTaxAudit (Clientid, empuid,m_uid,m_eff_start_date, ColumnName, ReplacedOn, ReplacedBy, OldValue,dblogin,newvalue)
     select Clientid, empuid,m_uid,m_eff_start_date,'taxcode', getdate(),IsNull(userid,@user), '', Left(@user,15)
     ,case when i.m_eff_end_date is null then 'Added' else 'Deleted' END
        from inserted i
         where not exists (select m_uid from EmpTaxAudit ETA
                    where i.m_uid=ETA.m_uid and  empuid = i.empuid and clientid = i.clientid  
                    and newvalue in('Deleted','DB-Deleted' , 'Added') 
                    )


     --insert EmpTaxAudit (Clientid, empuid,m_uid,m_eff_start_date, ColumnName, ReplacedOn, ReplacedBy, OldValue,dblogin,newvalue)
     --select Clientid, empuid,m_uid,m_eff_start_date,'taxcode', getdate(),IsNull(userid,@user), '', Left(@user,15),'Deleted'
     --   from inserted i
     --    where m_uid not in (select m_uid from EmpTaxAudit 
     --               where clientid = i.clientid  and (newvalue = 'Deleted'
     --       or newvalue = 'DB-Deleted'
     --                   or newvalue = 'Added') and  empuid = i.empuid)
     --  and i.m_eff_end_date is not null 


  end
end

Beachten Sie, dass Sie den Zustand innerhalb der NOT EXISTSKlausel korrigieren können , die geringfügig ist. Sie können jedoch perfekt funktionieren und eine bessere Leistung erzielen.

Erklären Sie auch, was der Clustered-Index in dieser Tabelle ist und warum so viele zusammengesetzte Primärschlüssel.

KumarHarsh
quelle
-1

Erste Kritik: Ich denke nicht, dass a PRIMARY KEYaus so vielen Feldern bestehen sollte. Ein Primärschlüssel soll lediglich eine einfache Kennung der Eindeutigkeit sein. Möglicherweise könnten Sie zwei Felder in Ihrer PK für viele-zu-viele Schnittpunkttabellen haben, obwohl ich persönlich immer einen Primärschlüssel mit einem Feld habe, auch für diese. Daher würde ich sagen, dass Ihre PK PK_EmpTaxAuditwirklich als eindeutiger Index neu definiert werden sollte.

Dann würde ich vorschlagen, hinzuzufügen newvalue zu den INCLUDEFeldern dieses Index :

create unique index IX_EmpTaxAudit(
  [ClientID] ASC,
  [EmpUID] ASC,
  [m_uid] ASC,
  [m_eff_start_date] ASC,
  [ReplacedOn] ASC,
  [ColumnName] ASC
)
include (newvalue)

Ich vermute, das wird Ihnen einen übersichtlicheren Ausführungsplan geben.

Shaul Behr
quelle
-1

Zunächst sollten Sie einen Clustered-Index (mit FILLFACTOR <= 95) für die Tabellen haben, um hohe E / A-Vorgänge zu vermeiden. Weil HEAP zu mehr Fragmentierung führt. Sie können also PK Non Clustered Index durch Clustered ersetzen.

Abgesehen davon sollten zur Vermeidung von HASH_MATCH alle in der SELECT-Anweisung verwendeten Spalten vorhanden sein und die WHERE-Klausel sollte sich in einem Index befinden. Entweder ersetzen Sie den nicht gruppierten Index idx_EmpTaxAudit_clientid_newvalue durch den folgenden oder erstellen einen neuen nicht gruppierten Index.

    CREATE NONCLUSTERED INDEX [idx_EmpTaxAudit_clientid_newvalue] ON [dbo].

    [EmpTaxAudit]
    (
        [ClientID] ASC,
        [NewValue] ASC,
        [newvalue]
    )
    INCLUDE (m_uid)
    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

Grund von HASH_MATCH

SQL Server Optimizer bevorzugt die Verwendung von Index anstelle von HEAP. In Ihrem Fall werden also Daten und Spalten angefordert, in denen die Bedingung auf zwei verschiedenen Indizes liegt. Aus diesem Grund hat SQL Server Optimizer beide Indizes verwendet und HASH_MATCH ausgeführt.

Vielen Dank

Rajesh Ranjan
quelle