Я написав заявку на сервер SQL Server, який збирає та зберігає та надзвичайно велику кількість записів. Я підрахував, що на піку середня кількість записів знаходиться десь у проспекті 3-4 мільярди на день (20 годин роботи).
Моє оригінальне рішення (до того, як я фактично зробив обчислення даних) - моє додаток вставляв записи в ту саму таблицю, яку запитували мої клієнти. Це вийшло з ладу та згоріло досить швидко, очевидно, тому що неможливо запитувати таблицю, в яку вставлено багато записів.
Моє друге рішення полягало у використанні 2 баз даних, одну для даних, отриманих програмою, та одну для даних, готових до клієнта.
Моя програма отримала б дані, склала їх у партії ~ 100 тис. Записів і вставла групово в таблицю інсценізації. Після ~ 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
тому що назви таблиць для постановочних таблиць надходять як текст із записів у таблиці завдань.
Ця збережена процедура працює кожні 2 секунди, використовуючи трюк, який я дізнався з цієї публікації на dba.stackexchange.com .
Проблема, яку я не можу вирішити протягом життя, - швидкість, з якою виконуються вставки у виробництво. Мій додаток створює тимчасові таблиці постановки та неймовірно швидко заповнює їх записами. Вставка у виробництво не може йти в ногу з кількістю таблиць, і, зрештою, є надлишок таблиць у тисячах. Тільки спосіб , яким я коли - небудь був в змозі йти в ногу зі вступниками даними, щоб видалити всі ключі, індекси, обмеження і т.д. ... на виробничому SignalValues
столі. Проблема, з якою я стикаюсь, полягає в тому, що таблиця закінчується такою кількістю записів, що їх неможливо запитувати.
Я спробував розділити таблицю, використовуючи [Timestamp]
стовпчик розділення безрезультатно. Будь-яка форма індексації взагалі сповільнює вставки настільки, що вони не можуть йти в ногу. Крім того, мені потрібно було б створити тисячі розділів (кожну хвилину? Годину?) Років наперед. Я не міг зрозуміти, як їх створити на льоту
Я спробував створити розбиття, додавши обчислюваний стовпець в таблиці під назвою TimestampMinute
, значення якого було, по INSERT
, DATEPART(MINUTE, GETUTCDATE())
. Ще занадто повільно.
Я спробував скласти таблицю, оптимізовану пам'ять, відповідно до цієї статті Microsoft . Можливо, я не розумію, як це зробити, але MOT зробив вставки якось повільніше.
Я перевірив План виконання збереженої процедури і виявив, що (я думаю?) Найінтенсивніша операція
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
Для мене це не має сенсу: я додав реєстрацію настінного годинника до збереженої процедури, яка виявила інше.
Що стосується журналу часу, цей конкретний оператор вище виконується в ~ 300 мс на 100 к записів.
Заява
INSERT INTO SignalValues SELECT * FROM #sValues
виконується в 2500-3000мс на 100к записів. Видалення з таблиці впливаних записів на:
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
займає ще 300 мс.
Як я можу зробити це швидше? Чи може SQL Server обробляти мільярди записів на день?
Якщо це доречно, це SQL Server 2014 Enterprise x64.
Конфігурація обладнання:
Я забув включити обладнання в першому проході цього питання. Моє ліжко.
Я виступлю з цим твердженнями: я знаю, що втрачаю певну продуктивність через конфігурацію обладнання. Я багато разів намагався, але через бюджет, рівень C, вирівнювання планет тощо ... я, на жаль, нічого не можу зробити, щоб покращити налаштування. Сервер працює на віртуальній машині, і я навіть не можу збільшити пам'ять, тому що у нас просто більше немає.
Ось моя системна інформація:
Сховище приєднано до сервера VM через інтерфейс iSCSI до коробки NAS (це погіршить продуктивність). У коробці NAS є 4 накопичувачі в конфігурації RAID 10. Це 4-ти дискові накопичувачі WD WD4000FYYZ з обертовими дисками з інтерфейсом SATA 6 Гб / с. На сервері є лише один сховище даних, налаштований таким чином tempdb і моя база даних знаходяться в одній сховищі даних.
Максимум DOP дорівнює нулю. Чи потрібно змінити це на постійне значення або просто дозволити SQL Server це впоратися? Я читав про RCSI: Чи я правильно вважаю, що єдину користь від RCSI отримує оновлення рядків? Ніколи не буде оновлень жодної з цих записів, вони будуть INSERT
редагуватися та SELECT
редагуватися. Чи все-таки мені допоможе RCSI?
Мій tempdb - 8 Мб. На підставі відповіді нижче від jyao, я змінив #sValues на звичайну таблицю, щоб взагалі уникнути tempdb. Продуктивність була приблизно однаковою. Я спробую збільшити розмір і зростання tempdb, але враховуючи, що розмір #sValues буде більш-менш завжди бути однакового розміру, я не очікую великого прибутку.
Я взяв план виконання, який я додав нижче. Цей план виконання є однією ітерацією поетапної таблиці - 100k записів. Виконання запиту було досить швидко, близько 2 -х секунд, але майте на увазі , що це без індексів на SignalValues
стіл і SignalValues
стіл, мішень з INSERT
, не має ніяких записів в ньому.