Bra*_*don 20 performance sql-server sql-server-2014 query-performance
我编写了一个带有 SQL Server 后端的应用程序,用于收集和存储大量记录。我已经计算出,在高峰期,平均记录量大约为每天 3-40 亿条(运行 20 小时)。
我最初的解决方案(在我完成数据的实际计算之前)是让我的应用程序将记录插入到我的客户查询的同一个表中。显然,这会很快崩溃并烧毁,因为不可能查询插入了这么多记录的表。
我的第二个解决方案是使用 2 个数据库,一个用于应用程序接收的数据,另一个用于客户端就绪数据。
我的应用程序将接收数据,将其分成大约 10 万条记录的批次,然后批量插入到临时表中。在大约 100k 条记录之后,应用程序将使用与之前相同的架构即时创建另一个临时表,并开始插入到该表中。它将在具有 10 万条记录的作业表中创建一条记录,并且 SQL Server 端的存储过程会将数据从临时表移动到客户端就绪的生产表,然后删除我的应用程序创建的表临时表。
除了具有作业表的临时数据库外,两个数据库都具有相同的 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
Run Code Online (Sandbox Code Playgroud)
我使用sp_executesql是因为临时表的表名是来自作业表中记录的文本。
这个存储过程使用我从这篇 dba.stackexchange.com 帖子中学到的技巧每 2 秒运行一次。
我终生无法解决的问题是插入生产的速度。我的应用程序创建临时登台表并以惊人的速度填充记录。插入到生产中无法跟上表的数量,最终会出现成千上万的表过剩。我一直能够跟上传入数据的唯一方法是删除生产SignalValues表上的所有键、索引、约束等。然后我面临的问题是,表中的记录太多以至于无法查询。
我尝试使用 将表[Timestamp]作为分区列进行分区,但无济于事。任何形式的索引都会减慢插入速度,以至于它们无法跟上。此外,我需要提前几年创建数千个分区(每分钟一个?每小时一个?)。我不知道如何即时创建它们
我尝试通过向名为的表中添加一个计算列来创建分区,该表TimestampMinute的值为, on INSERT, DATEPART(MINUTE, GETUTCDATE())。还是太慢了。
我已经尝试根据这篇 Microsoft 文章将其设为内存优化表。也许我不明白该怎么做,但是 MOT 以某种方式使插入变慢了。
我检查了存储过程的执行计划,发现(我认为?)最密集的操作是
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
Run Code Online (Sandbox Code Playgroud)
对我来说,这没有意义:我已经向存储过程添加了挂钟日志,但事实证明并非如此。
在时间记录方面,上面的特定语句在 10 万条记录上执行约 300 毫秒。
该声明
INSERT INTO SignalValues SELECT * FROM #sValues
Run Code Online (Sandbox Code Playgroud)
在 2500-3000 毫秒内对 10 万条记录执行。从表中删除受影响的记录,根据:
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
Run Code Online (Sandbox Code Playgroud)
还需要 300 毫秒。
我怎样才能更快地做到这一点?SQL Server 每天可以处理数十亿条记录吗?
如果相关,这是 SQL Server 2014 Enterprise x64。
硬件配置:
我忘了在这个问题的第一遍中包含硬件。我的错。
我将以这些声明作为开头:我知道由于我的硬件配置,我正在失去一些性能。我已经尝试了很多次,但由于预算、C 级水平、行星的排列等等……不幸的是,我无法做任何事情来获得更好的设置。服务器在虚拟机上运行,我什至无法增加内存,因为我们根本没有更多内存。
这是我的系统信息:
存储通过 iSCSI 接口连接到 VM 服务器到 NAS 盒(这会降低性能)。NAS 盒在 RAID 10 配置中有 4 个驱动器。它们是具有 6GB/s SATA 接口的 4TB WD WD4000FYYZ 旋转磁盘驱动器。服务器只配置了一个数据存储,因此 tempdb 和我的数据库位于同一个数据存储上。
最大 DOP 为零。我应该将其更改为常量值还是让 SQL Server 处理它?我阅读了 RCSI:我是否正确地假设 RCSI 的唯一好处来自行更新?这些特定记录中的任何一个永远不会更新,它们将被INSERT编辑和SELECT编辑。RCSI 还会使我受益吗?
我的临时数据库是 8mb。根据 jyao 的以下答案,我将 #sValues 更改为常规表以完全避免使用 tempdb。性能大致相同。我将尝试增加 tempdb 的大小和增长,但鉴于 #sValues 的大小或多或少总是相同的大小,我预计不会有太大的收益。
我已经在下面附上了一个执行计划。这个执行计划是临时表的一次迭代——100k 记录。查询的执行相当快,大约 2 秒,但请记住,这在SignalValues表上没有索引,并且SignalValues表( 的目标)中INSERT没有记录。
我已经计算出,在高峰期,平均记录量大约为每天 3-40 亿条(运行 20 小时)。
从您的屏幕截图中,您只有 8GB 的内存总 RAM 和 6GB 分配给 SQL Server。这对于您要实现的目标来说太低了。
我建议您将内存升级到更高的值 - 256GB 并提高您的 VM CPU。
此时您需要为您的工作负载投资硬件。
另请参阅数据加载性能指南- 它描述了有效加载数据的智能方法。
我的临时数据库是 8mb。
根据您的编辑 .. 您应该有一个合理的 tempdb - 最好是多个 tempdb 数据文件大小相同以及 TF 1117 和 1118 启用实例宽度。
我建议您进行专业的健康检查,然后从那里开始。
强烈推荐
提高您的服务器规格。
让专业*人员对您的数据库服务器实例进行健康检查并遵循建议。
一旦。和 b. 完成,然后将自己沉浸在查询调整和其他优化中,例如查看等待统计信息、查询计划等。
注:我是一个专业的SQL Server专家在hackhands.com -一个pluralsight公司,但绝不建议你聘请我帮忙。我只是建议您仅根据您的编辑获得专业帮助。
哈。
jya*_*yao -1
我将进行以下检查/优化:
确保生产数据库的数据和日志文件在插入操作期间不会增长(如果需要,请预先增长)
不使用
select * into [dest table] from [source table];
Run Code Online (Sandbox Code Playgroud)
而是预先定义 [dest table]。另外,我不会删除 [dest table] 并重新创建它,而是截断表。这样,如果需要,我将使用常规表,而不是使用临时表。(我也可能在[dest table]上创建索引以方便连接查询的性能)
我宁愿使用硬编码的表名和一些编码逻辑来选择要操作的表,而不是使用动态sql。
我还会监控内存、CPU 和磁盘 I/O 性能,看看在大工作负载期间是否存在资源匮乏的情况。
既然您提到您可以通过删除生产端的索引来处理插入,我会检查是否发生了许多页面拆分,如果是这样,我会减少索引的填充因子并重建索引,然后再考虑删除索引。
祝你好运,喜欢你的问题。