it-swarm.com.de

Slow JOIN für Tabellen mit Millionen von Zeilen

In meiner Anwendung muss ich Tabellen mit Millionen von Zeilen verbinden. Ich habe eine Frage wie diese:

SELECT DISTINCT "f"."id" AS "FileId"  
, "f"."name" AS "FileName"  
, "f"."year" AS "FileYear"  
, "vt"."value" AS "value"  
FROM files "f"  
JOIN "clients" "cl" ON("f"."cid" = "cl"."id" AND "cl"."id" = 10)  
LEFT JOIN "value_text" "vt" ON ("f"."id" = "vt"."id_file" AND "vt"."id_field" = 65739)  
GROUP BY "f"."id", "f"."name", "f"."year", "vt"."value"  

Die Tabelle "files" hat 10 Millionen Zeilen und die Tabelle "value_text" hat 40 Millionen Zeilen.

Diese Abfrage ist zu langsam. Die Ausführung dauert zwischen 40 Sekunden (15000 Ergebnisse) und 3 Minuten (65000 Ergebnisse).

Ich hatte darüber nachgedacht, die beiden Abfragen zu teilen, kann es aber nicht, weil ich manchmal nach der verbundenen Spalte (Wert) bestellen muss ...

Was kann ich machen? Ich verwende SQL Server mit Azure. Insbesondere Azure SQL-Datenbank mit Preis-/Modellschicht "PRS1 PremiumRS (125 DTUs)" .

Ich erhalte viele Daten, aber ich denke, die Internetverbindung ist kein Engpass, da ich bei anderen Abfragen auch viele Daten erhalte und diese schneller sind.

Ich habe versucht, die Client-Tabelle als Unterabfrage zu verwenden und DISTINCT mit denselben Ergebnissen zu entfernen.

Ich habe 1428 Zeilen in der Client-Tabelle.

Zusätzliche Information

clients Tabelle:

CREATE TABLE [dbo].[clients](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [code] [nvarchar](70) NOT NULL,
    [password] [nchar](40) NOT NULL,
    [name] [nvarchar](150) NOT NULL DEFAULT (N''),
    [email] [nvarchar](255) NULL DEFAULT (NULL),
    [entity] [int] NOT NULL DEFAULT ((0)),
    [users] [int] NOT NULL DEFAULT ((0)),
    [status] [varchar](8) NOT NULL DEFAULT ('inactive'),
    [created] [datetime2](7) NULL DEFAULT (getdate()),
    [activated] [datetime2](7) NULL DEFAULT (getdate()),
    [client_type] [varchar](10) NOT NULL DEFAULT ('normal'),
    [current_size] [bigint] NOT NULL DEFAULT ((0)),
CONSTRAINT [PK_clients_id] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON),
CONSTRAINT [clients$code] UNIQUE NONCLUSTERED 
(
    [code] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

files Tabelle:

CREATE TABLE [dbo].[files](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [cid] [bigint] NOT NULL DEFAULT ((0)),
    [eid] [bigint] NOT NULL DEFAULT ((0)),
    [year] [bigint] NOT NULL DEFAULT ((0)),
    [name] [nvarchar](255) NOT NULL DEFAULT (N''),
    [extension] [int] NOT NULL DEFAULT ((0)),
    [size] [bigint] NOT NULL DEFAULT ((0)),
    [id_doc] [bigint] NOT NULL DEFAULT ((0)),
    [created] [datetime2](7) NULL DEFAULT (getdate())
CONSTRAINT [PK_files_id] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON),
 CONSTRAINT [files$estructure_unique] UNIQUE NONCLUSTERED 
(
    [year] ASC,
    [name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

GO

ALTER TABLE [dbo].[files]  WITH NOCHECK ADD  CONSTRAINT [FK_files_client] FOREIGN KEY([cid])
REFERENCES [dbo].[clients] ([id])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[files] CHECK CONSTRAINT [FK_files_client]
GO

value_text Tabelle:

CREATE TABLE [dbo].[value_text](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [id_file] [bigint] NOT NULL DEFAULT ((0)),
    [id_field] [bigint] NOT NULL DEFAULT ((0)),
    [value] [nvarchar](255) NULL DEFAULT (NULL),
    [id_doc] [bigint] NULL DEFAULT (NULL)
 CONSTRAINT [PK_value_text_id] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

GO

ALTER TABLE [dbo].[value_text]  WITH NOCHECK ADD  CONSTRAINT [FK_valuesT_field] FOREIGN KEY([id_field])
REFERENCES [dbo].[fields] ([id])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[value_text] CHECK CONSTRAINT [FK_valuesT_field]
GO

Ausführungsplan:

(Execution plan

* Ich habe die Tabellen und Felder in dieser Frage zum allgemeinen Verständnis übersetzt. In diesem Bild entspricht "archivos" "files", "clientes" von "clients" und "valores_texto" von "value_text".

Ausführungsplan ohne DISTINCT:

(enter image description here

Ausführungsplan ohne DISTINCT und GROUP BY (etwas schneller abfragen):

(Execution Plan

Abfragetest (Antwort von Krismorte)

Dies ist der Ausführungsplan der Abfrage, der langsamer als zuvor ist. Hier gibt mir die Abfrage über 400.000 Zeilen zurück, aber selbst wenn die Ergebnisse paginiert werden, gibt es keine Änderungen.

Ausführungsplan detaillierter: https://www.brentozar.com/pastetheplan/?id=By_UC2aBG

(Slow Query

Und dies ist der Ausführungsplan der Abfrage, der schneller als zuvor ist. Hier gibt die Abfrage über 65.000 Zeilen zurück.

Ausführungsplan detaillierter: https://www.brentozar.com/pastetheplan/?id=r116e6pSM

(Fast Query

8
RuSSe

Ich denke, Sie brauchen diesen Index (als Krismorte schlug vor ):

CREATE NONCLUSTERED INDEX [IX dbo.value_text id_file, id_field, value]
ON dbo.value_text (id_file, id_field, [value]);

Der folgende Index ist wahrscheinlich nicht erforderlich, da Sie anscheinend über einen geeigneten vorhandenen Index verfügen (in der Frage nicht erwähnt), der Vollständigkeit halber füge ich ihn jedoch hinzu:

CREATE NONCLUSTERED INDEX [IX dbo.files cid (id, year, name)]
ON dbo.files (cid)
INCLUDE
(
    id,
    [year],
    [name]
);

Drücken Sie die Abfrage aus als:

SELECT
    FileId = F.id,
    [FileName] = F.[name],
    FileYear = F.[year],
    V.[value]
FROM dbo.files AS F
JOIN dbo.clients AS C
    ON C.id = F.cid
OUTER APPLY
(
        SELECT DISTINCT
            VT.[value]
        FROM dbo.value_text AS VT
        WHERE
            VT.id_file = F.id
            AND VT.id_field = 65739
) AS V
WHERE 
    C.id = 10
OPTION (RECOMPILE);

Dies sollte einen Ausführungsplan wie folgt ergeben:

(expected plan

Die OPTION (RECOMPILE) ist optional. Fügen Sie nur hinzu, wenn Sie feststellen, dass die ideale Planform für verschiedene Parameterwerte unterschiedlich ist. Es gibt andere mögliche Lösungen für solche "Parameter-Sniffing" -Probleme.

Mit dem neuen Index können Sie auch feststellen, dass der ursprüngliche Abfragetext einen sehr ähnlichen Plan erzeugt, auch mit guter Leistung.

Möglicherweise müssen Sie auch die Statistiken in der Tabelle files aktualisieren, da die Schätzung im bereitgestellten Plan für cid = 19 Nicht korrekt ist:

(Inaccurate estimate


Nach dem Aktualisieren der Statistiken in der Dateitabelle funktioniert die Abfrage in allen Fällen sehr schnell. Wenn ich in Zukunft weitere Felder in der Tabelle "Dateien" hinzufüge, sollte ich den Index aktualisieren oder so?

Wenn Sie der Dateitabelle weitere Spalten hinzufügen (und diese in Ihrer Abfrage verwenden/zurückgeben), müssen Sie sie dem Index hinzufügen (mindestens als eingeschlossene Spalten), damit der Index "bedeckt" bleibt. Andernfalls scannt das Optimierungsprogramm möglicherweise die Dateitabelle, anstatt die im Index nicht vorhandenen Spalten nachzuschlagen. Sie können stattdessen auch cid Teil eines Clustering-Index für diese Tabelle machen. Es hängt davon ab, ob. Stellen Sie eine neue Frage, wenn Sie diese Punkte klären möchten.

7
Paul White 9

Nun, diese Abfrage war ziemlich schwierig. Ich ändere die Filterordnungen und erstelle zwei Deckungsindizes

create index ix_stackexchange on [value_text] (id_file,id_field,value)
create index ix_stackexchange on [files] (id,cid,name,year)

die Abfrage

SELECT DISTINCT "f"."id" AS "FileId"  
, "f"."name" AS "FileName"  
, "f"."year" AS "FileYear"  
, "vt"."value" AS "value"  
FROM files "f"  
JOIN "clients" "cl" ON("f"."cid" = "cl"."id" )  
LEFT JOIN "value_text" "vt" ON ("f"."id" = "vt"."id_file" )  
where "cl"."id" = 10 AND "vt"."id_field" = 65739
GROUP BY "f"."id", "f"."name", "f"."year", "vt"."value"  

Ergebnisabfrageplan

(enter image description here

Versuchen Sie dies, ich hoffe, das wird genug sein

1
Krismorte