SQL Server中的巨大数据和性能


20

我已经编写了一个带有SQL Server后端的应用程序,该应用程序可以收集和存储大量记录。我已经计算出,在高峰时,平均记录量约为每天3亿4千万(运行20小时)。

我的原始解决方案(在完成数据的实际计算之前)是让我的应用程序将记录插入到客户查询的同一张表中。显然,该崩溃和烧毁很快,因为不可能查询插入了这么多记录的表。

我的第二个解决方案是使用2个数据库,一个用于应用程序接收的数据,另一个用于客户端就绪的数据。

我的应用程序将接收数据,将其分块成约10万条记录,然后批量插入到临时表中。在记录约100k之后,应用程序将即时创建另一个具有与以前相同的架构的登台表,然后开始插入该表中。它将在作业表中创建一条记录,该表的名称具有100k条记录,并且SQL Server端的存储过程会将数据从登台表移至可用于客户端的生产表,然后删除表由我的应用程序创建的临时表。

除了具有Jobs表的登台数据库之外,两个数据库都具有相同模式的5个表的相同集合。暂存数据库在将要存储大量记录的表上没有完整性约束,键,索引等。如下所示,表名称为SignalValues_staging。目的是让我的应用程序尽快将数据装入SQL Server。动态创建表以便轻松迁移表的工作流程效果很好。

以下是我的登台数据库中的5个相关表以及我的作业表:

登台表 我编写的存储过程负责处理所有登台表中的数据并将其插入生产环境。以下是我的存储过程的一部分,该过程从登台表插入生产环境:

-- Signalvalues jobs table.
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM 
(
    SELECT JobId 
           ,ProcessingComplete  
           ,SignalValueStagingTableName AS 'TableName'
           ,(DATEDIFF(SECOND, (SELECT last_user_update
                              FROM sys.dm_db_index_usage_stats
                              WHERE database_id = DB_ID(DB_NAME())
                                AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
                     ,GETUTCDATE())) SecondsSinceLastUpdate
    FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
   OR cte.SecondsSinceLastUpdate >= 120

DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)

DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128) 
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)

WHILE @i > 0
BEGIN

    SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
    FROM #JobsToProcess 
    WHERE RowIndex = @i 

    SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'

    SET @sqlStatement = N'

        --Signal values staging table.
        SELECT svs.* INTO #sValues
        FROM '+ @qualifiedTableName +' svs
        INNER JOIN SignalMetaData smd
            ON smd.SignalId = svs.SignalId  


        INSERT INTO SignalValues SELECT * FROM #sValues

        SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues

        DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

        DROP TABLE #sValues
        DROP TABLE #uniqueIdentifiers

        IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
        BEGIN
            -- processing is completed so drop the table and remvoe the entry
            IF @processingComplete = 1 
            BEGIN 
                DELETE FROM SignalValueJobs WHERE JobId = @currentJob

                IF '''+@currentTable+''' <> ''SignalValues_staging''
                BEGIN
                    DROP TABLE '+ @qualifiedTableName +'
                END
            END
        END 
    '

    EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;

    SET @i = @i - 1
END

DROP TABLE #JobsToProcess

我使用sp_executesql这是因为临时表的表名来自作业表中记录的文本。

使用我从dba.stackexchange.com帖子中学到的技巧,此存储过程每2秒运行一次。

我一生无法解决的问题是插入生产的速度。我的应用程序创建了临时登台表,并以令人难以置信的快速填充记录。插入产品无法跟上表格的数量,最终有成千上万的表格过剩。我能够跟上传入数据的唯一方法是删除生产SignalValues表上的所有键,索引,约束等。然后我要面对的问题是,该表最终有太多记录,因此无法查询。

我尝试使用[Timestamp]作为分区列对表进行分区无济于事。任何形式的索引都会使插入速度变慢,以至于无法跟上。另外,我需要提前创建数千个分区(每分钟一个小时?一个小时)。我不知道如何动态创建它们

我试图创建通过添加一个计算列称为表分区TimestampMinute,其价值是,上INSERTDATEPART(MINUTE, GETUTCDATE())。还是太慢了。

我已尝试根据此Microsoft文章使其成为内存优化表。也许我不知道该怎么做,但是MOT使刀片变慢了。

我检查了存储过程的执行计划,发现(我认为?)最密集的操作是

SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
    ON smd.SignalId = svs.SignalId

在我看来,这没有任何意义:我向存储过程添加了挂钟记录,事实证明并非如此。

在时间记录方面,上述特定语句在100k条记录上的执行时间约为300毫秒。

该声明

INSERT INTO SignalValues SELECT * FROM #sValues

在100,000个记录上以2500-3000ms执行。从表中删除受影响的记录,具体如下:

DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

再花300毫秒。

我怎样才能使它更快?SQL Server每天可以处理数十亿条记录吗?

如果相关,则为SQL Server 2014 Enterprise x64。

硬件配置:

我忘记在此问题的第一遍中包括硬件。我的错。

我将使用以下语句作为开头:我知道由于硬件配置,我将失去一些性能。我已经尝试了很多次,但是由于预算,C级,行星的对准等原因,不幸的是我无能为力。该服务器正在虚拟机上运行,​​我什至无法增加内存,因为我们根本没有更多的内存了。

这是我的系统信息:

系统信息

该存储通过iSCSI接口连接到VM服务器,并连接到NAS盒(这会降低性能)。NAS盒在RAID 10配置中有4个驱动器。它们是具有6GB / s SATA接口的4TB WD WD WD4000FYYZ旋转磁盘驱动器。该服务器仅配置了一个数据存储,因此tempdb和我的数据库位于同一数据存储上。

最大DOP为零。我应该将其更改为恒定值还是让SQL Server处理?我读了RCSI:我是否正确假设RCSI的唯一好处是行更新?永远不会有更新任何这些特定记录,他们将INSERTED和SELECT主编。RCSI还会使我受益吗?

我的tempdb是8mb。根据jyao的以下回答,我将#sValues更改为常规表,以完全避免使用tempdb。性能虽然差不多。我将尝试增加tempdb的大小和增长,但是鉴于#sValues的大小或多或少总是相同的,所以我预计不会有太大的收获。

我已经制定了以下所附的执行计划。该执行计划是登台表的一个迭代-100k条记录。查询的执行速度相当快,大约需要2秒钟,但是请记住,这在SignalValues表上没有索引,并且该SignalValues表(作为的目标)中INSERT没有记录。

执行计划


3
您是否已经尝试过延迟耐久性?
马丁·史密斯

2
插入缓慢的产品有哪些索引?
狗仔队2015年

到目前为止,我认为这里没有足够的数据来找出实际上消耗了这么多时间的东西。是CPU吗?是IO吗?由于您似乎每秒获得3万行,所以对我而言,它看起来不像IO。我是否理解这项权利,即您即将实现自己的绩效目标?您每秒需要50k行,因此每2秒进行一次100k批处理就足够了。现在,一批似乎需要3秒钟。过帐一个代表运行的实际执行计划。任何不攻击最耗时的操作的建议都是没有根据的。
usr

我已经发布了执行计划。
布兰登

Answers:


7

我已经计算出,在高峰时,平均记录量约为每天3亿4千万(运行20小时)。

从屏幕快照中,您只能为SQL Server分配8GB的总内存和6GB的内存。这对于您要实现的目标来说太低了。

我建议您将内存升级到更高的值-256GB,并同时增加VM CPU。

此时,您需要为工作量投资硬件。

另请参阅数据加载性能指南 -它描述了有效加载数据的智能方法。

我的tempdb是8mb。

根据您的编辑..,您应该有一个明智的tempdb-最好是多个tempdb数据文件,其大小与TF 1117和1118启用的实例范围相同。

我建议您进行专业的健康检查,然后从那里开始。

强烈推荐

  1. 提高您的服务器规格。

  2. 请专业人员*对您的数据库服务器实例进行健康检查,并按照建议进行操作。

  3. 一旦。和b。完成后,将自己投入到查询调整和其他优化中,例如查看等待统计信息,查询计划等。

注:我是一个专业的SQL Server专家hackhands.com -一个pluralsight公司,但绝不建议你聘请我帮忙。我只是建议您仅根据您的编辑来寻求专业帮助。

HTH。


我正在尝试为此提出一个建议(请参阅:乞求)更多的硬件。考虑到这一点,在这里您的回答是,从SQL Server配置或查询优化的角度来看,您没有其他建议建议这样做更快吗?
布兰登2015年

1

当面对一堵墙而无济于事时,针对大数据此类问题的一般建议:

一个鸡蛋大约要煮5分钟。如果有足够的电和水,则可以同时煮10个鸡蛋。

或者,换句话说:

首先,看一下硬件;其次,分离过程逻辑(数据重塑)并并行进行。

可以按表数量和表大小动态,自动地创建自定义垂直分区。如果我有Quarter_1_2017,Quarter_2_2017,Quarter_3_2017,Quarter_4_2017,Quarter_1_2018 ...并且我不知道我的记录在哪里以及有多少个分区,请同时针对所有自定义分区运行相同的查询,并分别进行会话和组装结果将根据我的逻辑进行处理。


OP的问题似乎在于处理插入和访问新输入的数据,而不是处理数周或数月前的数据。OP在他的时间戳上按分钟提及分区数据(因此有60个分区,将当前数据拆分为单独的存储桶);按季度划分将不会有太大帮助。您的观点通常是正确的,但在这种特定情况下不太可能对某人有所帮助。
RDFozz

-1

我将进行以下检查/优化:

  1. 确保生产数据库的数据和日志文件在插入操作过程中都没有增长(如果需要,可以预先增长)

  2. 不使用

    select * into [dest table] from [source table];

    但相反,请预先定义[目标表]。另外,我将截断表,而不是删除[目标表]并重新创建它。这样,如果需要,我将使用常规表来代替临时表。(我也可以在[目标表]上创建索引以促进联接查询的性能)

  3. 与其使用动态SQL,不如使用带有某些编码逻辑的硬编码表名来选择要操作的表。

  4. 我还将监视内存,CPU和磁盘I / O性能,以查看在大工作量期间是否存在资源不足。

  5. 既然您提到您可以通过在生产端删除索引来处理插入,所以我将检查是否发生了许多页面拆分,如果这样,我将在考虑删除之前降低索引的填充因子并重建索引索引。

祝您好运,并喜欢您的问题。


感谢您的回答。我将数据库的大小设置为1gb,然后以1gb的速度增长,因为预计增长操作将需要一些时间,这确实有助于提高速度。我将尝试实现今天的预增长。我确实将[dest]表实现为常规表,但是并没有看到多少性能提升。过去几天我没有很多时间,但今天我将尝试与其他人联系。
布兰登2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.