매장에서 데이터를 가져와 회사 전체의 재고 테이블을 업데이트하는 프로세스가 있습니다. 이 테이블에는 날짜 및 항목별로 모든 상점에 대한 행이 있습니다. 많은 상점을 보유한 고객의 경우이 테이블은 5 억 개의 행으로 매우 커질 수 있습니다.
이 재고 갱신 프로세스는 일반적으로 상점이 데이터를 입력함에 따라 하루에 여러 번 실행됩니다. 이 실행은 소수의 상점에서만 데이터를 업데이트합니다. 그러나 고객은 지난 30 일 동안 모든 상점을 업데이트하기 위해이를 실행할 수도 있습니다. 이 경우 프로세스는 10 개의 스레드를 가동시키고 각 저장소의 인벤토리를 별도의 스레드로 업데이트합니다.
고객이 처리하는 데 시간이 오래 걸린다고 불평하고 있습니다. 프로세스를 프로파일 링하고이 테이블에 INSERT하는 하나의 쿼리가 예상보다 훨씬 많은 시간을 소비하고 있음을 발견했습니다. 이 삽입은 때때로 30 초 안에 완료됩니다.
이 테이블에 대해 임시 SQL INSERT 명령을 실행하면 (BEGIN TRAN 및 ROLLBACK에 의해 제한됨) 임시 SQL이 밀리 초 단위로 완료됩니다.
느리게 수행되는 쿼리는 다음과 같습니다. 아이디어는 다양한 데이터 비트를 계산할 때 존재하지 않는 레코드를 삽입하고 나중에 업데이트하는 것입니다. 프로세스의 이전 단계에서 업데이트해야 할 항목을 식별하고 일부 계산을 수행 한 후 tempdb 테이블 Update_Item_Work에 결과를 입력했습니다. 이 프로세스는 10 개의 개별 스레드에서 실행 중이며 각 스레드에는 Update_Item_Work에 고유 한 GUID가 있습니다.
INSERT INTO Inventory
(
Inv_Site_Key,
Inv_Item_Key,
Inv_Date,
Inv_BusEnt_ID,
Inv_End_WtAvg_Cost
)
SELECT DISTINCT
UpdItemWrk_Site_Key,
UpdItemWrk_Item_Key,
UpdItemWrk_Date,
UpdItemWrk_BusEnt_ID,
(CASE UpdItemWrk_Set_WtAvg_Cost WHEN 1 THEN UpdItemWrk_WtAvg_Cost ELSE 0 END)
FROM tempdb..Update_Item_Work (NOLOCK)
WHERE UpdItemWrk_GUID = @GUID
AND NOT EXISTS
-- Only insert for site/item/date combinations that don't exist
(SELECT *
FROM Inventory (NOLOCK)
WHERE Inv_Site_Key = UpdItemWrk_Site_Key
AND Inv_Item_Key = UpdItemWrk_Item_Key
AND Inv_Date = UpdItemWrk_Date)
재고 테이블에는 42 개의 열이 있으며, 대부분의 열은 다양한 재고 조정에 대한 수량 및 개수를 추적합니다. sys.dm_db_index_physical_stats에 따르면 각 행은 약 242 바이트이므로 단일 8KB 페이지에 약 33 개의 행이 들어갈 것으로 예상합니다.
테이블은 고유 제한 조건 (Inv_Site_Key, Inv_Item_Key, Inv_Date)에 클러스터됩니다. 모든 키는 DECIMAL (15,0)이며 날짜는 SMALLDATETIME입니다. IDENTITY 기본 키 (비 클러스터형) 및 4 개의 다른 인덱스가 있습니다. 모든 인덱스 및 클러스터 제약 조건은 명시 적으로 정의됩니다 (FILLFACTOR = 90, PAD_INDEX = ON).
페이지 분할을 계산하기 위해 로그 파일을 조사했습니다. 클러스터형 인덱스에서 약 1,027 개의 분할을 측정하고 다른 인덱스에서 1,724 개의 분할을 측정했지만 이러한 간격이 발생한 간격에 대해서는 기록하지 않았습니다. 1 시간 반 후에 클러스터 된 인덱스에서 7,035 페이지 분할을 측정했습니다.
프로파일 러에서 캡처 한 쿼리 계획은 다음과 같습니다.
Rows Executes StmtText
---- -------- --------
490 1 Sequence
0 1 |--Index Update
0 1 | |--Collapse
0 1 | |--Sort
0 1 | |--Filter
996 1 | |--Table Spool
996 1 | |--Split
498 1 | |--Assert
0 0 | |--Compute Scalar
498 1 | |--Clustered Index Update(UK_Inventory)
498 1 | |--Compute Scalar
0 0 | |--Compute Scalar
0 0 | |--Compute Scalar
498 1 | |--Compute Scalar
498 1 | |--Top
498 1 | |--Nested Loops
498 1 | |--Stream Aggregate
0 0 | | |--Compute Scalar
498 1 | | |--Clustered Index Seek(tempdb..Update_Item_Work)
498 498 | |--Clustered Index Seek(Inventory)
0 1 |--Index Update(UX_Inv_Exceptions_Date_Site_Item)
0 1 | |--Collapse
0 1 | |--Sort
0 1 | |--Filter
996 1 | |--Table Spool
490 1 |--Index Update(UX_Inv_Date_Site_Item)
490 1 |--Collapse
980 1 |--Sort
980 1 |--Filter
996 1 |--Table Spool
쿼리와 다양한 dmv를 살펴보면이 인벤토리 테이블의 페이지에서 쿼리가 0의 지속 시간 동안 PAGEIOLATCH_EX에서 대기하고 있음을 알 수 있습니다. 잠금이 대기 또는 차단되지 않습니다.
이 기계에는 약 32GB의 메모리가 있습니다. 2008 R2 Enterprise Edition으로 곧 업그레이드 될 예정이지만 SQL Server 2005 Standard Edition을 실행하고 있습니다. 디스크 사용량 측면에서 인벤토리 테이블의 크기에 대한 숫자는 없지만 필요한 경우 얻을 수 있습니다. 이 시스템에서 가장 큰 테이블 중 하나입니다.
sys.dm_io_virtual_file_stats에 대해 쿼리를 실행했으며 tempdb에 대한 평균 쓰기 대기 시간이 1.1 초 이상임을 알았 습니다 . 이 테이블이 저장된 데이터베이스의 평균 쓰기 대기 시간은 ~ 350ms입니다. 그러나 그들은 6 개월 정도마다 서버를 다시 시작하기 때문에이 정보가 관련이 있는지 모르겠습니다. tempdb는 4 개의 서로 다른 파일로 분산되어 있으며 인벤토리 테이블을 보유하는 데이터베이스에 대해 3 개의 서로 다른 파일이 있습니다.
단일 INSERT가 매우 빠를 때 많은 다른 스레드로 실행될 때이 쿼리가 왜 몇 개의 행을 삽입하는 데 시간이 오래 걸립니까?
-업데이트-
읽은 바이트를 포함하여 드라이브 당 대기 시간 수는 다음과 같습니다. 보시다시피 tempdb 성능에 문제가 있습니다. 재고 테이블은 PDICompany_252_01.mdf, PDICompany_252_01_Second.ndf 또는 PDICompany_252_01_Third.ndf에 있습니다.
ReadLatencyWriteLatencyLatencyAvgBPerRead AvgBPerWriteAvgBPerTransferDriveDB physical_name
42 1112 623 62171 67654 65147R: tempdb R:\Microsoft SQL Server\Tempdb\tempdev1.mdf
38 1101 615 62122 67626 65109S: tempdb S:\Microsoft SQL Server\Tempdb\tempdev2.ndf
38 1101 615 62136 67639 65123T: tempdb T:\Microsoft SQL Server\Tempdb\tempdev3.ndf
38 1101 615 62140 67629 65119U: tempdb U:\Microsoft SQL Server\Tempdb\tempdev4.ndf
25 341 71 92767 53288 87009X: PDICompany X:\Program Files\PDI\Enterprise\Databases\PDICompany_Third.ndf
26 339 71 90902 52507 85345X: PDICompany X:\Program Files\PDI\Enterprise\Databases\PDICompany_Second.ndf
10 231 90 98544 60191 84618W: PDICompany_FRx W:\Program Files\PDI\Enterprise\Databases\PDICompany_FRx.mdf
61 137 68 9120 9181 9125W: model W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\modeldev.mdf
36 113 97 9376 5663 6419V: model V:\Microsoft SQL Server\Logs\modellog.ldf
22 99 34 92233 52112 86304W: PDICompany W:\Program Files\PDI\Enterprise\Databases\PDICompany.mdf
9 20 10 25188 9120 23538W: master W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\master.mdf
20 18 19 53419 10759 40850W: msdb W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\MSDBData.mdf
23 18 19 947956 58304 110123V: PDICompany_FRx V:\Program Files\PDI\Enterprise\Databases\PDICompany_FRx_1.ldf
20 17 17 828123 55295 104730V: PDICompany V:\Program Files\PDI\Enterprise\Databases\PDICompany.ldf
5 13 13 12308 4868 5129V: master V:\Microsoft SQL Server\Logs\mastlog.ldf
11 13 13 22233 7598 8513V: PDIMaster V:\Program Files\PDI\Enterprise\Databases\PDIMaster.ldf
14 11 13 13846 9540 12598W: PDIMaster W:\Program Files\PDI\Enterprise\Databases\PDIMaster.mdf
13 11 11 22350 1107 1110V: msdb V:\Microsoft SQL Server\Logs\MSDBLog.ldf
17 9 9 745437 11821 23249V: PDIFoundation V:\Program Files\PDI\Enterprise\Databases\PDIFoundation.ldf
34 8 31 29490 33725 30031W: PDIFoundation W:\Program Files\PDI\Enterprise\Databases\PDIFoundation.mdf
5 8 8 61560 61236 61237V: tempdb V:\Microsoft SQL Server\Logs\templog.ldf
13 6 11 8370 35087 16785W: SAHost_Company01 W:\Program Files\PDI\Enterprise\Databases\SAHostCompany.mdf
2 6 5 56235 33667 38911W: SAHost_Company01 W:\Program Files\PDI\Enterprise\Databases\SAHost_Company_01_log.LDF