У нас є процес, який забирає дані з магазинів та оновлює таблицю інвентаризацій для всієї компанії. Ця таблиця містить рядки для кожного магазину за датою та за предметом. У клієнтів з багатьма магазинами цей стіл може вийти дуже великим - порядку 500 мільйонів рядів.
Цей процес оновлення запасів зазвичай запускається багато разів на день, коли магазини вводять дані. Ці запуски оновлюють дані лише з кількох магазинів. Однак клієнти також можуть запустити це, щоб оновити, скажімо, всі магазини за останні 30 днів. У цьому випадку процес накручується на 10 потоків і оновлює інвентар кожного магазину окремим потоком.
Замовник скаржиться, що процес займає тривалий час. Я профайлював процес і виявив, що один запит, що ВСТАВКИ в цю таблицю, займає набагато більше часу, ніж я очікував. Цей INSERT іноді завершується за 30 секунд.
Коли я запускаю спеціальну команду SQL INSERT проти цієї таблиці (обмежена BEGIN TRAN і ROLLBACK), спеціальна SQL завершується на порядок мілісекунд.
Нижче показано повільний запит. Ідея полягає в тому, щоб ВСТАВИТИ записи, яких немає, а пізніше ОНОВЛЕННЯ їх, коли ми обчислюємо різні біти даних. Попереднім кроком у процесі виявлено елементи, які потрібно оновити, виконати деякі обчислення та внести результати в таблицю tempdb Update_Item_Work. Цей процес працює в 10 окремих потоках, і кожен потік має свій GUID у Update_Item_Work.
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 байтів, тому я очікую, що на одній сторінці в 8 кб розміститься близько 33 рядків.
Таблиця кластеризована на унікальному обмеженні (Inv_Site_Key, Inv_Item_Key, Inv_Date). Усі клавіші DECIMAL (15,0), а дата SMALLDATETIME. Є первинний ключ IDENTITY (без кластера) та 4 інші індекси. Всі індекси та кластерне обмеження визначаються явним (FILLFACTOR = 90, PAD_INDEX = ON).
Я заглянув у файл журналу, щоб підрахувати розбиття сторінки. Я виміряв приблизно 1027 розщеплення за кластерним індексом та 1724 розщеплення за іншим індексом, але я не записав, за який інтервал вони відбулися. Через півтори години я виміряв 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, я бачу, що запит очікується на PAGEIOLATCH_EX тривалістю 0 на сторінці цієї таблиці інвентаризації. Я не бачу жодного очікування чи блокування на замки.
Ця машина має близько 32 ГБ пам'яті. У ньому працює стандартне видання SQL Server 2005, хоча вони незабаром оновлюються до версії 2008 R2 Enterprise Edition. У мене немає номерів, наскільки велика таблиця інвентаризації з точки зору використання диска, але я можу отримати це, якщо потрібно. Це одна з найбільших таблиць у цій системі.
Я запустив запит проти sys.dm_io_virtual_file_stats і побачив, що середнє очікування запису проти tempdb буде на 1,1 секунди . База даних, в якій зберігається ця таблиця, має середнє очікування запису ~ 350 мс. Але вони перезапускають сервер лише кожні 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