Database
 sql >> база данни >  >> RDS >> Database

Минимално регистриране с INSERT...SELECT и бързо зареждане на контекст

Тази публикация предоставя нова информация относно предпоставките за минимално регистриран насипен товар когато използвате INSERT...SELECT в индексирани таблици .

Вътрешното средство, което позволява тези случаи, се нарича FastLoadContext . Може да се активира от SQL Server 2008 до 2014 включително с помощта на документиран флаг за проследяване 610. От SQL Server 2016 нататък, FastLoadContext е активиран по подразбиране; флагът за проследяване не се изисква.

Без FastLoadContext , единствените вмъквания на индекс, които могат да бъдат минимално регистрирани те са в празна клъстериран индекс без вторични индекси, както е обхванато в част втора от тази серия. Минимално регистриране условията за неиндексирани хеп таблици бяха разгледани в част първа.

За повече информация вижте Ръководството за зареждане на производителността на данните и Екипът на Tiger бележки относно промените в поведението за SQL Server 2016.

Контекст за бързо зареждане

Като бързо напомняне, RowsetBulk съоръжение (обхванато в части 1 и 2) позволява минимално регистриран насипно натоварване за:

  • Празно и непразнохийп таблици с:
    • Заключване на масата; и
    • Без вторични индекси.
  • Празни групирани таблици , с:
    • Заключване на масата; и
    • Без вторични индекси; и
    • DMLRequestSort=true на Клъстеризирано вмъкване на индекс оператор.

FastLoadContext кодовият път добавя поддръжка за минимално регистрирани и едновременни насипно натоварване на:

  • Празни и непразни групирани b-дърво индекси.
  • Празни и непразни неклъстерни b-дърво индекси, поддържани от специализиран Вмъкване на индекс оператор на план.

FastLoadContext също така изисква DMLRequestSort=true на съответния оператор на план във всички случаи.

Може да сте забелязали припокриване между RowsetBulk и FastLoadContext за празни клъстерирани таблици без вторични индекси. A TABLOCK намек не се изисква с FastLoadContext , но тоне е задължително да отсъствате или. Като следствие, подходяща вмъкване с TABLOCK все пак може да отговаря на изискванията за минимална сеч чрез FastLoadContext ако не успее, подробният RowsetBulk тестове.

FastLoadContext може да бъде деактивиран на SQL Server 2016 с помощта на документиран флаг за проследяване 692. Разширено събитие на канала за отстраняване на грешки fastloadcontext_enabled може да се използва за наблюдение на FastLoadContext използване на индексен дял (набор от редове). Това събитие не се задейства за RowsetBulk товари.

Смесено регистриране

Единичен INSERT...SELECT израз, използващ FastLoadContext може да напълно регистриран някои редове при минимално регистриране други.

Редовете се вмъкват един по един от Индекс вмъкване оператор инапълно регистриран в следните случаи:

  • Всички редове, добавени към първия индексна страница, ако индексът е бил празен в началото на операцията.
  • Редове, добавени към съществуващи индексни страници.
  • Редовете преместени между страници чрез разделяне на страница.

В противен случай редовете от поръчания поток за вмъкване се добавят към чисто нова страница използвайки оптимизиран и минимално регистриран кодов път. След като на новата страница бъдат записани възможно най-много редове, тя се свързва директно със съществуващата структура на целевия индекс.

Новодобавената страница не е задължително да бъде пълен (макар че очевидно това е идеалният случай), защото SQL Server трябва да внимава да не добавя редове към новата страница, които логически принадлежат на съществуващ индексна страница. Новата страница ще бъде „зашита“ в индекса като единица, така че не можем да имаме редове на новата страница, които принадлежат на друго място. Това е основно проблем при добавяне на редове вътре съществуващия диапазон от ключове на индекса, а не преди началото или след края на съществуващия диапазон от ключове на индекса.

Все още е възможно за добавяне на нови страници в рамките на съществуващия диапазон от ключове на индекса, но новите редове трябва да сортират по-високо от най-високия ключ на предходния съществуваща индексна страница и сортирайте по-ниско от най-ниския ключ на следния съществуваща индексна страница. За най-добрия шанс за постигане на минимална сеч при тези обстоятелства се уверете, че вмъкнатите редове не се припокриват със съществуващите, доколкото е възможно.

Условия за DMLRequestSort

Не забравяйте, че FastLoadContext може да се активира само ако DMLRequestSort е зададен на true за съответния Индекс вмъкване оператор в плана за изпълнение.

Има два основни кодови пътя, които могат да задават DMLRequestSort до вярно за индексни вложки. И двата пътя връщане на true е достатъчно.

1. FOptimizeInsert

sqllang!CUpdUtil::FOptimizeInsert кодът изисква:

  • Повече от 250 реда приблизително да се вмъкне; и
  • Повече от 2 страници приблизително вмъкване на размер на данните; и
  • целевият индекс трябва да има по-малко от 3 листни страници .

Тези условия са същите като RowsetBulk на празен клъстериран индекс, с допълнително изискване за не повече от две страници на ниво лист на индекса. Забележете внимателно, че това се отнася до размера на съществуващия индекс преди вложката,не прогнозния размер на данните, които ще бъдат добавени.

Скриптът по-долу е модификация на демонстрацията, използвана в по-ранни части от тази серия. Показва минимално регистриране когато по-малко от три индексни страници са попълнени преди тестът INSERT...SELECT бяга. Схемата на тестовата таблица е такава, че 130 реда могат да се поберат на една страница от 8KB, когато версията на реда е изключена за базата данни. Множителят в първия TOP клаузата може да бъде променена, за да се определи броят на съществуващите индексни страници преди тестът INSERT...SELECT се изпълнява:

IF OBJECT_ID(N'dbo.Test', N'U') IS NOT NULL
BEGIN
    DROP TABLE dbo.Test;
END;
GO
CREATE TABLE dbo.Test 
(
    id integer NOT NULL IDENTITY
        CONSTRAINT [PK dbo.Test (id)]
        PRIMARY KEY,
    c1 integer NOT NULL,
    padding char(45) NOT NULL
        DEFAULT ''
);
GO
-- 130 rows per page for this table 
-- structure with row versioning off
INSERT dbo.Test
    (c1)
SELECT TOP (3 * 130)    -- Change the 3 here
    CHECKSUM(NEWID())
FROM master.dbo.spt_values AS SV;
GO
-- Show physical index statistics
-- to confirm the number of pages
SELECT
    DDIPS.index_type_desc,
    DDIPS.alloc_unit_type_desc,
    DDIPS.page_count,
    DDIPS.record_count,
    DDIPS.avg_record_size_in_bytes
FROM sys.dm_db_index_physical_stats
(
    DB_ID(), 
    OBJECT_ID(N'dbo.Test', N'U'), 
    1,      -- Index ID
    NULL,   -- Partition ID
    'DETAILED'
) AS DDIPS
WHERE
    DDIPS.index_level = 0;  -- leaf level only
GO
-- Clear the plan cache
DBCC FREEPROCCACHE;
GO
-- Clear the log
CHECKPOINT;
GO
-- Main test
INSERT dbo.Test
    (c1)
SELECT TOP (269)
    CHECKSUM(NEWID())
FROM master.dbo.spt_values AS SV;
GO
-- Show log entries
SELECT
    FD.Operation,
    FD.Context,
    FD.[Log Record Length],
    FD.[Log Reserve],
    FD.AllocUnitName,
    FD.[Transaction Name],
    FD.[Lock Information],
    FD.[Description]
FROM sys.fn_dblog(NULL, NULL) AS FD;
GO
-- Count the number of  fully-logged rows
SELECT 
    [Fully Logged Rows] = COUNT_BIG(*) 
FROM sys.fn_dblog(NULL, NULL) AS FD
WHERE 
    FD.Operation = N'LOP_INSERT_ROWS'
    AND FD.Context = N'LCX_CLUSTERED'
    AND FD.AllocUnitName = N'dbo.Test.PK dbo.Test (id)';
GO

Когато клъстерираният индекс е предварително зареден с 3 страници , тестовата вложка е напълно регистрирана (подробните записи в регистъра на транзакциите са пропуснати за краткост):

Когато таблицата е предварително заредена ссамо 1 или 2 страници , тестовата вложка е минимално регистрирана :

Когато таблицата не е предварително заредена с всякакви страници тестът е еквивалентен на стартиране на демонстрацията на празната клъстерирана таблица от част втора, но без TABLOCK намек:

Първите 130 реда са напълно регистрирани . Това е така, защото индексът беше празен преди да започнем и 130 реда се побират на първата страница. Не забравяйте, че първата страница винаги се регистрира напълно, когато FastLoadContext се използва и индексът е бил празен предварително. Останалите 139 реда се вмъкват сминимално регистриране .

Ако TABLOCK намек се добавя към вмъкването, всички страници са минимално регистрирани (включително първия), тъй като празното натоварване на клъстериран индекс вече отговаря на изискванията за RowsetBulk механизъм (с цената на вземане на Sch-M заключване).

2. FDemandRowsSortedForPerformance

Ако FOptimizeInsert тестовете се провалят, DMLRequestSort може все още да е настроен на true чрез втори набор от тестове в sqllang!CUpdUtil::FDemandRowsSortedForPerformance код. Тези условия са малко по-сложни, така че ще бъде полезно да дефинирате някои параметри:

  • P – брой съществуващи страници на ниво лист в целевия индекс .
  • Iприблизително брой редове за вмъкване.
  • R =P / I (целеви страници на вмъкнат ред).
  • T – брой целеви дялове (1 за неразделени).

Логиката за определяне на стойността на DMLRequestSort е след това:

  • Ако P <= 16 върнете false , в противен случай :
    • Ако R < 8 :
      • Ако P > 524 върнете true , в противен случай невярно .
    • Ако R >= 8 :
      • Ако T > 1 и I > 250 върнете true , в противен случай невярно .

Горните тестове се оценяват от процесора на заявки по време на компилацията на плана. Има еднокрайно условие оценява се от кода на машината за съхранение (IndexDataSetSession::WakeUpInternal ) в момента на изпълнение:

  • DMLRequestSort в момента е true; и
  • I >= 100 .

След това ще разбием цялата тази логика на управляеми части.

Повече от 16 съществуващи целеви страници

Първият тест P <= 16 означава, че индекси с по-малко от 17 съществуващи листови страници няма да отговарят на изискванията за FastLoadContext чрез този кодов път. За да бъдем абсолютно ясен по този въпрос, P е броят на страниците на ниво лист в целевия индекс преди INSERT...SELECT се изпълнява.

За да демонстрираме тази част от логиката, ще заредим предварително тестовата клъстерирана таблица с 16 страници на данни. Това има два важни ефекта (не забравяйте, че и двата кодови пътя трябва да връщат false за да се окаже false стойност за DMLRequestSort ):

  1. Той гарантира, че предишният FOptimizeInsert тестнеуспешен , тъй като третото условие не е изпълнено (P < 3 ).
  2. P <= 16 условие в FDemandRowsSortedForPerformance също щене бъдете изпълнени.

Затова очакваме FastLoadContext да не се активира. Модифицираният демо скрипт е:

IF OBJECT_ID(N'dbo.Test', N'U') IS NOT NULL
BEGIN
    DROP TABLE dbo.Test;
END;
GO
CREATE TABLE dbo.Test 
(
    id integer NOT NULL IDENTITY
        CONSTRAINT [PK dbo.Test (id)]
        PRIMARY KEY,
    c1 integer NOT NULL,
    padding char(45) NOT NULL
        DEFAULT ''
);
GO
-- 130 rows per page for this table 
-- structure with row versioning off
INSERT dbo.Test
    (c1)
SELECT TOP (16 * 130) -- 16 pages
    CHECKSUM(NEWID())
FROM master.dbo.spt_values AS SV;
GO
-- Show physical index statistics
-- to confirm the number of pages
SELECT
    DDIPS.index_type_desc,
    DDIPS.alloc_unit_type_desc,
    DDIPS.page_count,
    DDIPS.record_count,
    DDIPS.avg_record_size_in_bytes
FROM sys.dm_db_index_physical_stats
(
    DB_ID(), 
    OBJECT_ID(N'dbo.Test', N'U'), 
    1,      -- Index ID
    NULL,   -- Partition ID
    'DETAILED'
) AS DDIPS
WHERE
    DDIPS.index_level = 0;  -- leaf level only
GO
-- Clear the plan cache
DBCC FREEPROCCACHE;
GO
-- Clear the log
CHECKPOINT;
GO
-- Main test
INSERT dbo.Test
    (c1)
SELECT TOP (269)
    CHECKSUM(NEWID())
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2;
GO
-- Show log entries
SELECT
    FD.Operation,
    FD.Context,
    FD.[Log Record Length],
    FD.[Log Reserve],
    FD.AllocUnitName,
    FD.[Transaction Name],
    FD.[Lock Information],
    FD.[Description]
FROM sys.fn_dblog(NULL, NULL) AS FD;
GO
-- Count the number of  fully-logged rows
SELECT 
    [Fully Logged Rows] = COUNT_BIG(*) 
FROM sys.fn_dblog(NULL, NULL) AS FD
WHERE 
    FD.Operation = N'LOP_INSERT_ROWS'
    AND FD.Context = N'LCX_CLUSTERED'
    AND FD.AllocUnitName = N'dbo.Test.PK dbo.Test (id)';

Всички 269 реда са напълно регистрирани както беше предвидено:

Имайте предвид, че без значение колко високо сме задали броя на новите редове за вмъкване, скриптът по-горе никога произвеждат минимална сеч поради P <= 16 тест (и P < 3 тествайте в FOptimizeInsert ).

Ако решите да стартирате демонстрацията сами с по-голям брой редове, коментирайте секцията, която показва отделни записи в регистъра на транзакциите, в противен случай ще чакате много дълго време и SSMS може да се срине. (За да бъдем честни, това може да стане така или иначе, но защо да увеличавате риска.)

Страници на вмъкнат ред съотношение

Ако има 17 или повече листни страници в съществуващия индекс, предишният P <= 16 тестът няма да се провали. Следващият раздел на логиката се занимава с съотношението на съществуващите страници към нововмъкнати редове . Това също трябва да премине, за да се постигне минимално регистриране . Напомняме, съответните условия са:

  • Съотношение R =P / I .
  • Ако R < 8 :
    • Ако P > 524 върнете true , в противен случай невярно .

Трябва също да запомним окончателния тест на двигателя за съхранение за поне 100 реда:

  • I >= 100 .

Малко реорганизиране на тези условия, всичко от следните трябва да е вярно:

  1. P > 524 (съществуващи индексни страници)
  2. I >= 100 (приблизителни вмъкнати редове)
  3. P / I < 8 (съотношение R )

Има няколко начина да изпълните тези три условия едновременно. Нека изберем минималните възможни стойности за P (525) и I (100) дава R стойност на (525 / 100) =5,25. Това удовлетворява (R < 8 тест), така че очакваме тази комбинация да доведе до минимално регистриране :

IF OBJECT_ID(N'dbo.Test', N'U') IS NOT NULL
BEGIN
    DROP TABLE dbo.Test;
END;
GO
CREATE TABLE dbo.Test 
(
    id integer NOT NULL IDENTITY
        CONSTRAINT [PK dbo.Test (id)]
        PRIMARY KEY,
    c1 integer NOT NULL,
    padding char(45) NOT NULL
        DEFAULT ''
);
GO
-- 130 rows per page for this table 
-- structure with row versioning off
INSERT dbo.Test
    (c1)
SELECT TOP (525 * 130) -- 525 pages
    CHECKSUM(NEWID())
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2;
GO
-- Show physical index statistics
-- to confirm the number of pages
SELECT
    DDIPS.index_type_desc,
    DDIPS.alloc_unit_type_desc,
    DDIPS.page_count,
    DDIPS.record_count,
    DDIPS.avg_record_size_in_bytes
FROM sys.dm_db_index_physical_stats
(
    DB_ID(), 
    OBJECT_ID(N'dbo.Test', N'U'), 
    1,      -- Index ID
    NULL,   -- Partition ID
    'DETAILED'
) AS DDIPS
WHERE
    DDIPS.index_level = 0;  -- leaf level only
GO
-- Clear the plan cache
DBCC FREEPROCCACHE;
GO
-- Clear the log
CHECKPOINT;
GO
-- Main test
INSERT dbo.Test
    (c1)
SELECT TOP (100)
    CHECKSUM(NEWID())
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2;
GO
-- Show log entries
SELECT
    FD.Operation,
    FD.Context,
    FD.[Log Record Length],
    FD.[Log Reserve],
    FD.AllocUnitName,
    FD.[Transaction Name],
    FD.[Lock Information],
    FD.[Description]
FROM sys.fn_dblog(NULL, NULL) AS FD;
GO
-- Count the number of  fully-logged rows
SELECT 
    [Fully Logged Rows] = COUNT_BIG(*) 
FROM sys.fn_dblog(NULL, NULL) AS FD
WHERE 
    FD.Operation = N'LOP_INSERT_ROWS'
    AND FD.Context = N'LCX_CLUSTERED'
    AND FD.AllocUnitName = N'dbo.Test.PK dbo.Test (id)';

100-редовият INSERT...SELECT наистина е минимално регистриран :

Намаляване на приблизителната вмъкнати редове до 99 (прекъсване на I >= 100 ) и/или намаляване на броя на съществуващите индексни страници до 524 (нарушаване на P > 524 ) води до пълно регистриране . Бихме могли също да направим промени като R е не по-малко от 8, за да произведе пълно регистриране . Например, задаване на P = 1000 и I = 125 дава R = 8 , със следните резултати:

125-те вмъкнати реда бяха напълно регистрирани както се очаква. (Това не се дължи на пълната регистрация на първата страница, тъй като индексът не е бил празен предварително.)

Съотношение на страниците за разделени индекси

Ако всички предходни тестове се провалят, оставащият тест изисква R >= 8 и може само бъде удовлетворен, когато броят на дяловете (T ) е по-голямо от 1и има повече от 250 оценени вмъкнати редове (I ). Припомняне:

  • Ако R >= 8 :
    • Ако T > 1 и I > 250 върнете true , в противен случай невярно .

Една тънкост:За разделени индекси, правилото, което казва, че всички редове на първа страница са напълно регистрирани (за първоначално празен индекс), се прилага на дял . За обект с 15 000 дяла това означава 15 000 напълно регистрирани „първи“ страници.

Обобщение и заключителни мисли

Формулите и редът на оценка, описани в тялото, се основават на проверка на кода с помощта на дебъгер. Те бяха представени във форма, която отблизо представя времето и реда, използвани в реалния код.

Възможно е да се пренаредят и опрости малко тези условия, за да се създаде по-кратко обобщение на практическите изисквания за минимално сечване при вмъкване в b-дърво с помощта на INSERT...SELECT . Прецизираните изрази по-долу използват следните три параметъра:

  • P =брой съществуващи индексирайте страници на ниво лист.
  • I =приблизително брой редове за вмъкване.
  • S =приблизително вмъкнете размер на данни в 8KB страници.

Групово зареждане на набор от редове

  • Използва sqlmin!RowsetBulk .
  • Изисква празен цел за клъстериран индекс с TABLOCK (или еквивалент).
  • Изисква DMLRequestSort = true на Клъстеризирано вмъкване на индекс оператор.
  • DMLRequestSort е зададено true ако I > 250 и S > 2 .
  • Всички вмъкнати редове са минимално регистрирани .
  • Sch-M заключване предотвратява едновременния достъп до таблицата.

Контекст за бързо зареждане

  • Използва sqlmin!FastLoadContext .
  • Разрешава минимално регистриран вмъква в индекси на b-дърво:
    • Клъстерни или неклъстерирани.
    • С или без заключване на масата.
    • Целевият индекс е празен или не.
  • Изисква DMLRequestSort = true на свързаното Индексно вмъкване оператор на план.
  • Само редове, записани към чисто нови страници са групово заредени и минимално регистрирани .
  • Първата страница на по-рано празен индекс дял винаги е напълно регистриран .
  • Абсолютният минимум от I >= 100 .
  • Изисква флаг за проследяване 610 преди SQL Server 2016.
  • Налице по подразбиране от SQL Server 2016 (флаг за проследяване 692 деактивира).

DMLRequestSort е зададено true за:

  • Всякакъв индекс (разделен или не) ако:
    • I > 250 и P < 3 и S > 2; или
    • I >= 100 и P > 524 и P < I * 8

Само за разделени индекси (с> 1 дял), DMLRequestSort също е зададено true ако:

  • I > 250 и P > 16 и P >= I * 8

Има няколко интересни случая, произтичащи от тези FastLoadContext условия:

  • Всички вмъква в неразделен индекс смежду 3 и 524 (включително) съществуващите листни страници ще да бъдат напълно регистрирани независимо от броя и общия размер на добавените редове. Това най-забележимо ще засегне големи вмъквания към малки (но не празни) таблици.
  • Всички вмъква в разделен индекс с между3 и 16 съществуващите страници ще бъдат напълно регистрирани .
  • Големи вложки до големи неразделени индексите може да не са минимално регистрирани поради неравенството P < I * 8 . Когато P е голям, съответно голям оценен брой вмъкнати редове (I ) изисква се. Например, индекс с 8 милиона страници не може да поддържа минимално регистриране при вмъкване на 1 милион реда или по-малко.

Неклъстерирани индекси

Същите съображения и изчисления, приложени към клъстерирани индекси в демонстрациите, важат и за неклъстерирани b-tree индекси също, стига индексът да се поддържа от оператор на специален план (широко , или на индекс план). Неклъстерирани индекси, поддържани от оператор на основна таблица (напр. Вмъкване на клъстериран индекс ) не отговарят на условията за FastLoadContext .

Имайте предвид, че параметрите на формулата трябва да бъдат оценени наново за всеки неклъстер индексен оператор — изчислен размер на реда, брой съществуващи индексни страници и оценка на кардиналитета.

Общи бележки

Внимавайте за ниски оценки на кардиналите в Индекс вмъкване оператор, тъй като те ще засегнат I и S параметри. Ако прагът не бъде достигнат поради грешка в оценката на кардиналността, вмъкването ще бъде напълно регистрирано .

Не забравяйте, че DMLRequestSort секешира с плана — не се оценява при всяко изпълнение на повторно използван план. Това може да въведе форма на добре познатия проблем с чувствителността на параметрите (известен също като „смъркане на параметри“).

Стойността на P (индексни листни страници) не е обновена в началото на всяко изявление. Текущата реализация кешира стойността за цялата партида . Това може да има неочаквани странични ефекти. Например TRUNCATE TABLE в същата партида като INSERT...SELECT няма да нулира P до нула за изчисленията, описани в тази статия — те ще продължат да използват предварително съкратената стойност и прекомпилирането няма да помогне. Заобиколно решение е да подадете големи промени в отделни партиди.

Флагове за проследяване

Възможно е да се принуди FDemandRowsSortedForPerformance за да върнете true като зададете недокументирано и неподдържано флаг за проследяване 2332, както написах в Оптимизиране на T-SQL заявки, които променят данните. Когато TF 2332 е активен, броят на приблизителните редове за вмъкване все още трябва да е поне 100 . TF 2332 засяга минималното регистриране решение за FastLoadContext само (ефективен е за разделени купчини, доколкото DMLRequestSort е загрижен, но няма ефект върху самата купчина, тъй като FastLoadContext важи само за индекси).

широко/по индекс Формата на плана за поддръжка на неклъстериран индекс може да бъде принудена за таблици на хранилище на редове, като се използва флаг за проследяване 8790 (не е официално документиран, но е споменат в статия от базата знания, както и в моята статия, свързана за TF2332 точно по-горе).

Всичко от Сунил Агарвал от екипа на SQL Server:

  • Какви са оптимизациите за групово импортиране?
  • Оптимизации за групово импортиране (минимално регистриране)
  • Минимални промени в журнала в SQL Server 2008
  • Минимални промени в журнала в SQL Server 2008 (част-2)
  • Минимални промени в журнала в SQL Server 2008 (част-3)

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да сортирате в SQL

  2. SQL ALTER TABLE за начинаещи

  3. Как да създадете таблица с множество чужди ключове и да не се объркате

  4. Индексирани изгледи и статистика

  5. Проектиране на база данни за система за набиране на персонал