在具有数百万行的表上进行慢速 JOIN

RuS*_*SSe 8 performance azure-sql-database query-performance

在我的应用程序中,我必须连接数百万行的表。我有一个这样的查询:

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"  
Run Code Online (Sandbox Code Playgroud)

表“files”有1000万行,表“value_text”有4000万行。

这个查询太慢了,执行需要 40 秒(15000 个结果) - 3 分钟(65000 个结果)。

我曾想过将两个查询分开,但我不能,因为有时我需要按连接的列(值)排序...

我能做什么?我将 SQL Server 与 Azure 结合使用。具体而言,具有定价/模型层“PRS1 PremiumRS (125 DTU)”的 Azure SQL 数据库

我收到了大量数据,但我认为互联网连接不是瓶颈,因为在其他查询中,我也收到了大量数据,而且速度更快。

我尝试使用客户端表作为子查询并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)
)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

执行计划:

执行计划

*我翻译了这个问题中的表格和字段以获得一般理解。在这张图片中,“archivos”相当于“files”、“clients”的“clientes”和“value_text”的“valores_texto”。

执行计划没有DISTINCT

在此处输入图片说明

没有DISTINCT和的执行计划GROUP BY(查询快一点):

执行计划

查询测试(Krismorte 答案)

这是比以前慢的查询的执行计划。在这里,查询返回了超过 400.000 行,但即使对结果进行分页,也没有变化。

更详细的执行计划:https : //www.brentozar.com/pastetheplan/?id=By_UC2aBG

慢查询

这是比以前更快的查询的执行计划。在这里,查询返回超过 65.000 行。

更详细的执行计划:https : //www.brentozar.com/pastetheplan/?id=r116e6pSM

快速查询

Pau*_*ite 7

我认为你需要这个索引(正如Krismorte 建议的那样):

CREATE NONCLUSTERED INDEX [IX dbo.value_text id_file, id_field, value]
ON dbo.value_text (id_file, id_field, [value]);
Run Code Online (Sandbox Code Playgroud)

以下索引可能不是必需的,因为您似乎有一个合适的现有索引(问题中未提及),但为了完整起见,我将其包括在内:

CREATE NONCLUSTERED INDEX [IX dbo.files cid (id, year, name)]
ON dbo.files (cid)
INCLUDE
(
    id,
    [year],
    [name]
);
Run Code Online (Sandbox Code Playgroud)

将查询表达为:

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);
Run Code Online (Sandbox Code Playgroud)

这应该给出一个执行计划,如:

预期计划

OPTION (RECOMPILE)是可选的。仅当您发现不同参数值的理想平面形状不同时才添加。对于此类“参数嗅探”问题,还有其他可能的解决方案。

使用新索引,您可能还会发现原始查询文本生成的计划非常相似,而且性能也不错。

您可能还需要更新files表上的统计信息,因为提供的计划中的估计cid = 19不准确:

估计不准确


更新文件表上的统计信息后,查询在所有情况下都运行得非常快。如果将来我在“文件”表中添加更多字段,我应该更新索引还是什么?

如果您向文件表添加更多列(并在查询中使用/返回它们),您将需要将它们添加到索引(至少作为包含的列)以保持索引“覆盖”。否则,优化器可能会选择扫描文件表而不是查找索引中不存在的列。您也可以选择cid在该表上创建聚簇索引的一部分。这取决于。如果您想澄清这些要点,请提出一个新问题。