私のアプリケーションでは、数百万行のテーブルを結合する必要があります。次のようなクエリがあります。
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
実行計画:
*一般的な理解のために、この質問の表とフィールドを翻訳しました。この画像では、「archivos」は「files」、「clientes」の「clients」、「valores_texto」の「value_text」と同等です。
DISTINCT
なしの実行計画:
DISTINCT
とGROUP BY
なしの実行プラン(クエリが少し速くなります):
これは、以前より遅いクエリの実行プランです。ここでは、クエリが400.000行以上を返しますが、結果にページ番号を付けても、変更はありません。
実行計画の詳細: https://www.brentozar.com/pastetheplan/?id=By_UC2aBG
そして、これは以前よりも高速なクエリの実行プランです。ここでは、クエリは65.000行以上を返します。
実行計画の詳細: https://www.brentozar.com/pastetheplan/?id=r116e6pSM
私はあなたがこのインデックスを必要とすると思います( 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);
_
これにより、次のような実行計画が得られます。
OPTION (RECOMPILE)
はオプションです。パラメータ値が異なると理想的な平面形状が異なる場合にのみ追加してください。このような「パラメータスニッフィング」問題には、他に考えられる解決策があります。
新しいインデックスを使用すると、元のクエリテキストが非常によく似たプランを作成し、パフォーマンスも向上することがあります。
_cid = 19
_の提供されたプランの見積もりが正確でないため、files
テーブルの統計を更新する必要がある場合もあります。
ファイルテーブルの統計を更新した後、クエリはすべてのケースで非常に高速に動作します。今後、「files」テーブルにフィールドを追加する場合、インデックスなどを更新する必要がありますか?
ファイルテーブルにさらに列を追加する場合(およびクエリでそれらを使用/返す場合)、それらの列をインデックスに追加して(最低でも含まれる列として)、インデックスを「カバー」し続ける必要があります。そうでない場合、オプティマイザは、インデックスに存在しない列を検索するのではなく、ファイルテーブルをスキャンすることを選択する場合があります。代わりに、そのテーブルのクラスタリングインデックスの一部をcid
にすることもできます。場合によります。これらの点について明確にしたい場合は、新しい質問をしてください。
さて、このクエリはかなり大変でした、私はフィルター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"
結果クエリプラン
これを試してください、それで十分でしょう