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 / 问题 / 331081
Accepted
Geezer
Geezer
Asked: 2023-09-11 16:26:59 +0800 CST2023-09-11 16:26:59 +0800 CST 2023-09-11 16:26:59 +0800 CST

当我有索引时获取 SORT 运算符

  • 772

在 Azure SQL 数据库(SQL2019 兼容)上,我有一个 ETL 进程,它以 DeltaTrack 模式填充 HISTORY 表。

在 Proc 中,有一个对 HISTORY 表的更新,查询引擎正在使用 SORT,但我有一个应该覆盖它的索引。

此 UPDATE 的用例是针对现有行,自从该行首次添加到 HISTORY 表以来,我们已向摄取添加了额外的列。

这种排序会导致我们更大/更宽的表上的更新速度极其缓慢。

如何调整索引或查询以删除查询 3中的排序?

这是根据京东要求更新的 执行计划

这是 DDL。

DROP TABLE IF EXISTS dbo.STAGE;
GO
CREATE TABLE dbo.STAGE
(
    Id varchar(18) NULL,
    CreatedDate varchar(4000) NULL,
    LastModifiedDate varchar(4000) NULL,
    LastReferencedDate varchar(4000) NULL,
    [Name] varchar(4000) NULL,
    OwnerId varchar(4000) NULL,
    SystemTimestamp datetime2(7) NULL
)
GO

DROP TABLE IF EXISTS dbo.HISTORY;
GO
CREATE TABLE dbo.HISTORY
(
    HistoryRecordId int IDENTITY(1,1) NOT NULL,
    [Hash] binary(64) NOT NULL,
    [IsActive]  BIT NOT NULL ,
    ActiveFromDateTime datetime2(7) NOT NULL,
    ActiveToDateTime datetime2(7) NOT NULL,
    Id varchar(18) NOT NULL,
    CreatedDate datetime2(7) NULL,
    LastModifiedDate datetime2(7) NULL,
    LastReferencedDate datetime2(7) NULL,
    [Name] varchar(80) NULL,
    OwnerId varchar(18) NULL,
    SystemTimestamp datetime2(7) NULL
) 
GO
CREATE UNIQUE CLUSTERED INDEX [CL__HISTORY] ON dbo.HISTORY
(
    Id , 
    [ActiveToDateTime] ASC,
    [IsActive] ASC
)
GO
CREATE NONCLUSTERED INDEX [IX__HISTORY_IsActive] ON dbo.HISTORY
(
    [Id] ASC
)
INCLUDE([IsActive],[ActiveToDateTime]) 
GO

DROP TABLE IF EXISTS #updates;
GO


WITH src AS (
  SELECT 
    CONVERT(VARCHAR(18), t.[Id]) AS [Id]
  , CONVERT(DATETIME2, t.[CreatedDate]) AS [CreatedDate]
  , CONVERT(DATETIME2, t.[LastModifiedDate]) AS [LastModifiedDate]
  , CONVERT(DATETIME2, t.[LastReferencedDate]) AS [LastReferencedDate]
  , CONVERT(VARCHAR(80), t.[Name]) AS [Name]
  , CONVERT(VARCHAR(18), t.[OwnerId]) AS [OwnerId]
  , CONVERT(DATETIME2, t.SystemTimestamp) AS SystemTimestamp
  , dgst.[Hash]
  , CONVERT(DATETIME2, SystemTimestamp) AS [ActiveFromDateTime]
  , RN = ROW_NUMBER() OVER ( 
            PARTITION BY 
                t.[Id] 
                ORDER BY CONVERT(DATETIME2, SystemTimestamp) DESC
        ) 
  FROM dbo.STAGE t
    OUTER APPLY (
      SELECT 
        CAST(HASHBYTES('SHA2_256',
          COALESCE(CAST([CreatedDate] AS NVARCHAR(4000)), N'')
            + N'||' + COALESCE(CAST([LastModifiedDate] AS NVARCHAR(4000)), N'')
            + N'||' + COALESCE(CAST([LastReferencedDate] AS NVARCHAR(4000)), N'')
            + N'||' + COALESCE(CAST([Name] AS NVARCHAR(4000)), N'')
            + N'||' + COALESCE(CAST([OwnerId] AS NVARCHAR(4000)), N'')
            + N'||' + COALESCE(CAST(SystemTimestamp AS NVARCHAR(4000)), N'')
        ) AS BINARY(64)) AS [Hash]
      ) dgst
), tgt AS (
  SELECT *
  FROM dbo.HISTORY t
  WHERE t.[ActiveToDateTime] > GETUTCDATE()
  AND 1 = 1  
)
SELECT 
  tgt.HistoryRecordId
, src.*
INTO #updates
FROM src
  LEFT JOIN tgt 
    ON tgt.[Id] = src.[Id] WHERE src.RN = 1;  
GO

--Create index on temp table (#updates) 
CREATE NONCLUSTERED INDEX NCCI_#updates__Kimble_HISTORY_ForecastStatus 
    ON #updates ( [Id] , ActiveFromDateTime, [Hash] );
GO  


    UPDATE  tgt 
    SET
      tgt.[Hash]        = src.[Hash] 
    , tgt.IsActive      = 1
    , tgt.[CreatedDate] = src.[CreatedDate]
    , tgt.[LastModifiedDate]    = src.[LastModifiedDate]
    , tgt.[LastReferencedDate]  = src.[LastReferencedDate]
    , tgt.[Name]            = src.[Name]
    , tgt.[OwnerId]         = src.[OwnerId]
    , tgt.SystemTimestamp   = src.SystemTimestamp
    FROM dbo.HISTORY tgt
      INNER JOIN #updates src   
            ON tgt.[Id] = src.[Id]
            AND src.[ActiveFromDateTime] = tgt.[ActiveFromDateTime] 
            AND tgt.[Hash]  <> src.[Hash] ; 
GO
sql-server
  • 3 3 个回答
  • 318 Views

3 个回答

  • Voted
  1. Best Answer
    Paul White
    2023-09-12T17:16:05+08:002023-09-12T17:16:05+08:00

    临时表中的列Id是唯一的,但您没有告诉优化器这一点。

    将临时表上现有的非聚集索引替换为:

    CREATE UNIQUE CLUSTERED INDEX CCI_#updates__Id
    ON #updates ([Id]);
    

    注意索引是UNIQUE和CLUSTERED。

    这将从计划中删除哈希匹配聚合(为每个未声明的键选择任意行值)。

    现在为最终更新添加提示:FORCESEEK

    UPDATE tgt 
    SET
        tgt.[Hash] = src.[Hash], 
        tgt.IsActive = 1, 
        tgt.[CreatedDate] = src.[CreatedDate], 
        tgt.[LastModifiedDate] = src.[LastModifiedDate],
        tgt.[LastReferencedDate] = src.[LastReferencedDate],
        tgt.[Name] = src.[Name],
        tgt.[OwnerId] = src.[OwnerId],
        tgt.SystemTimestamp = src.SystemTimestamp
    FROM dbo.HISTORY tgt
    INNER JOIN #updates src   
        WITH (FORCESEEK) -- NEW!
        ON tgt.[Id] = src.[Id]
        AND src.[ActiveFromDateTime] = tgt.[ActiveFromDateTime] 
        AND tgt.[Hash]  <> src.[Hash]; 
    

    你应该得到一个没有排序或散列的计划,如下所示:

    预期计划

    万圣节保护需要Eager Table Spool,因为您要更新集群键 ( IsActive )。

    您可能会发现这种计划形状效果最好。您没有更新大量行。

    引入原始排序是为了按键顺序将行呈现给聚集索引更新运算符。这有助于产生顺序访问模式,而不是为每次更新寻找聚集索引。上面的计划依赖于保留该键顺序,因此不需要排序。


    我知道您说过您正在遵循某种模式,但脚本的许多方面似乎都是多余的、低效的或不安全的。

    1. 历史表对标识列没有唯一约束。
    2. 哈希计算可以使用CONCAT_WS.
    3. 哈希计算不使用日期转换的样式格式。
    4. 保存到临时表的HistoryRecordId列永远不会被使用。
    5. 目前尚不清楚您是否使用散列来保存任何内容而不是直接比较列。
    6. 您的最终更新无条件更改集群键列IsActive,需要万圣节保护。您可以考虑不这样做,或者仅在绝对必要时才这样做,也许在单独的更新中。这完全取决于该列的含义以及您的流程所保证的内容。
    • 7
  2. Erik Darling
    2023-09-12T01:53:07+08:002023-09-12T01:53:07+08:00

    将其聚类

    查询计划的主要问题是使用批处理模式排序。这让您如此沮丧的原因是因为除非它们是 Window Aggregate 的子运算符,否则所有行最终都会在单个线程上:

    坚果

    表上的索引无效的原因#updates是它没有被使用。SQL Server 不想执行 200 万次查找来获取所有不属于非聚集索引的请求列。

    坚果

    您可能会更好地在#updates表上创建聚集索引,这将按键列对数据进行排序,并包含表中的所有其他列。

    CREATE CLUSTERED INDEX 
        NCCI_#updates__Kimble_HISTORY_ForecastStatus 
    ON 
        #updates 
        ([Id], ActiveFromDateTime, [Hash])
    WITH
        (SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
    

    然而!您可能仍然会获得批处理模式计划,并且它可能会使用哈希连接,因为这是批处理模式可以使用的唯一连接类型。由于散列连接不保留顺序(合并和某些类型的嵌套循环会保留顺序),因此您可能仍然会得到排序运算符。

    您的选择是使用 OPTION(MERGE JOIN) 强制使用该连接类型,或使用 OPTION(USE HINT('DISALLOW_BATCH_MODE')) 禁用查询的批处理模式。

    • 5
  3. rois
    2023-09-12T01:56:02+08:002023-09-12T01:56:02+08:00

    最后一个查询似乎主要是在等待BSORT(在 XML 视图中最容易看到): 在此输入图像描述

    等了将近4分钟。这指向批处理模式排序。事实上,这个查询中的排序是批处理模式的:

    在此输入图像描述

    另外值得注意的是,CPU 时间仅为 10 秒,而实际(挂钟)时间为 46 秒。

    所有这些都表明批处理模式存在问题。本文建议使用跟踪标志 9358 和其他解决方法。但它适用于 SQL Server 2016,因此不确定它在 2019 年如何工作。我个人会尝试强制查询以较低的兼容性级别运行。意思是OPTION(USE HINT('QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_140'))在查询末尾添加。行存储上的批处理模式(我们在此处看到)从兼容性级别 150 开始可用,因此使用较低的级别应禁用它。

    作为旁注,索引 [IX__HISTORY_IsActive] ON dbo.HISTORY 似乎与聚集索引是多余的。它具有相同的第一个键列,并且包含其他键列。我会考虑放弃它。这将加快表上的任何更新并节省空间。

    • 2

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

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

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

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