SQL Server 백엔드를 사용하여 매우 많은 양의 레코드를 수집하고 저장하는 응용 프로그램을 작성했습니다. 나는 최고점에서 평균 레코드 양이 하루에 약 3-4 억 (20 시간의 작동 시간) 정도 어딘가에 계산했습니다.
내 원래 솔루션 (실제로 데이터를 계산하기 전에)은 내 응용 프로그램이 클라이언트가 쿼리 한 동일한 테이블에 레코드를 삽입하는 것이 었습니다. 많은 레코드가 삽입 된 테이블을 쿼리 할 수 없기 때문에 충돌이 발생했습니다.
두 번째 솔루션은 두 개의 데이터베이스를 사용하는 것이 었습니다. 하나는 응용 프로그램이 수신 한 데이터와 하나는 클라이언트 준비 데이터를위한 것입니다.
내 응용 프로그램은 데이터를 수신하여 ~ 100k 레코드의 배치로 청크하고 준비 테이블에 대량 삽입합니다. ~ 100k 레코드 후 응용 프로그램은 즉시 이전과 동일한 스키마를 사용하여 다른 준비 테이블을 만들고 해당 테이블에 삽입을 시작합니다. 100k 레코드가있는 테이블 이름으로 작업 테이블에 레코드를 작성하고 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
내가 사용하는 sp_executesql
스테이징 테이블에 대한 테이블 이름은 작업 테이블의 레코드에서 텍스트로 제공하기 때문이다.
이 저장 프로시 저는 이 dba.stackexchange.com post 에서 배운 트릭을 사용하여 2 초마다 실행됩니다 .
내가 평생 해결할 수없는 문제는 생산에 대한 인서트가 수행되는 속도입니다. 내 응용 프로그램은 임시 준비 테이블을 만들어 매우 빠르게 레코드로 채 웁니다. 프로덕션에 대한 삽입은 테이블의 양을 따라갈 수 없으며 결국 수천의 테이블에 잉여 테이블이 있습니다. 단지 내가 들어오는 데이터를 유지할 수 있었던 방법은 생산에 ... 모든 키, 인덱스, 제약 조건 등을 제거하는 것입니다 SignalValues
테이블. 내가 직면 한 문제는 테이블이 너무 많은 레코드로 끝나서 쿼리 할 수 없게된다는 것입니다.
[Timestamp]
분할 열로 사용하여 테이블을 분할하려고했지만 아무 소용이 없습니다. 인덱싱의 모든 형태는 인서트를 너무 느리게 유지하여 유지할 수 없습니다. 또한 몇 년 전에 수천 분의 파티션 (1 분에 1 시간?)을 만들어야합니다. 나는 그것을 즉시 만드는 방법을 알 수 없었다
I 불리는 테이블에 계산 된 열에 추가하여 분할 만드는 시도 TimestampMinute
값이고, 온 INSERT
, DATEPART(MINUTE, GETUTCDATE())
. 여전히 너무 느립니다.
이 Microsoft 기사에 따라 메모리 최적화 테이블로 만들려고했습니다 . 어쩌면 나는 그것을하는 방법을 이해하지 못하지만 MOT는 어떻게 든 삽입물을 느리게 만들었습니다.
저장 프로 시저의 실행 계획을 확인한 결과 가장 집중적 인 작업은 다음과 같습니다.
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
나에게 이것은 말이되지 않습니다 : 나는 다른 방법으로 입증 된 저장 프로 시저에 벽 시계 로깅을 추가했습니다.
시간 기록과 관련하여 위의 특정 명령문은 100k 레코드에서 ~ 300ms로 실행됩니다.
진술
INSERT INTO SignalValues SELECT * FROM #sValues
100k 레코드에서 2500-3000ms로 실행됩니다. 다음에 따라 영향을받은 레코드를 테이블에서 삭제합니다.
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
또 다른 300ms가 걸립니다.
어떻게하면 더 빨리 할 수 있습니까? SQL Server는 매일 수십억 개의 레코드를 처리 할 수 있습니까?
관련이있는 경우 이는 SQL Server 2014 Enterprise x64입니다.
하드웨어 구성 :
이 질문의 첫 번째 단계에서 하드웨어를 포함하는 것을 잊었습니다. 내 잘못이야.
다음과 같이이 머리말을 붙 입니다. 하드웨어 구성으로 인해 성능이 저하되는 것을 알고 있습니다. 나는 여러 번 시도했지만 예산, C 레벨, 행성의 정렬 등으로 인해 불행히도 더 나은 설정을하기 위해 할 수있는 일은 없습니다. 서버가 가상 머신에서 실행 중이며 더 이상 메모리가 없기 때문에 메모리를 늘릴 수도 없습니다.
내 시스템 정보는 다음과 같습니다.
스토리지는 iSCSI 인터페이스를 통해 VM 서버에 NAS 상자에 연결됩니다 (이로 인해 성능이 저하됩니다). NAS 상자에는 RAID 10 구성에 4 개의 드라이브가 있습니다. 6GB / s SATA 인터페이스를 갖춘 4TB WD WD4000FYYZ 회전 디스크 드라이브입니다. 서버에는 하나의 데이터 저장소 만 구성되어 tempdb와 내 데이터베이스가 동일한 데이터 저장소에 있습니다.
최대 DOP는 0입니다. 이것을 상수 값으로 변경하거나 SQL Server가 처리하도록해야합니까? RCSI에 대해 읽었습니다. RCSI의 유일한 이점이 행 업데이트와 함께 제공된다고 가정하면 정확합니까? 이러한 특정 기록의 업데이트가 없을 것, 그들은됩니다 INSERT
에드와 SELECT
에드. RCSI가 여전히 나에게 도움이됩니까?
내 tempdb는 8mb입니다. jyao의 아래 답변에 따라 tempdb를 완전히 피하기 위해 #sValues를 일반 테이블로 변경했습니다. 그래도 성능은 거의 동일했습니다. tempdb의 크기와 성장을 늘리려 고 노력하지만 #sValues의 크기가 항상 거의 같은 크기가 될 것이므로 많은 이득을 기대하지는 않습니다.
아래 첨부 된 실행 계획을 세웠습니다. 이 실행 계획은 준비 테이블 (100k 레코드)의 반복입니다. 쿼리 실행은 2 초 정도 걸리지 만 상당히 빠르지 만 SignalValues
테이블에 인덱스가없고 SignalValues
테이블의 대상인 테이블에 INSERT
레코드가 없습니다.