【问题标题】:Slow query with many rows (simple query with no joins)多行慢查询(没有连接的简单查询)
【发布时间】:2018-08-22 05:26:16
【问题描述】:

我有一个查询通常需要五六分钟才能运行。它有一个WHEREORDER BY 子句,但没有JOINs。但是,如果我将查询简化为:

SELECT * FROM ReportIndex

运行仍然需要五六分钟。

该表有超过 1100 万行。

由于没有连接,我看不出索引如何使它更快。谁能建议我可以尝试加快此查询的其他任何内容?

更新:

这是我正在使用的实际查询。它返回 6,668,324 行。

SELECT '1' AS vrsID,
    lprKey,
    lprRptName,
    lprTitle,
    lprDate,
    lprOwner,
    lprUserID,
    lprArchiveDate,
    lprTrackDate,
    lprActionView,
    lprActionEmail,
    lprActionExcel,
    lprActionForward,
    lprActionReassign,
    lprActionDownload,
    lprActionLocalPrint,
    lprActionServerPrint,
    lprPageCount,
    lprBytes,
    lprDataType,
    lprArchived,
    lprJobName,
    lprViewed,
    lprRptID
FROM ReportIndex
WHERE (lprOwner IN ('admin', 'APAdmin', 'APClerk', 'AP-Tab-700', 'AP-Tab-A-B', 'AP-Tab-A-K_EMP', 'AP-Tab-AP520', 'AP-Tab-CAN', 'AP-Tab-C-E', 'AP-Tab-COMM', 'AP-Tab-Confidential', 'AP-Tab-EE-Waiting', 'AP-Tab-F-O', 'AP-Tab-Historical', 'AP-Tab-LCD', 'AP-Tab-LCD_EMP', 'AP-Tab-LEA', 'AP-Tab-LPP', 'AP-Tab-LPS', 'AP-Tab-LPS_EMP', 'AP-Tab-LSI', 'AP-Tab-LTI', 'AP-Tab-L-Z_EMP', 'AP-Tab-P-R', 'AP-Tab-S-Z', 'AP-Tab-Unknown', 'Group-Category-VendorDocuments', 'Group-Quality-Control', 'Group-VendorDocs', 'HRAdmin', 'HR-Category-Payroll', 'HR-Category-Performance', 'HR-Category-Personnel', 'HR-Category-Upload', 'HR-Document-Delete', 'HR-Document-Index', 'HR-Group-DocumentMaintenance', 'HR-IndexQueue-Email', 'HRROLE01', 'HRROLE02', 'HRROLE03', 'HRROLE04', 'HRROLE05', 'HRROLE06', 'HRROLE09', 'HRROLE10', 'HRROLE11', 'HRROLE12', 'HRROLE13', 'HRROLE14', 'HRROLE15', 'HRROLE16', 'HRROLE17', 'HRROLE18', 'HRROLE19', 'HRROLE21', 'HRROLE23', 'HRROLE24', 'HRROLE25', 'HRROLE26', 'HRROLE28', 'HRROLE29', 'HRROLE30', 'HRROLE31', 'HRROLE34', 'HRROLE35', 'HRROLE36', 'HRROLE37', 'HRROLE39', 'HRROLE41', 'HRROLE42', 'HRROLE43', 'HRROLE44', 'HRROLE45', 'HRROLE46', 'HRROLE47', 'HRROLE48', 'HRROLE49', 'HRROLE50', 'HRROLE51', 'HRROLE52', 'HRROLE53', 'HRROLE54', 'HRROLE55', 'HRROLE56', 'HRROLE57', 'HRROLE58', 'HRROLE59', 'HRROLE60', 'HRROLE61', 'HRROLE62', 'HRROLE63', 'WFAdmin', 'AccountsPayable')
AND lprArchived = 0 AND lprPendingDelete = 0) AND lprDone=0
ORDER BY lprDate DESC

更新 2:

执行计划如下:https://www.brentozar.com/pastetheplan/?id=Skjuu8sLX

【问题讨论】:

  • 你真的需要SELECT所有的列和所有的行吗?使用WHERE 子句 - 结果返回多少行?可能可以创建索引来优化搜索,但首先要告诉您是否需要所有列。
  • '执行计划太大' - ReportIndex是表还是视图?
  • plain select的执行计划怎么能大?那就上传到这里看看吧:brentozar.com/pastetheplan
  • "here" - 我的意思是评论末尾的“brentozar”网址。并在此处(在问题中)分享它的链接。 @JonathanWood
  • @JonathanWood Gordon 回答的倒数第二段。与 MJH 的建议相同。除非您的查询变得更有选择性,否则您无能为力。

标签: sql sql-server sqlperformance


【解决方案1】:

评论太长了。

您的查询本质上是:

SELECT . . .
FROM ReportIndex
WHERE lprOwner IN (<big list>) AND
      lprArchived = 0 AND
      lprPendingDelete = 0 AND
      lprDone = 0
ORDER BY lprDate DESC

标准推荐是像ReportIndex(lprArchived, lprPendingDelete, lprDone, lprOwner, lprDate) 这样的索引。

但是,您的查询从 1200 万行中选择了 660 万行。索引无济于事,因为SELECT 的选择性不够。如果您选择 6600 行,那么索引可能会有很大帮助。

你无能为力。如果这真的很重要,您可以在(lprDate) 上创建一个聚集 索引。这将需要一些时间来创建——所有数据都必须在数据页面上进行排序。

然后应该通过按顺序读取数据并应用WHERE 条件来继续查询。

【讨论】:

  • 我仍在测试,但正如问题中所述,即使没有 WHERE 子句,性能似乎也很慢。
  • 查询速度慢有两个原因: 1. 您选择了很多数据(执行计划中的表扫描 23%)。和 2. 排序运算符(执行计划的 74%)。当您选择这么多行时,您无法对表扫描做任何事情,但您可以通过在 lprData 列上创建聚集索引来对排序运算符做一些事情。
  • @MJH:你能解释一下为什么它应该是聚集索引而不是非聚集索引吗?
  • 如果您创建一个非聚集索引,它必须包含查询中引用的所有列(按列排序、where 子句列、连接列和选择列表),否则计划将不得不这样做针对每一行的数据进行“查找”。将聚集索引放在排序列上(lprDate DESC)将导致磁盘上的数据按该顺序存储,因此读取操作将按照排序所需的顺序从磁盘读取数据,从而无需计划中的排序运算符。
  • 在这种情况下,主键对性能的影响很小。编辑:虽然,我应该说所有表都有一个 PK 是一个好习惯。
【解决方案2】:

为了提高效率,尽量避免使用 IN 子句。如果您在 IN 中有大量值,则可能会非常慢。为了优化它通常更好地将这些值添加到 TVP 然后加入它。然后,您还可以添加索引以提高连接效率。试一试:

DECLARE @tvp_lprOwner AS TABLE (owner NVARCHAR(255) NOT NULL);

INSERT INTO @tvp_lprOwner (owner)
VALUES ('admin') , ('APAdmin') , ('APClerk') , ('AP-Tab-700') , ('AP-Tab-A-B') , ('AP-Tab-A-K_EMP') , ('AP-Tab-AP520') , ('AP-Tab-CAN') , ('AP-Tab-C-E') , ('AP-Tab-COMM') , ('AP-Tab-Confidential') , 
('AP-Tab-EE-Waiting') , ('AP-Tab-F-O') , ('AP-Tab-Historical') , ('AP-Tab-LCD') , ('AP-Tab-LCD_EMP') , ('AP-Tab-LEA') , ('AP-Tab-LPP') , ('AP-Tab-LPS') , ('AP-Tab-LPS_EMP') , ('AP-Tab-LSI') , 
('AP-Tab-LTI') , ('AP-Tab-L-Z_EMP') , ('AP-Tab-P-R') , ('AP-Tab-S-Z') , ('AP-Tab-Unknown') , ('Group-Category-VendorDocuments') , ('Group-Quality-Control') , ('Group-VendorDocs') , ('HRAdmin') , 
('HR-Category-Payroll') , ('HR-Category-Performance') , ('HR-Category-Personnel') , ('HR-Category-Upload') , ('HR-Document-Delete') , ('HR-Document-Index') , ('HR-Group-DocumentMaintenance') , 
('HR-IndexQueue-Email') , ('HRROLE01') , ('HRROLE02') , ('HRROLE03') , ('HRROLE04') , ('HRROLE05') , ('HRROLE06') , ('HRROLE09') , ('HRROLE10') , ('HRROLE11') , ('HRROLE12') , ('HRROLE13') , 
('HRROLE14') , ('HRROLE15') , ('HRROLE16') , ('HRROLE17') , ('HRROLE18') , ('HRROLE19') , ('HRROLE21') , ('HRROLE23') , ('HRROLE24') , ('HRROLE25') , ('HRROLE26') , ('HRROLE28') , ('HRROLE29') , 
('HRROLE30') , ('HRROLE31') , ('HRROLE34') , ('HRROLE35') , ('HRROLE36') , ('HRROLE37') , ('HRROLE39') , ('HRROLE41') , ('HRROLE42') , ('HRROLE43') , ('HRROLE44') , ('HRROLE45') , ('HRROLE46') , 
('HRROLE47') , ('HRROLE48') , ('HRROLE49') , ('HRROLE50') , ('HRROLE51') , ('HRROLE52') , ('HRROLE53') , ('HRROLE54') , ('HRROLE55') , ('HRROLE56') , ('HRROLE57') , ('HRROLE58') , ('HRROLE59') , 
('HRROLE60') , ('HRROLE61') , ('HRROLE62') , ('HRROLE63') , ('WFAdmin') , ('AccountsPayable');

SELECT  '1' AS vrsID
,       RI.lprKey
,       RI.lprRptName
,       RI.lprTitle
,       RI.lprDate
,       RI.lprOwner
,       RI.lprUserID
,       RI.lprArchiveDate
,       RI.lprTrackDate
,       RI.lprActionView
,       RI.lprActionEmail
,       RI.lprActionExcel
,       RI.lprActionForward
,       RI.lprActionReassign
,       RI.lprActionDownload
,       RI.lprActionLocalPrint
,       RI.lprActionServerPrint
,       RI.lprPageCount
,       RI.lprBytes
,       RI.lprDataType
,       RI.lprArchived
,       RI.lprJobName
,       RI.lprViewed
,       RI.lprRptID
  FROM  ReportIndex RI
  JOIN @tvp_lprOwner O ON RI.lprOwner = O.owner
 WHERE  (lprArchived = 0 AND lprPendingDelete = 0)
   AND  lprDone = 0
 ORDER BY lprDate DESC;

值得运行它,然后查看执行计划,如果需要,从那里索引。

【讨论】:

  • 是的,这是我在之前评论中的建议。我怀疑它不会有帮助(除了可读性)但绝对值得测试
  • 对不起汤姆。没有看到评论 - 它隐藏在“更多 cmets”位中。你是对的,尽管 TVP 是解决这个问题的方法。无需详细介绍索引 - 执行计划应根据实际的数据库统计信息就这方面的事情提出建议。如果明智的话,我会同意的。
  • 很高兴看到@mathew_baker。有时这些事情在#temp 表中的表现也非常不同。两者都值得测试。
  • 我最初只发布了查询的简化版本(没有IN 子句),因为它本身很慢。我包括了我的实际查询,因为我有几个 cmets 说我应该。因此,虽然我很欣赏这里的建议,但我不确定它是否解决了我的问题,因为当我完全删除 WHERE 子句时,我真的没有注意到任何性能改进。
【解决方案3】:

尝试创建一个新索引:

CREATE INDEX [IX_Composite] ON [ReportIndex] ([lprOwner], [lprArchived], [lprPendingDate], [lprDone], [lprDate]);

包含这 5 个字段是因为其中 4 个出现在您的 WHERE 子句中,最后一个用于您的 ORDER BY。

根据您的用例,可能值得查看分页结果。这可以帮助您解决问题的核心所在。

【讨论】:

  • 但它不对这些列进行排序。所以我不明白为什么他们的索引会有所作为。
  • 拥有覆盖 WHERE 子句中列的索引将使查询运行得更快。尝试创建索引,看看它是否有帮助。它可能会让你越界。
  • SELECT 列表中的列呢?你认为 SQL Server 会选择具有 600 万次查找的计划吗?
【解决方案4】:

根据您对 cme​​ts 部分的回复。

您提到表上没有索引,而且,这可能不是唯一的问题,硬件也将在这方面发挥重要作用。

因此,您需要做的是首先检查硬件规格,如果它已过时或无法处理那么多数据处理,那么就停下来,并建议他们使用合适的硬件进行升级。 如果他们有良好的硬件,那么您可以继续使用其他 SQL Server 建议。

首先,数据顺序: 我在您的查询中看到您正在按 lprDate 以降序对数据进行排序,这将是查询的昂贵部分,尤其是在大规模查询中。实际的例子是使用相同的查询没有where子句,只需使用ORDER BY lprDate DESC,然后将其与没有它的相同查询进行比较(使用TOP 10000进行测试)。您将看到两个查询之间的性能差异。如果您总是从表中选择最近的数据(大多数人都会这样做),那么您需要创建一个降序排列的聚集索引。这将使用表中的所有数据,并且新数据将始终位于首页。您可能遇到的唯一问题是使用 11M 行将花费太多时间来处理,并且有时该过程会失败。所以,我实际上做的(为了避免这种情况)是用不同的名字重新创建同一个表。然后创建一个具有正确排序的新聚集索引以及所需的正确索引。然后当我完成后,我只是分批重新插入记录。您可以执行相同的方法,您可以使用 lprDate 或创建一个 IDENTITY(或使用当前的,如果有的话)使它们聚集并将排序调整为 DESC,然后添加适当的索引。之后,将数据(批量)重新插入到新表中,并确保在批量中指定 order by。

您可以使用这种批量重新插入数据的方法:

DECLARE @Count  INT 
SET @Count = 1
WHILE @Count > 0
BEGIN
    INSERT INTO NewTable(Columns)
    SELECT 10000 Columns
    FROM 
        OriginalTable
    ORDER BY ID 

    SET @Count = @@ROWCOUNT
END

它将在每次运行中插入 10 千行。您可以根据需要增加或减少行数,只要最适合您。

现在您可以在新表中测试查询,看看它会如何执行。

您可能需要为实际查询(在帖子中)创建索引,并重新排列 WHERE 子句。

所以,你的 WHERE 子句将是这样的:

WHERE 
    lprArchived = 0 
AND     lprPendingDelete = 0
AND     lprDone=0
AND     lprOwner IN (.....)

你的索引将是:

CREATE INDEX [XReport] ON (lprArchived, lprPendingDelete, lprDone, lprOwner) INCLUDE (lprKey,  lprRptName,  lprTitle,  lprDate,  lprOwner,  lprUserID,  lprArchiveDate,  lprTrackDate,  lprActionView,  lprActionEmail,  lprActionExcel,  lprActionForward,  lprActionReassign,  lprActionDownload,  lprActionLocalPrint,  lprActionServerPrint,  lprPageCount,  lprBytes,  lprDataType,  lprArchived,  lprJobName,  lprViewed,  lprRptID)

对于lprOwner IN (.....),您可以采取另一种方法:

第一个(因为您有大量值),我会考虑比较表中的 lprOwner 值和当前使用的值。由于您有大约 94 个值,因此查看表中实际剩余的数量。

基本上:

SELECT DISTINCT lprOwner FROM ReportIndex WHERE lprOwner NOT IN(current used values)

如果返回的总值少于 94 行,那么使用 reverse 方法会好很多。哪个会使用这样的东西:

AND lprOwner NOT IN(the returned values)

您还可以使用 Temp 或 TVP 方法将它们与查询连接起来,这样就可以了

SELECT ....
FROM ReportIndex ri 
LEFT JOIN (The values table) t ON ri.lprOwner = t.lprOwner
WHERE 
    lprArchived = 0 
AND     lprPendingDelete = 0
AND     lprDone=0

另一种方法是使用子查询 像这样:

SELECT *
FROM (
SELECT .... , lprOwner 
FROM ReportIndex 
WHERE 
    lprArchived = 0 
AND     lprPendingDelete = 0
AND     lprDone=0
) D 
WHERE 
lprOwner ..... 

使用子查询的方式,会先将结果通过lprArchived、lprPendingDelete、lprDone进行过滤。然后,再通过IprOwner进行过滤。因此,您要过滤两组而不是一组。这有时在大表中很有用。

这就是我的想法,当然,不是每一种方法或建议都可以适用于所有情况,但至少这个想法本身可以引导你获得救赎

【讨论】:

    猜你喜欢
    • 2016-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    相关资源
    最近更新 更多