2019 Enterprise - 没有 NOLOCK 的 NOLOCK,tempdb 上的页面闩锁超时,最后是转储

SQL*_*pro 9 sql-server dump tempdb sql-server-2019

我们在 2 台新物理机上的新 SQL Server 2019 有一个非常奇怪的问题:

基础结构:在 2 台新物理机(用于 AlwaysOn 副本)上开始新安装 SQL Server 2019 Enterprise(Windows Server 2019 Standard 10.0 / Build 17763 上的 15.0.2000.5 / X64)。新机器是联想:

  • ThinkSystem SR630 – [7X02CTO1WW]
  • 1 个 CPU:1 个 Xeon Gold 6208U – 2.90 Ghz(16 核 x 2 – 超线程)
  • 256 Gb 内存 (32 Gb x 8)

该问题系统地出现在 2 台新机器上......

测试:产生故障的测试如下:

  1. 创建数据库
  2. 扩大数据库文件并切换到简单恢复模式
  3. 表的创建
  4. 用 100 万行填充表格
  5. 测试请求的执行(聚合时间间隔的计算 - 折叠)

正是最后一个查询(执行了近 10 次)经常导致出现以下消息的错误:

消息 601,级别 12,状态 1,行……由于数据移动,无法使用 NOLOCK 继续扫描。

当然,我们从未实现过NOLOCK提示或READ UNCOMMITTED隔离级别。但是……信息记录在 SQL Server 事件日志中:

  1. tempdb 页面中的缓冲区锁存器超时
  2. 有时会生成转储

“缓冲区锁存器”消息示例:

等待缓冲区锁存器时发生超时 — 类型 4,bp 00000292CE3D60C0,第 9 页:18634,统计信息 0x10b,数据库 ID:2,分配单元 ID:422212527063040/140737550352030400x0x00x700x00x0702000000000000000000000000000000000007拥有任务 0x00000292AB06B848。没有继续等待。等待缓冲区锁存器时发生超时 — 类型 4,bp 00000292CE398340,第 6 页:10372,统计信息 0x10b,数据库 ID:2,分配单元 ID:422212527063040/1407375503520308400x70x00x7020000000000x10000000000x1000拥有任务 0x00000292AB073468。没有继续等待。等待缓冲区锁存器时发生超时 — 类型 4,bp 00000292CE3DC480,第 9 页:18655,统计信息 0x10b,数据库 ID:2,分配单元 ID:422212527063040/140737550352000200x3082020 秒标志,30 秒 30 秒,任务 拥有任务 0x00000292AB07B468。没有继续等待。

我们的调查:

  • 安装CU6和CU7之后没有解决问题
  • 磁盘故障已解决,因为我们在 3 个不同的硬盘驱动器上创建了数据库。即使我们移动了 tempdb,问题仍然存在。
  • “缓冲区锁存器”信息可能表示内存故障。但是似乎没有关于服务器中 RAM 的硬件信息(我们执行了完整的内存测试)。
  • 虽然启用了软 NUMA,但我们通过禁用 SOFT NUMA 进行了补充测试,但这并没有解决问题。

一些似乎可以减少这种现象发生的元素:

  1. 禁用 SOFT NUMA(启用超线程):前 3 个测试成功,最后 7 个测试失败
  2. 禁用超线程(禁用 SOFT NUMA):在 10 次测试中只有 1 次失败
  3. 禁用超线程(启用 SOFT NUMA):在 90 次测试中只有 1 次失败

一些似乎可以解决问题的元素:

  1. 设置 MAXDOP 1
  2. 重新计算统计信息(UPDATE STATISTICS T_TIME_INTERVAL_TIV WITH FULLSCAN;)

我们移除 RAM 并将其替换为另一个,并使用不同的参考。问题依旧...

也许这与 SQL Server 2019 的新“并发 PFS 更新”有关……是否有任何跟踪标志禁用了这种新行为?

========== SCRIPT ==========
 
-- 1) creating database
CREATE DATABASE DB_BENCH
GO
 
DECLARE @SQL NVARCHAR(max) = N'';
SELECT @SQL = @SQL + N'ALTER DATABASE DB_BENCH MODIFY FILE (NAME = ''' + name + N''', SIZE = 10 GB, FILEGROWTH = 64 MB);'
FROM DB_BENCH.sys.database_files;
SET @SQL = @SQL + N'ALTER DATABASE DB_BENCH SET RECOVERY SIMPLE;'
EXEC (@SQL);
GO
 
USE DB_BENCH
GO
 
-- 2) creating table and view
CREATE TABLE T_TIME_INTERVAL_TIV
(TIV_ID       INT NOT NULL IDENTITY PRIMARY KEY,
 TIV_GROUP    INT,
 TIV_DEBUT    DATETIME2(0),
 TIV_FIN      DATETIME2(0))
GO 
 
CREATE VIEW V
AS
SELECT TIV_GROUP AS id, TIV_DEBUT AS intime, TIV_FIN AS outtime
FROM   T_TIME_INTERVAL_TIV
GO
 
-- 3) inserting datas
TRUNCATE TABLE T_TIME_INTERVAL_TIV;
GO
 
BULK INSERT T_TIME_INTERVAL_TIV
FROM "C:\DATA_SQL\intervals.txt"
WITH (KEEPIDENTITY , FIELDTERMINATOR = '\t',
      ROWTERMINATOR = '\n');
GO
 
-- 4) testing
SET NOCOUNT ON;
GO
 
SET STATISTICS TIME ON;
GO
 
WITH T1 As
(SELECT id, intime 
 FROM   V
 UNION  ALL
 SELECT id, outtime FROM V),
T2 As
(SELECT ROW_NUMBER() OVER(PARTITION BY id ORDER BY intime) NN, id, intime
 FROM   T1 T1_1),
T3 As
(SELECT T2_1.NN - ROW_NUMBER() OVER(PARTITION BY T2_1.id ORDER BY T2_1.intime,T2_2.intime) NN1,
        T2_1.id, T2_1.intime intime, T2_2.intime outtime
 FROM   T2 T2_1
        INNER JOIN T2 T2_2
              ON T2_1.id=T2_2.id
                 And T2_1.NN=T2_2.NN-1
 WHERE  EXISTS (SELECT *
                FROM   V S
                WHERE  S.id=T2_1.id
                  AND  (S.intime < T2_2.intime AND S.outtime>T2_1.intime))
    OR  T2_1.intime = T2_2.intime)
SELECT id, MIN(intime) intime, MAX(outtime) outtime
FROM   T3
GROUP  BY id, NN1
ORDER BY id, intime, outtime;
 
========== END OF SCRIPT ==========
Run Code Online (Sandbox Code Playgroud)

执行脚本的文件:

  1. 数据
  2. 转储 0005 SQL Server
    2.1日志
    2.2 mdmp
    2.3 txt
    2.4 SQLDUMPER_ERRORLOG.log
  3. 查询计划
    3.1原始数据
    3.2在 FULLSCAN 模式下 UPDATE STATS 后
    3.3两个计划的比较

2020-09-14UTC16:00,补充测试:

在 SQL Server 2019 上使用 2017 版本的数据库,如下所示:

USE master
GO
ALTER DATABASE DB_BENCH SET COMPATIBILITY_LEVEL = 140;
GO
Run Code Online (Sandbox Code Playgroud)

执行相同查询超过 180 次,结果没有任何错误

使用 2019 版本并启用 TF 3925 或 3972或 3925 和 3972 的组合,导致所有执行(使用 MAXDOP 0、超线程和 NUMA 软)时出现系统错误。

小智 0

请参阅最底部的示例报告

我在这里提供详细且完整的建议答案。发帖者需要按已接受的方式做出回应或提供反馈以改进解决方案。我的观点是,报告的问题不是 Microsoft SQL Server 2019 的故障,不是 TRACE FLAGS 的故障,也不是 TEMPDB 的故障。问题在于代码和需求的概念化。要求相当简单,如下所示。

这里的一般概念类似于 FILO 堆栈(先进后出)。它精确地模拟一千 (1000) 个数字分类的“演员”带着可写日记或记录卡走上拥挤的舞台,演员们在一系列百老汇演出中多次进入舞台并离开舞台,由早在新冠疫情爆发之前,丽莎·米内利 (Liza Minelli) 就在美国纽约。

1000 名演员中每一位的退出和进入循环可能会持续数天甚至数年,因为这是一场非常成功的演出,他们的退出和进入会被跟踪到当天的第二天。数据的粒度为每天 86400 秒,没有分数。我没有分析是否有多个Actor可以同时退出和进入,但数据中会有证据。

每个演员从舞台右侧进入,然后从舞台左侧退出。每个演员都用一张编号卡“标记”。Liza Minelli 的一些业务分析师将其称为“分类器”。分析师将这个徽章编号分配给每个演员,并将臭气熏天的徽章贴在他们的肩膀上。

演员的徽章编号并不唯一标识海报源数据 (SQL Pro) 中的每一行,因为具有徽章编号的演员多年来可能多次进入舞台并退出舞台,就像符号先知一样。

因此,它们的分类器在每一行上出现多次,具有不同的进入和退出日期时间。他们在舞台右侧输入的每个日期时间,演员都会在他们携带的“日期时间输入”栏中的记录卡上标记一个新的日期时间。接下来,当他们随后在舞台左侧退出时,演员会在名为“Datetime OUT”的另一列中获得另一个标记。

多年来,演员可以多次进出舞台,但 Liza Minelli 领导下的安全部门的年度报告只需要看到第一次“进入舞台右侧”,即“Datetime IN”和最后一次“退出舞台” Left”,称为“Datetime OUT”。他们需要为每一个肩上带有编号的臭徽章的演员提供这个。请注意,徽章确实很臭。事实上,即使演员们说他们不需要臭徽章,他们仍然很臭。现在咯咯笑以保持快乐。

而且,第一个将在最后,最后一个将在最前,因为丽莎·米内利有信心(见马太福音 19:30)。无论如何,以下是完成此要求/紧急情况的步骤:

这里列出了所有的代码,包括所有的DDL(数据定义)和DML(数据操作/T-SQL),甚至还有几十行结果。下面显示了方括号,而不是创建的对象名称中的字面意思。我只是将它们用作惯例。下面的代码块将由以下编号列表引用。

  • 1.0:首先创建一个名为【Quantum】的数据库;
  • 2.0:在其中创建一个名为[Meta]的新命名空间模式,以及一个名为[Hub]的命名空间模式;
  • 2.1:在【Hub】内新建一个表,命名为【Hub】.【Intervals】;
  • 2.2:在该表定义上包含覆盖聚集索引(参见代码)
  • 3.0:使用OLEDB BULK OPENROWSET提供程序将源数据加载到表中
  • 3.1:为提供程序使用 XML 格式文件(请参阅 GitHub),就像 Azure 一样
  • 4.0:创建参数化存储过程来加载数据
  • 5.0:实现代码页 1252 raw 的输入参数
  • 5.1:实现起始行和截断需求的输入参数
  • 6.0:在SPROC中实现错误处理
  • 7.0:为 FQFP 文本和格式文件 UNC 路径实现更多输入参数
  • 7.1:根据SQL 2019 Bulk OPENROWSET提供程序创建XML格式文件
  • 8.0:使用 UNC 路径创建 Windows 共享并根据需要分配权限
  • 9.0:从此 SQL Pro 帖子下载选项卡式文本文件
  • 9.1:从 8.0 将选项卡式文件放入 UNC 文件夹
  • 10.0:推断 SPROC 签名以进行测试,并包含参数
  • 10.1:使用引发的截断标志随意加载源数据
  • 10.1:可以针对多个测试场景调整参数
  • 11.0:创建将提取报告的代码(请参阅 Git)
  • 11.1:参见Itzik Ben-Gan 等人的T-SQL 查询(ISBN:978-0-7356-8504-8)
  • 11.2:学习有关窗口函数的章节
  • 特别参见 FIRST_VALUE() 和 LAST_VALUE()
  • 12.0 创建一组简单的公用表表达式
  • 13.0 使用CTE,查询源数据并输出报告

报告应具有以下特点:

  • A。每个分类参与者排一排(每人编号徽章)
  • b. 两列,每行都有 32 位日期时间列
  • C。每行每个分类操作的最长退出时间和最短进入时间

这实际上是一个经典的STACK解决方案。先进后出(推入/弹出)。海报似乎每个分类器需要 1 行,其中该行显示分类器编号(非键整数)及其入口和出口。有关演员进入和退出人生舞台的更多信息,请参见莎士比亚。

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Purpose: Creates the ActorStage Database for SQL Pro
--------------------------------------------------------
-- SQL Server 2019 Version
-- The folders must exist as shown 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
USE [master]
go
EXEC sp_configure 'show advanced options', 1;
GO
EXEC sp_configure'xp_cmdshell', 1;
GO
RECONFIGURE
-- Next, directories, will only work once. Look at your system.
GO
xp_cmdshell 'MKDIR C:\OLTP_DATA\ActorStage\System\';
GO
xp_cmdshell 'MKDIR C:\OLTP_DATA\ActorStage\Hub\';
GO
xp_cmdshell 'MKDIR C:\OLTP_DATA\ActorStage\Metadata\';
GO
xp_cmdshell 'MKDIR C:\OLTP_DATA\ActorStage\Indices\';
GO
xp_cmdshell 'MKDIR D:\OLTP_TLOG\ActorStage\';
GO

USE [master]
GO

CREATE DATABASE [ActorStage]
CONTAINMENT = NONE
ON PRIMARY -- The Primary Filegroup should never have user data on it.
( NAME = N'ActorStage_SYSDATA_0', FILENAME = N'C:\OLTP_DATA\ActorStage\System\ActorStage_SYSDATA_0.MDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB),
( NAME = N'ActorStage_SYSDATA_1', FILENAME = N'C:\OLTP_DATA\ActorStage\System\ActorStage_SYSDATA_1.MDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB),

FILEGROUP [HUB] 
( NAME = N'ActorStage_HUB_0', FILENAME = N'C:\OLTP_DATA\ActorStage\Hub\ActorStage_HUB_0.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 12MB),
( NAME = N'ActorStage_HUB_1', FILENAME = N'C:\OLTP_DATA\ActorStage\Hub\ActorStage_HUB_1.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 12MB),
( NAME = N'ActorStage_HUB_2', FILENAME = N'C:\OLTP_DATA\ActorStage\Hub\ActorStage_HUB_2.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 12MB),
( NAME = N'ActorStage_HUB_3', FILENAME = N'C:\OLTP_DATA\ActorStage\Hub\ActorStage_HUB_3.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 12MB),

FILEGROUP [METADATA]
( NAME = N'ActorStage_METADATA_0', FILENAME = N'C:\OLTP_DATA\ActorStage\Metadata\ActorStage_METADATA_0.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB),
( NAME = N'ActorStage_METADATA_1', FILENAME = N'C:\OLTP_DATA\ActorStage\Metadata\ActorStage_METADATA_1.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB), 

FILEGROUP [INDICES] 
( NAME = N'ActorStage_INDICES_0', FILENAME = N'C:\OLTP_DATA\ActorStage\Indices\ActorStage_INDICES_0.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB),
( NAME = N'ActorStage_INDICES_1', FILENAME = N'C:\OLTP_DATA\ActorStage\Indices\ActorStage_INDICES_1.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB),
( NAME = N'ActorStage_INDICES_2', FILENAME = N'C:\OLTP_DATA\ActorStage\Indices\ActorStage_INDICES_2.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB),
( NAME = N'ActorStage_INDICES_3', FILENAME = N'C:\OLTP_DATA\ActorStage\Indices\ActorStage_INDICES_3.NDF' , SIZE = 25MB , MAXSIZE = UNLIMITED, FILEGROWTH = 10MB)

LOG ON -- Log files only fill 1 at a time. The second log file is just exposed here as a precautionary measure.
( NAME = N'ActorStage_TLOG_0', FILENAME = N'D:\OLTP_TLOG\ActorStage\ActorStage_TLOG_0.LDF' , SIZE = 102400KB , MAXSIZE = 204800KB , FILEGROWTH = 20480KB ),
( NAME = N'ActorStage_TLOG_1', FILENAME = N'D:\OLTP_TLOG\ActorStage\ActorStage_TLOG_1.LDF' , SIZE = 102400KB , MAXSIZE = 204800KB , FILEGROWTH = 20480KB )
GO

USE [ActorStage]
GO
CREATE SCHEMA [Metadata] AUTHORIZATION [dbo];
GO
CREATE SCHEMA [Hub] AUTHORIZATION [dbo];
GO
CREATE SCHEMA [Spoke] AUTHORIZATION [dbo];
GO

-- Force CS / AS because Actors are Sensitive about their Accents in each Case
ALTER DATABASE [ActorStage] COLLATE SQL_Latin1_General_CP1_CS_AS;
ALTER DATABASE [ActorStage] SET RECOVERY SIMPLE
--ALTER DATABASE [ActorStage] SET COMPATIBILITY_LEVEL = x
GO

ALTER DATABASE [ActorStage] SET ANSI_NULL_DEFAULT ON
ALTER DATABASE [ActorStage] SET ANSI_NULLS ON 
ALTER DATABASE [ActorStage] SET ANSI_PADDING ON
ALTER DATABASE [ActorStage] SET ANSI_WARNINGS ON
ALTER DATABASE [ActorStage] SET QUOTED_IDENTIFIER OFF
GO

ALTER DATABASE [ActorStage] SET ARITHABORT ON
ALTER DATABASE [ActorStage] SET NUMERIC_ROUNDABORT ON 
GO

ALTER DATABASE [ActorStage] SET AUTO_CLOSE OFF 
ALTER DATABASE [ActorStage] SET AUTO_SHRINK OFF 
ALTER DATABASE [ActorStage] SET AUTO_UPDATE_STATISTICS ON 

GO
ALTER DATABASE [ActorStage] SET CURSOR_CLOSE_ON_COMMIT OFF 
ALTER DATABASE [ActorStage] SET CURSOR_DEFAULT GLOBAL 
ALTER DATABASE [ActorStage] SET CONCAT_NULL_YIELDS_NULL ON 
GO

ALTER DATABASE [ActorStage] SET RECURSIVE_TRIGGERS OFF 
ALTER DATABASE [ActorStage] SET DISABLE_BROKER 
ALTER DATABASE [ActorStage] SET AUTO_UPDATE_STATISTICS_ASYNC OFF 
ALTER DATABASE [ActorStage] SET DATE_CORRELATION_OPTIMIZATION OFF 
ALTER DATABASE [ActorStage] SET TRUSTWORTHY OFF 
ALTER DATABASE [ActorStage] SET ALLOW_SNAPSHOT_ISOLATION OFF 
ALTER DATABASE [ActorStage] SET PARAMETERIZATION SIMPLE 
ALTER DATABASE [ActorStage] SET READ_COMMITTED_SNAPSHOT OFF 
ALTER DATABASE [ActorStage] SET HONOR_BROKER_PRIORITY OFF 
GO

ALTER DATABASE [ActorStage] SET PAGE_VERIFY CHECKSUM  
ALTER DATABASE [ActorStage] SET DB_CHAINING OFF 
ALTER DATABASE [ActorStage] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF ) 
ALTER DATABASE [ActorStage] SET TARGET_RECOVERY_TIME = 0 SECONDS 
GO

ALTER DATABASE [ActorStage] SET READ_WRITE 
ALTER DATABASE [ActorStage] SET MULTI_USER 
GO

IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
BEGIN
    EXECUTE [ActorStage].[dbo].[sp_fulltext_database] @action = 'enable'
END
GO
Run Code Online (Sandbox Code Playgroud)

下一个:

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Actors Stage
-------------------------------------------------
-- This Script Name: CreateTableIntervals.sql
-- Creator: @CubeSpark
-- Created: 2020 COVID Days
-- Purpose: Creates the Intervals table in the Spoke schema for SQL Pro
-- Compats: Tested on SQL Server 2019
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

DROP TABLE IF EXISTS Hub.Intervals;
PRINT 'Table Hub.Intervals will be created.';
GO

CREATE TABLE [Hub].[Intervals](
    [Interval_PSK] INTEGER NOT NULL,
    [Classifier] INTEGER NOT NULL,
    [DatetimeIN] DATETIME NOT NULL,
    [DatetimeOUT] DATETIME NOT NULL
 CONSTRAINT [Intervals_PKC] PRIMARY KEY CLUSTERED  -- index
(
    [Interval_PSK], [Classifier], [DatetimeIN], [DatetimeOUT] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [HUB]
) ON [HUB];
GO

IF OBJECT_ID (N'Hub.Intervals', N'U') IS NOT NULL 
PRINT 'Table Hub.Intervals was successfully Created.';
GO
Run Code Online (Sandbox Code Playgroud)

接下来,OLEDB OPENROWSET BULK PROVIDER 格式文件

<?xml version="1.0"?>
<BCPFORMAT xmlns="http://schemas.microsoft.com/sqlserver/2004/bulkload/format" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <RECORD>
  <FIELD ID="1" xsi:type="CharTerm" TERMINATOR="\t"/>
  <FIELD ID="2" xsi:type="CharTerm" TERMINATOR="\t"/>
  <FIELD ID="3" xsi:type="CharTerm" TERMINATOR="\t"/>
  <FIELD ID="4" xsi:type="CharTerm" TERMINATOR="\t"/>
  <FIELD ID="5" xsi:type="CharTerm" TERMINATOR="\r\n"/>
 </RECORD>
 <ROW>
  <COLUMN SOURCE="1" NAME="Interval_PSK" xsi:type="SQLINT"/>
  <COLUMN SOURCE="2" NAME="Classifier" xsi:type="SQLINT"/>
  <COLUMN SOURCE="3" NAME="DateTimeIN" xsi:type="SQLDATETIME"/>
  <COLUMN SOURCE="4" NAME="DateTimeOUT" xsi:type="SQLDATETIME"/>
 </ROW>
</BCPFORMAT>
Run Code Online (Sandbox Code Playgroud)

接下来,播种 SPROC 和错误 SPROC

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    -- Systems Deployment Management.
    -------------------------------------------------
    -- This Script Name: CreateGetErrorData.sql
    -- Creator: @CubeSpark
    -- Created: 2020 COVID Days
    This procedure will obtain error data from Catch blocks.

    -- Compats: Tested on SQL Server 2019      
    --ERROR_NUMBER() returns the number of the error.
    --ERROR_SEVERITY() returns the severity.
    --ERROR_STATE() returns the error state number.
    --ERROR_PROCEDURE() returns the name of the stored procedure or trigger 
    --ERROR_LINE() returns the line number inside the routine that caused 
    --ERROR_MESSAGE() returns the complete text of the error message. 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    
    DROP PROCEDURE IF EXISTS Meta.GetErrorData;  
    GO 
    
    IF OBJECT_ID(N'Meta.GetErrorData') IS NULL
    BEGIN
      PRINT 'Stored Procedure Meta.GetErrorData will be created.'
    END;
    
    GO
    -- Create procedure to retrieve error information.  
    CREATE PROCEDURE Meta.GetErrorData
    @InsertErrorRowOnly BIT = 0,
    @SetMessageText NVARCHAR(200) = 'Calling SQL Script is not known'
    AS
    SET NOCOUNT ON;
    IF @InsertErrorRowOnly = 0  -- use this if you want to just print the statements in the results window
    BEGIN
        SELECT
        'UserName' = 'CubeSpark',
        'ErrorNumber' = ERROR_NUMBER(),
        'ErrorSeverity' = ERROR_SEVERITY(),
        'ErrorState' = ERROR_STATE(),
        'ErrorProcedure' = ISNULL(ERROR_PROCEDURE(), @SetMessageText),
        'ErrorLine' = ERROR_LINE(),
        'ErrorMessage' = ERROR_MESSAGE();
    END;
    
    IF @InsertErrorRowOnly = 1 -- use this if you want to register the error in the meta data table.
    BEGIN
        INSERT INTO [Meta].[ErrorData] (
        [UserName], 
        [ErrorNumber], 
        [ErrorSeverity], 
        [ErrorState],
        [ErrorProcedure], 
        [ErrorLine], 
        [ErrorMessage], 
        [ErrorDateTime])
        SELECT 
        'UserName' = 'CubeSpark',
        'ErrorNumber' = ERROR_NUMBER(),
        'ErrorSeverity' = ERROR_SEVERITY(),
        'ErrorState' = ERROR_STATE(),
        'ErrorProcedure' = ISNULL(ERROR_PROCEDURE(), @SetMessageText),
        'ErrorLine' = ERROR_LINE(),
        'ErrorMessage' = ERROR_MESSAGE(),
        'ErrorDateTime' = GETDATE();
    END;
    GO  
    
    IF OBJECT_ID(N'Meta.GetErrorData') IS NOT NULL
    BEGIN
      PRINT 'Stored Procedure Meta.GetErrorData has been created.'
    END
Run Code Online (Sandbox Code Playgroud)

接下来是播种和装载机。您需要 SQL Pro 帖子中的数据

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    -- Data Seed Spreading and Loading Script
    -------------------------------------------------
    -- SQL Server 2019 Version
    -- This Script Name: CreateProcedureSeeding.sql
    -- Creator: Mr. Grant Peace
    -- Alterer: Mr. Getauf Thescaffolt 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    -- Use to make it USEFUL is your job

    DROP PROCEDURE IF EXISTS [Meta].[Seeding];
    PRINT 'Stored Procedure Meta.Seeding will be recreated'
    GO
    
    CREATE PROCEDURE [Meta].[Seeding]
    @SchemaName NVARCHAR(20) = 'Hub',
    @TableName NVARCHAR(100) = 'Intervals',
    @StartingRow INTEGER = 1,
    @CodePage INTEGER = 1252,
    @UNC_QualifiedErrorFilePath NVARCHAR(200) = '\\YOURTHING\OutputFiles\Errors.txt',
    @UNC_QualifiedSeedFilePath NVARCHAR(200) = '\\YOURTHING\InputFiles\Intervals.txt',
    @UNC_QualifiedFormatFilePath NVARCHAR(200) = '\\YOURTHING\OutputFiles\Intervals.xml',
    @PrintOnly BIT = 1,
    @WithTruncate BIT = 0,
    @InsertErrorRowOnly BIT = 0,
    @Message_OUT NVARCHAR(MAX) = 'Test' OUTPUT,
    @ErrorMessage_OUT NVARCHAR(MAX) = 'Test'  OUTPUT,
    @RowCount_OUT INTEGER = 0 OUTPUT
    AS
    SET NOCOUNT ON
    DECLARE 
    @Executable NVARCHAR(MAX),
    @RowCount INTEGER, 
    @crlf NCHAR(2) = CHAR(13) + CHAR(10),
    @Parameters NVARCHAR(100),
    @SetMessageText NVARCHAR(1024) = 'No Errors Handled';
    
    SET @Parameters = N'@RowCount_EX INTEGER OUTPUT'
    SET @RowCount_OUT = 0;
    
    SET @Message_OUT = 'Success.';
    SET @ErrorMessage_OUT = 'No errors.';
    
        -- No table exists, so then you can do a SELECT INTO.
        BEGIN TRY
            IF @WithTruncate = 1 
            BEGIN
                SET @Executable = 
                'IF OBJECT_ID (N' 
                + '''' + '[' + @SchemaName + '].[' 
                + @TableName + ']' + '''' + ', N' + '''' + 'U' + '''' + ') IS NOT NULL ' 
                + @crlf
                + 'BEGIN '
                + @crlf
                + '  TRUNCATE TABLE [' + @SchemaName + '].[' + @TableName + '];'
                + @crlf
                + '  INSERT INTO [' + @SchemaName + '].[' + @TableName + '] WITH (KEEPDEFAULTS)'
                + @crlf
                + '  SELECT SourceLoad.* FROM OPENROWSET(BULK ' + '''' + @UNC_QualifiedSeedFilePath + '''' + ', '
                + @crlf
                + '  FORMATFILE = ' + '''' + @UNC_QualifiedFormatFilePath + '''' + ', '
                + @crlf
                + '  ERRORFILE = ' + '''' + @UNC_QualifiedErrorFilePath + '''' + ', '
                + @crlf
                + '  FIRSTROW = ' + CAST(@StartingRow AS NVARCHAR(10)) + ', ' --2,'
                + @crlf
                + '  CODEPAGE = ' + '''' + CAST(@CodePage AS NVARCHAR(6)) + '''' + ') AS SourceLoad'
                + @crlf
                + 'END;' 
                + @crlf
                + 'SET @RowCount_EX = @@ROWCOUNT;'
            END
            ELSE
            BEGIN
                SET @Executable = 
                'IF OBJECT_ID (N' 
                + '''' + '[' + @SchemaName + '].[' 
                + @TableName + ']' + '''' + ', N' + '''' + 'U' + '''' + ') IS NOT NULL ' 
                + @crlf
                + 'BEGIN '
                + @crlf
                + '  INSERT INTO [' + @SchemaName + '].[' + @TableName + '] WITH (KEEPDEFAULTS)'
                + @crlf
                + '  SELECT SourceLoad.* FROM OPENROWSET(BULK ' + '''' + @UNC_QualifiedSeedFilePath + '''' + ', '
                + @crlf
                + '  FORMATFILE = ' + '''' + @UNC_QualifiedFormatFilePath + '''' + ', '
                + @crlf
                + '  ERRORFILE = ' + '''' + @UNC_QualifiedErrorFilePath + '''' + ', '
                + @crlf
                + '  FIRSTROW = ' + CAST(@StartingRow AS NVARCHAR(10)) + ', ' --2,'
                + @crlf
                + '  CODEPAGE = ' + '''' + CAST(@CodePage AS NVARCHAR(6)) + '''' + ') AS SourceLoad'
                + @crlf
                --+ @crlf
                --+ '  FIRSTROW = 2,'
                --+ '  CODEPAGE = ' + '''' + 'RAW' + ''''   + ') AS SourceLoad'
                + @crlf
                + 'END;' 
                + @crlf
                + 'SET @RowCount_EX = @@ROWCOUNT;'
            END;
    
            IF @PrintOnly = 1 PRINT @Executable  -- SSIS Suppresses PRINT because its not STDIO.
            IF @PrintOnly = 0 EXECUTE sp_executesql @Executable, @Parameters, @RowCount_EX = @RowCount_OUT OUTPUT;
            
            IF @PrintOnly = 0 AND @RowCount_OUT > 0
            BEGIN
                PRINT 'Seeding of [' + @SchemaName + '].[' + @TableName + '] is complete, with an affected row count of: ' + CAST(@RowCount_OUT AS VARCHAR(10));
            END
            
            IF @PrintOnly = 0 AND @RowCount_OUT = 0 
            BEGIN
                PRINT 'Table [' + @SchemaName + '].[' + @TableName + '] does not exist. Create the table first before loading.'
            END
        
        END TRY
        
        BEGIN CATCH
        -- This will only register faults in the dynamic SQL.
        PRINT 'Inside Catch'
            SET @SetMessageText =   N'Caller: ' + OBJECT_SCHEMA_NAME(@@PROCID) + '.' + OBJECT_NAME(@@PROCID);
            
            EXECUTE Meta.GetErrorData 
            @SetMessageText = @SetMessageText, 
            @InsertErrorRowOnly = @InsertErrorRowOnly;
            
            SET @Message_OUT = 'Failure';
    
            SET @ErrorMessage_OUT = 'Error Number #: '
            +   CONVERT(NVARCHAR(15), ERROR_NUMBER())
            + '.' + @crlf + 'Error Message: '
            +   ERROR_MESSAGE()
            + @crlf +   'Module line #: '
            +   CONVERT(NVARCHAR(15), ERROR_LINE())
            + @crlf +   'Error Severity: '
            +   CONVERT(NVARCHAR(10), ERROR_SEVERITY())
            + @crlf +   'Error State: '
            +   CONVERT(NVARCHAR(10), ERROR_STATE())
    
            PRINT @ErrorMessage_OUT;
            
        END CATCH;
    
    GO
    IF OBJECT_ID(N'Meta.Seeding') IS NOT NULL
    BEGIN
         PRINT 'Stored Procedure Meta.Seeding was recreated.'
    END
    GO

Next: Data Loader. Pay careful attention to the folder and the parameters.


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Stack Exchange
-- Data Loading SPROC
-------------------------------------------------
https://dba.stackexchange.com/questions/275487/2019-enterprise-nolock-without-nolock-page-latch-time-out-on-tempdb-and-final/280977#280977

-- SQL Server 2019 Version
-- This Script Name: LoadIntervals.sql
-- Creator: Mr. Getauf Thescaffolt
-- Created: 2018-03-31 09:00:00.000
-- Altered: 2019-07-28 09:00:00.000
-- Alterer: Mr. Getauf Thescaffolt 
-- Outputs: Data to table
-- Purpose: 
-- Package: 
-- Folders: 

Requires Format File, UNC Paths, and TAB Text as specified by SQL Pro
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

USE [Quantum]
GO

DECLARE @RC INTEGER,
@SchemaName NVARCHAR(20), 
@TableName NVARCHAR(100),
@UNC_QualifiedErrorFilePath NVARCHAR(200),
@UNC_QualifiedSeedFilePath NVARCHAR(200),
@UNC_QualifiedFormatFilePath NVARCHAR(200),
@PrintOnly BIT,
@WithTruncate BIT,
@InsertErrorRowOnly BIT,
@Message_OUT NVARCHAR(MAX),
@ErrorMessage_OUT NVARCHAR(MAX),
@RowCount_OUT INTEGER;

EXECUTE [Meta].[Seeding] 
@SchemaName = [Hub],
@TableName = [Intervals],
@CodePage = 1252,
@StartingRow = 1,
@UNC_QualifiedErrorFilePath = '\\YOURTHING\OutputFiles\Errors.txt',
@UNC_QualifiedSeedFilePath = '\\YOURTHING\InputFiles\Intervals.tx