Dans mon application, je dois joindre des tables avec des millions de lignes. J'ai une requête comme celle-ci:
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"
La table "files" a 10 millions de lignes et la table "value_text" a 40 millions de lignes.
Cette requête est trop lente, elle prend entre 40s (15000 résultats) - 3 minutes (65000 résultats) pour être exécutée.
J'avais pensé à diviser les deux requêtes, mais je ne peux pas car parfois j'ai besoin de trier par la colonne jointe (valeur) ...
Que puis-je faire? J'utilise SQL Server avec Azure. Plus précisément, Azure SQL Database avec tarification/niveau de modèle "PRS1 PremiumRS (125 DTU)" .
Je reçois beaucoup de données, mais je pense que la connexion Internet n'est pas un goulot d'étranglement, car dans d'autres requêtes, je reçois également beaucoup de données et elles sont plus rapides.
J'ai essayé d'utiliser la table client comme sous-requête et de supprimer DISTINCT
avec les mêmes résultats.
J'ai 1428 lignes dans la table client.
clients
table:
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
table:
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
table:
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
Plan d'exécution:
* J'ai traduit les tableaux et les champs de cette question pour une compréhension générale. Dans cette image, "archivos" est l'équivalent de "fichiers", "clientes" de "clients" et "valores_texto" de "value_text".
Plan d'exécution sans DISTINCT
:
Plan d'exécution sans DISTINCT
et GROUP BY
(interrogez un peu plus vite):
Il s'agit du plan d'exécution de la requête qui est plus lent qu'auparavant. Ici, la requête me renvoie plus de 400 000 lignes, mais même en paginant les résultats, il n'y a pas de changements.
Plan d'exécution plus détaillé: https://www.brentozar.com/pastetheplan/?id=By_UC2aBG
Et c'est le plan d'exécution de la requête qui est plus rapide qu'auparavant. Ici, la requête renvoie plus de 65 000 lignes.
Plan d'exécution plus détaillé: https://www.brentozar.com/pastetheplan/?id=r116e6pSM
Je pense que vous avez besoin de cet index (comme Krismorte a suggéré ):
CREATE NONCLUSTERED INDEX [IX dbo.value_text id_file, id_field, value]
ON dbo.value_text (id_file, id_field, [value]);
L'index suivant n'est probablement pas requis car vous semblez avoir un index existant approprié (non mentionné dans la question) mais je l'inclus pour être complet:
CREATE NONCLUSTERED INDEX [IX dbo.files cid (id, year, name)]
ON dbo.files (cid)
INCLUDE
(
id,
[year],
[name]
);
Exprimez la requête comme suit:
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);
Cela devrait donner un plan d'exécution comme:
La OPTION (RECOMPILE)
est facultative. Ajoutez uniquement si vous trouvez que la forme de plan idéale est différente pour différentes valeurs de paramètre. Il existe d'autres solutions possibles à ces problèmes de "détection de paramètres".
Avec le nouvel index, vous pouvez également trouver que le texte de requête d'origine produit un plan très similaire, également avec de bonnes performances.
Vous devrez peut-être également mettre à jour les statistiques sur la table files
, car l'estimation dans le plan fourni pour cid = 19
N'est pas exacte:
Après la mise à jour des statistiques sur la table des fichiers, la requête fonctionne très rapidement dans tous les cas. Si à l'avenir j'ajoute plus de champs dans la table "fichiers", dois-je mettre à jour l'index ou quelque chose?
Si vous ajoutez plus de colonnes à la table de fichiers (et que vous les utilisez/renvoyez dans votre requête), vous devrez les ajouter à l'index (au minimum en tant que colonnes incluses) pour garder l'index "couvrant". Sinon, l'optimiseur peut choisir d'analyser la table des fichiers plutôt que de rechercher les colonnes non présentes dans l'index. Vous pouvez également choisir d'intégrer cid
à un index de clustering sur cette table. Ça dépend. Posez une nouvelle question si vous souhaitez des éclaircissements sur ces points.
Eh bien, cette requête a été assez difficile, je modifie les ordres de filtrage et crée deux index de couverture
create index ix_stackexchange on [value_text] (id_file,id_field,value)
create index ix_stackexchange on [files] (id,cid,name,year)
la requête
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"
Plan de requête de résultat
Essayez ceci, j'espère que ce sera suffisant