Величезні дані та продуктивність у SQL Server


20

Я написав заявку на сервер 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, не має ніяких записів в ньому.

План виконання


3
Ви вже експериментували із затримкою міцності?
Мартін Сміт

2
Які показники існували при повільних виробничих вставках?
папараццо

Поки я не думаю, що тут є достатньо даних, щоб дізнатися, що насправді забирає стільки часу. Це процесор? Це IO? Оскільки ви, здається, отримуєте 30 к рядків в секунду, мені це не схоже на IO. Я розумію це право, що ви досить близькі до досягнення своєї перф-мети? Вам потрібно 50k рядків в секунду, тому однієї партії 100k кожні 2 секунди повинно вистачити. Зараз, здається, одна партія займає 3 секунди. Опублікуйте фактичний план виконання одного представницького циклу. Будь-яка пропозиція, яка не атакує найбільш трудомісткі операції, є суперечкою.
usr

Я розмістив план виконання.
Брендон

Відповіді:


7

Я підрахував, що на піку середня кількість записів знаходиться десь у проспекті 3-4 мільярди на день (20 годин роботи).

З екрану екрана ви ТІЛЬКИ маєте 8 Гб оперативної пам’яті та 6 ГБ, виділених на SQL Server. Це занадто низький рівень для того, що ви намагаєтеся досягти.

Я пропоную вам оновити пам’ять до більш високого значення - 256 Гб і також збільшити свої процесори VM.

У цей момент вам потрібно вкласти кошти в апаратне забезпечення для вашого навантаження.

Також зверніться до посібника з продуктивності завантаження даних - він описує розумні способи ефективного завантаження даних.

Мій tempdb - 8 Мб.

Виходячи з редагування .. у вас повинен бути розумний tempdb - бажано, декілька файлів даних tempdb однакового розміру разом із екземплярами TF 1117 та 1118 з широким екземпляром.

Я б запропонував вам пройти професійну перевірку здоров’я та почати звідти.

Настійно рекомендую

  1. Збільшити свої серверні характеристики.

  2. Запропонуйте професіоналу * провести медичну перевірку екземпляра сервера вашої бази даних та дотримуйтесь рекомендацій.

  3. Раз а. і b. зроблено, а потім зануримось у налаштування запитів та інші оптимізації, такі як перегляд статистики очікування, плани запитів тощо.

Примітка. Я є професійним експертом сервера sql на сайті hackhands.com - компанії з питань плюралізму, але жодним чином не пропоную вам найняти мене на допомогу. Я просто пропоную вам скористатися професійною допомогою лише на основі ваших змін.

HTH.


Я намагаюся скласти для цього пропозицію (читати: благаю) більше обладнання для цього. Зважаючи на це і вашу відповідь тут, ви не можете запропонувати зробити це швидше?
Брендон

1

Загальна порада при таких проблемах із великими даними, коли стикається зі стіною і нічого не працює:

Одне яйце готується 5 хвилин. 10 яєць будуть зварені одночасно, якщо вистачить електроенергії та води.

Або, іншими словами:

Спочатку подивіться на обладнання; по-друге, відокремте логіку процесу (реконструкція даних) і зробіть це паралельно.

Цілком можливо створити власні вертикальні розділення динамічно та автоматизовано, за кількістю таблиць та за розміром таблиці; Якщо у мене є Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... і я не знаю, де мої записи і скільки у мене розділів, запускайте один і той же запит щодо всіх спеціальних розділів одночасно, окремі сесії та збірка результат, який слід обробити вперед за моєю логікою.


Проблема ОП, схоже, стосується вставки та доступу до нещодавно введених даних, більше ніж обробка даних за тиждень чи місяці тому. ОП згадує дані про розділення за хвилиною на його часовій позначці (так 60 розділів, розбиваючи поточні дані на окремі відрізки); розщеплення на чверть, швидше за все, не допоможе. Ваша думка загалом сприймається, але навряд чи допоможе комусь у цій конкретній ситуації.
RDFozz

-1

Я проведу таку перевірку / оптимізацію:

  1. Переконайтесь, що файл даних та журналів виробничої бази даних не росте під час операції вставки (попередньо розробити її за потреби)

  2. Не використовувати

    select * into [dest table] from [source table];

    але замість цього попередньо визначте [таблицю dest]. Також замість того, щоб скидати [таблицю dest] і відтворити його, я врізаю таблицю. Таким чином, якщо потрібно, замість того, щоб використовувати таблицю temp, я б використовував звичайну таблицю. (Я також можу створити індекс у [dest таблиці] для полегшення виконання запиту приєднання)

  3. Замість того, щоб використовувати динамічний sql, я б скористався жорстко кодованими іменами таблиці з певною логікою кодування, щоб вибрати, якою таблицею працювати.

  4. Я також буду стежити за роботою пам’яті, процесора та дискового вводу / виводу, щоб побачити, чи є голодні ресурси під час великого навантаження.

  5. Оскільки ви згадали, що можете вводити вставку, скидаючи індекси на виробничій стороні, я би перевірив, чи виникає багато розщеплення сторінок, якщо так, я зменшив би коефіцієнт заповнення індексів і відновив індекси перед тим, як я зважуся на скидання індекси.

Удачі та любові у вашому питанні.


Дякую за відповідь. Я встановив розмір бази даних на 1 ГБ і зрости на 1 ГБ, очікуючи, що операції з росту потребують певного часу, що спочатку допомогло зі швидкістю. Я спробую сьогодні здійснити передріст. Я реалізував таблицю [dest] як звичайну таблицю, але не побачив значного збільшення продуктивності. У мене не було багато часу за останні кілька днів, але я спробую сьогодні дістатися до інших.
Брендон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.