AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 219575
Accepted
Jez
Jez
Asked: 2018-10-09 12:56:05 +0800 CST2018-10-09 12:56:05 +0800 CST 2018-10-09 12:56:05 +0800 CST

我怎样才能使这个嵌套查询更有效率?

  • 772

我有 3 个表:Room、Conference和Participant。 Room有很多Conferences,Conference有很多Participants。我需要我的查询来显示来自的字段Room,以及它具有的关联数量,以及每个具有Conferences的关联数量的总和。这是我为获取此信息而编写的查询的简化版本;首先,我刚刚选择了房间 ID:ParticipantConferenceSELECT

SELECT TOP(1000)
  rm.[Id]
FROM
  [Room] rm
LEFT JOIN (
  SELECT
    conf.[Id] AS [ConferenceId],
    MIN(conf.[Name]) AS [ConferenceName],
    MIN(conf.[RoomId]) AS [RoomId],
    COUNT(part.[Id]) AS CalcConferenceParticipantCount
  FROM
    [Conference] conf
  LEFT JOIN
    [Participant] part on part.[ConferenceId] = conf.[Id]
  GROUP BY
    conf.[Id]
  ) confData ON confData.[RoomId] = rm.[Id]
GROUP BY
  rm.[Id]

这是非常快的,因为 SQL Server 能够仅从子查询中提取数据Room并且几乎忽略了子查询(请参见下图中的试验 1 - 试验 4)。然后我在ConferenceName子查询的字段中添加,以及每个房间的会议数量计数:

SELECT TOP(1000)
  rm.[Id],
  COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
  MIN(confData.[ConferenceName])
FROM
  [Room] rm
LEFT JOIN (
  SELECT
    conf.[Id] AS [ConferenceId],
    MIN(conf.[Name]) AS [ConferenceName],
    MIN(conf.[RoomId]) AS [RoomId],
    COUNT(part.[Id]) AS CalcConferenceParticipantCount
  FROM
    [Conference] conf
  LEFT JOIN
    [Participant] part on part.[ConferenceId] = conf.[Id]
  GROUP BY
    conf.[Id]
  ) confData ON confData.[RoomId] = rm.[Id]
GROUP BY
  rm.[Id]

这大大降低了查询速度,大约降低了 100 倍(请参见下图中的试验 5 - 试验 7)。然后我从子查询中添加了参与者计数,这意味着使用了 2 个级别的聚合函数:

SELECT TOP(1000)
  rm.[Id],
  COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
  MIN(confData.[ConferenceName]),
  SUM(confData.[CalcConferenceParticipantCount]) AS CalcRoomParticipantCount
FROM
  [Room] rm
LEFT JOIN (
  SELECT
    conf.[Id] AS [ConferenceId],
    MIN(conf.[Name]) AS [ConferenceName],
    MIN(conf.[RoomId]) AS [RoomId],
    COUNT(part.[Id]) AS CalcConferenceParticipantCount
  FROM
    [Conference] conf
  LEFT JOIN
    [Participant] part on part.[ConferenceId] = conf.[Id]
  GROUP BY
    conf.[Id]
  ) confData ON confData.[RoomId] = rm.[Id]
GROUP BY
  rm.[Id]

这进一步将查询速度减慢了大约 4 倍(参见下图中的试验 8 - 试验 10)。以下是包含 10 次试验数据的客户统计数据:

客户统计

下面是慢查询的查询计划:https ://www.brentozar.com/pastetheplan/?id=SJpyeec5Q

有没有一种方法可以使这种查询——我计算子查询聚合的聚合——更有效?

sql-server performance
  • 1 1 个回答
  • 1263 Views

1 个回答

  • Voted
  1. Best Answer
    Joe Obbish
    2018-10-10T20:14:54+08:002018-10-10T20:14:54+08:00

    我通过查看表中的行数模拟数据,为它们提供均匀的数据分布,并对模式进行猜测:

    DROP TABLE IF EXISTS [Room];
    
    CREATE TABLE [Room] (
        [Id] BIGINT NOT NULL,
        FILLER VARCHAR(200) NOT NULL,
        PRIMARY KEY ([Id])
    );
    
    INSERT INTO [Room] WITH (TABLOCK)
    SELECT TOP (3088) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 200)
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
    OPTION (MAXDOP 1);
    
    
    DROP TABLE IF EXISTS [Conference];
    
    CREATE TABLE [Conference] (
        [Id] BIGINT NOT NULL,
        [Name] VARCHAR(30) NOT NULL,
        [RoomId] BIGINT NOT NULL,
        FILLER VARCHAR(200) NOT NULL,
        PRIMARY KEY ([Id])
    );
    
    
    INSERT INTO [Conference] WITH (TABLOCK)
    SELECT RN
    , 'MY FAVORITE MEETING ROOM'
    , 1 + RN % 3088
    , REPLICATE('Z', 200)
    FROM
    (
        SELECT TOP (97413) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
        FROM master..spt_values t1
        CROSS JOIN master..spt_values t2
    ) q
    OPTION (MAXDOP 1);
    
    
    DROP TABLE IF EXISTS [Participant];
    
    CREATE TABLE [Participant] (
        [Id] BIGINT NOT NULL,
        [ConferenceId] BIGINT NOT NULL,
        FILLER VARCHAR(200) NOT NULL,
        PRIMARY KEY ([Id])
    );
    
    
    INSERT INTO [Participant] WITH (TABLOCK)
    SELECT RN
    , 1 + RN % 97413
    , REPLICATE('Z', 200)
    FROM
    (
        SELECT TOP (235323) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
        FROM master..spt_values t1
        CROSS JOIN master..spt_values t2
    ) q
    OPTION (MAXDOP 1);
    
    
    CREATE INDEX NCI_Part ON [Participant] ([ConferenceId]) INCLUDE (Id);
    

    我对架构所做的最重要的假设是Id列是[Conference]表的主键。考虑到查询计划和涉及的索引名称,这似乎是合理的。

    在我的机器上,我获得了与您相同的查询计划,但我的起始查询仅占用 163 毫秒的 CPU。我假设差异归结为硬件、数据分布的差异,以及我没有将数据返回给客户端这一事实。

    我首先想到的是派生表GROUP BY中的不必要项。是表的主键,因此您不需要所有聚合。有了正确的索引(对于这种特殊情况,您已经有了),子查询不一定是坏事。重写你必须删除的内容:confDataIdGROUP BY

    SELECT TOP(1000)
      rm.[Id],
      COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
      MIN(confData.[ConferenceName]),
      SUM(confData.[CalcConferenceParticipantCount]) AS CalcRoomParticipantCount
    FROM
      [Room] rm
    LEFT JOIN (
      SELECT
        conf.[Id] AS [ConferenceId],
        conf.[Name] AS [ConferenceName],
        conf.[RoomId] AS [RoomId],
        (
            SELECT COUNT(part.[Id])
            FROM [Participant] part
            WHERE part.[ConferenceId] = conf.[Id]
        ) AS CalcConferenceParticipantCount
      FROM
        [Conference] conf
      ) confData ON confData.[RoomId] = rm.[Id]
    GROUP BY
      rm.[Id]
    OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
    

    这导致流聚合被进一步下推到计划中:

    子查询

    上传的计划占用 113 毫秒的 CPU。存在相同的运算符,但其中一些运算符处理的行数较少,从而节省了时间。您可以通过在[Conference]with上定义覆盖索引Id作为索引键来提高此查询的效率。这似乎是一件奇怪的事情,但您的聚簇索引扫描占用了总查询时间的 10%,并且可能包括您不需要的列。

    如果你想让查询更快,你也可以考虑索引视图。当您可以定义一个简单的索引视图来为您执行聚合时,为什么每次都执行聚合?

    CREATE VIEW IndexedViewOnParticipant WITH SCHEMABINDING
    AS
    SELECT [ConferenceId], COUNT_BIG([Id]) CntId, COUNT_BIG(*) Cnt
    FROM dbo.[Participant]
    GROUP BY [ConferenceId];
    
    GO
    
    CREATE UNIQUE CLUSTERED INDEX CI ON IndexedViewOnParticipant ([ConferenceId]);
    

    这将导致在表上执行 DML 时会多一点空间和一点点开销。总的来说,我认为这是索引视图的一个很好的用例。再次重写查询:

    SELECT TOP(1000)
      rm.[Id],
      COUNT(confData.[ConferenceId]) AS CalcRoomConferenceCount,
      MIN(confData.[ConferenceName]),
      SUM(confData.[CalcConferenceParticipantCount]) AS CalcRoomParticipantCount
    FROM 
      [Room] rm
    LEFT JOIN (
      SELECT
        conf.[Id] AS [ConferenceId],
        conf.[Name] AS [ConferenceName],
        conf.[RoomId] AS [RoomId],
        (
            SELECT CntId
            FROM IndexedViewOnParticipant part WITH (NOEXPAND)
            WHERE part.[ConferenceId] = conf.[Id]
        ) AS CalcConferenceParticipantCount
      FROM
        [Conference] conf
      ) confData ON confData.[RoomId] = rm.[Id]
    GROUP BY
      rm.[Id]
    OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
    

    SQL Server 同意我的评估,这是一个好主意,CPU 时间下降到 78 ms。

    在我的机器上,我能够使查询更快,但是这开始进入有点冒险的优化,因为它可能需要LOOP JOIN提示。当您的查询或表中的数据发生变化时,该提示可能不是一个好主意。它也可能不适合您的硬件。这种方法背后的想法是创建一个合适的索引[Conference]并充分利用TOP只执行嵌套循环的计划。这是我添加的索引:

    CREATE INDEX NCI_Conf ON [Conference] ([RoomId]) INCLUDE ([Name]);
    

    运行与之前相同的查询并LOOP JOIN提示我得到以下计划:

    循环连接提示

    该查询只占用了 58 毫秒的 CPU 时间。值得一提的是,我注意到在这个阶段请求实际计划会增加相当多的相对开销。我想到的所有其他可能的优化对于生产来说都是不安全的,所以我会在这里停下来。

    最后想一想,您真的要返回 1000 行任意行和最小会议名称吗?这些信息对您的最终用户有用吗?

    • 2

相关问题

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

  • 我在哪里可以找到mysql慢日志?

  • 如何优化大型数据库的 mysqldump?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve