web-dev-qa-db-ja.com

数百万行のテーブルでJOINが遅い

私のアプリケーションでは、数百万行のテーブルを結合する必要があります。次のようなクエリがあります。

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"  

テーブル「files」には1,000万行があり、テーブル「value_text」には4,000万行があります。

このクエリは遅すぎるため、実行に40秒(15000結果)〜3分(65000結果)かかります。

2つのクエリを分割することを考えていましたが、結合された列(値)で並べ替える必要がある場合があるため、できません...

私に何ができる? AzureでSQL Serverを使用しています。具体的には、Azure SQL Databaseの価格/モデル階層「PRS1 PremiumRS(125 DTU)」

大量のデータを受信して​​いますが、インターネット接続はボトルネックではないと思います。他のクエリでも大量のデータを受信するため、データが高速になるためです。

クライアントテーブルをサブクエリとして使用し、DISTINCTを削除して同じ結果を得ようとしました。

クライアントテーブルに1428行あります。

追加情報

clientsテーブル:

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テーブル:

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テーブル:

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

実行計画:

Execution plan

*一般的な理解のために、この質問の表とフィールドを翻訳しました。この画像では、「archivos」は「files」、「clientes」の「clients」、「valores_texto」の「value_text」と同等です。

DISTINCTなしの実行計画:

enter image description here

DISTINCTGROUP BYなしの実行プラン(クエリが少し速くなります):

Execution Plan

クエリテスト(クリスモート回答)

これは、以前より遅いクエリの実行プランです。ここでは、クエリが400.000行以上を返しますが、結果にページ番号を付けても、変更はありません。

実行計画の詳細: https://www.brentozar.com/pastetheplan/?id=By_UC2aBG

Slow Query

そして、これは以前よりも高速なクエリの実行プランです。ここでは、クエリは65.000行以上を返します。

実行計画の詳細: https://www.brentozar.com/pastetheplan/?id=r116e6pSM

Fast Query

8
RuSSe

私はあなたがこのインデックスを必要とすると思います( Krismorteが示唆したように ):

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

次のインデックスは、適切な既存のインデックス(質問には記載されていません)があるため、おそらく必要ではありませんが、完全を期すために含めています。

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

クエリを次のように表現します。

_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);
_

これにより、次のような実行計画が得られます。

expected plan

OPTION (RECOMPILE)はオプションです。パラメータ値が異なると理想的な平面形状が異なる場合にのみ追加してください。このような「パラメータスニッフィング」問題には、他に考えられる解決策があります。

新しいインデックスを使用すると、元のクエリテキストが非常によく似たプランを作成し、パフォーマンスも向上することがあります。

_cid = 19_の提供されたプランの見積もりが正確でないため、filesテーブルの統計を更新する必要がある場合もあります。

Inaccurate estimate


ファイルテーブルの統計を更新した後、クエリはすべてのケースで非常に高速に動作します。今後、「files」テーブルにフィールドを追加する場合、インデックスなどを更新する必要がありますか?

ファイルテーブルにさらに列を追加する場合(およびクエリでそれらを使用/返す場合)、それらの列をインデックスに追加して(最低でも含まれる列として)、インデックスを「カバー」し続ける必要があります。そうでない場合、オプティマイザは、インデックスに存在しない列を検索するのではなく、ファイルテーブルをスキャンすることを選択する場合があります。代わりに、そのテーブルのクラスタリングインデックスの一部をcidにすることもできます。場合によります。これらの点について明確にしたい場合は、新しい質問をしてください。

7
Paul White 9

さて、このクエリはかなり大変でした、私はフィルターordesを変更し、2つのカバーインデックスを作成します

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

クエリ

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"  

結果クエリプラン

enter image description here

これを試してください、それで十分でしょう

1
Krismorte