Вероятно сте чували много пъти преди, че SQL Server предоставя гаранция за свойствата на транзакциите на ACID. Тази статия се фокусира върху частта D, която разбира се означава издръжливост. По-конкретно, тази статия се фокусира върху аспект от архитектурата за регистриране на SQL Server, която налага трайността на транзакциите – изтриване на буфера на регистрационните файлове. Говоря за функцията, която обслужва регистрационният буфер, условията, които принуждават SQL Server да изчисти буфера на регистрационния файл на диск, какво можете да направите, за да оптимизирате производителността на транзакциите, както и наскоро добавените свързани технологии като отложена издръжливост и енергонезависима памет за класове за съхранение.
Прочистване на буфера на журнала
Частта D в свойствата на транзакцията на ACID означава трайност. На логическо ниво това означава, че когато приложение изпрати на SQL Server инструкция за извършване на транзакция (изрично или с транзакция за автоматично извършване), SQL Server обикновено връща контрола на повикващия само след като може да гарантира, че транзакцията е трайна. С други думи, след като обаждащият се върне контрола след извършване на транзакция, той може да се довери, че дори миг по-късно сървърът преживее прекъсване на захранването, промените в транзакцията са дошли в базата данни. Докато сървърът се рестартира успешно и файловете на базата данни не са повредени, ще откриете, че всички промени в транзакциите са приложени.
Начинът, по който SQL Server налага трайността на транзакциите, отчасти, е като гарантира, че всички промени на транзакцията се записват в регистъра на транзакциите на базата данни на диск преди да върне контрола на обаждащия се. В случай на прекъсване на захранването след потвърждение на извършване на транзакция, знаете, че всички тези промени са били поне записани в регистъра на транзакциите на диска. Това е така, дори ако свързаните страници с данни са били променени само в кеша на данните (буферния пул), но все още не са прехвърлени към файловете с данни на диска. Когато рестартирате SQL Server, по време на фазата на повторно изпълнение на процеса на възстановяване, SQL Server използва информацията, записана в дневника, за да възпроизведе промените, които са били приложени след последната контролна точка и които не са стигнали до файловете с данни. Има малко повече в историята в зависимост от модела за възстановяване, който използвате и от това дали груповите операции са били приложени след последната контролна точка, но за целите на нашата дискусия е достатъчно да се съсредоточим върху частта, която включва втвърдяване на промените в регистър на транзакциите.
Трудната част в архитектурата за регистриране на SQL Server е, че записите в регистрационни файлове са последователни. Ако SQL Server не беше използвал някакъв буфер на регистрационни файлове, за да облекчи записите на регистрационни файлове на диск, системите с интензивно записване – особено тези, които включват много малки транзакции – бързо биха се сблъскали с ужасни затруднения в производителността, свързани с записването на регистрационни файлове.
За да облекчи отрицателното въздействие върху производителността от честите последователни записи на регистрационни файлове на диск, SQL Server използва буфер на регистрационни файлове в паметта. Записванията на регистрационни файлове първо се извършват в буфера на регистрационните файлове и определени условия карат SQL Server да изчисти или втвърди буфера на дневника към диска. Закалената единица (известна още като лог блок) може да варира от минимален размер на сектор (512 байта) до максимум 60 KB. Следват условия, които задействат изчистване на буфера на журнала (засега игнорирайте частите, които се показват в квадратни скоби):
- SQL Server получава заявка за извършване на [напълно издръжлива] транзакция, която променя данни [в база данни, различна от tempdb]
- Буферът на журнала се запълва, достигайки капацитета си от 60 KB
- SQL Server трябва да втвърди мръсни страници с данни, например по време на процес на контролна точка, а регистрационните записи, представящи промените в тези страници, все още не са затвърдени (записване напред , или накратко WAL)
- Ръчно заявявате изтриване на буфера на регистрационния файл, като изпълнявате процедурата sys.sp_flush_log
- SQL Server записва нова стойност за възстановяване, свързана с кеш на последователността [в база данни, различна от tempdb]
Първите четири условия трябва да са доста ясни, ако засега пренебрегнете информацията в квадратни скоби. Последното може би все още не е ясно, но ще го обясня подробно по-късно в статията.
Времето, в което SQL Server изчаква за завършване на I/O операция, обработваща изчистването на буфера на журнала, се отразява от типа на изчакване WRITELOG.
И така, защо тази информация е толкова интересна и какво да правим с нея? Разбирането на условията, които задействат изтриване на буфера на журнала, може да ви помогне да разберете защо определени работни натоварвания изпитват свързани тесни места. Освен това, в някои случаи има действия, които можете да предприемете, за да намалите или премахнете такива тесни места. Ще покрия редица примери като една голяма транзакция срещу много малки транзакции, напълно издръжливи срещу забавени трайни транзакции, потребителска база данни срещу tempdb и кеширане на последователни обекти.
Една голяма транзакция срещу много малки транзакции
Както беше споменато, едно от условията, което задейства изтриване на буфера на журнала, е когато извършите транзакция, за да гарантирате трайността на транзакцията. Това означава, че работните натоварвания, които включват много малки транзакции, като OLTP натоварвания, могат потенциално да изпитат затруднения, свързани с записването в регистрационни файлове.
Въпреки че това често не е така, ако имате една сесия, изпращаща много малки промени, прост и ефективен начин за оптимизиране на работата е да приложите промените в една голяма транзакция вместо в множество малки.
Помислете за следния опростен пример (изтеглете PerformanceV3 тук):
ЗАДАДЕТЕ NOCOUNT ON; ИЗПОЛЗВАЙТЕ PerformanceV3; ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Деактивирано; -- по подразбиране DROP TABLE, АКО СЪЩЕСТВУВА dbo.T1; СЪЗДАВАНЕ НА ТАБЛИЦА dbo.T1(col1 INT NOT NULL); ДЕКЛАРИРАНЕ @i КАТО INT =1; ДОКАТО @i <=1000000ЗАПОЧВА ЗАПОЧВА TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN; SET @i +=1;КРАЙ;
Този код изпълнява 1 000 000 малки транзакции, които променят данните в потребителска база данни. Тази работа ще задейства най-малко 1 000 000 изтривания на буфер на журнала. Можете да получите няколко допълнителни поради запълване на буфера на журнала. Можете да използвате следния тестов шаблон, за да преброите броя на изтриванията на буфера на журнала и да измерите времето, необходимо за завършване на работата:
-- Тестов шаблон -- ... Подготовката е тук ... -- Пребройте изтриванията на дневника и измервайте timeDECLARE @logflushes КАТО INT, @starttime КАТО DATETIME2, @duration КАТО INT; -- Статистика предиSET @logflushes =( ИЗБЕРЕТЕ cntr_value ОТ sys.dm_os_performance_counters WHERE counter_name ='Log Flushes/sec' И instance_name =@db ); SET @starttime =SYSDATETIME(); -- ... Действителната работа отива тук ... -- Статистика следSET @duration =DATEDIFF(втора, @starttime, SYSDATETIME());SET @logflushes =( ИЗБЕРЕТЕ cntr_value ОТ sys.dm_os_performance_counters WHERE counter_name ='Изтриване на дневник/сек ' И instance_name =@db ) - @logflushes; ИЗБЕРЕТЕ @duration AS durationinseconds, @logflushes AS logflushes;
Въпреки че името на брояча на производителността е Log Flushes/sec, той всъщност продължава да натрупва броя на изтриванията на буфера на журнала досега. И така, кодът изважда броя преди работа от броя след работа, за да изчисли броя на дневниците, генерирани от работата. Този код също така измерва времето в секунди, необходимо за завършване на работата. Въпреки че не правя това тук, вие бихте могли, ако искате, по подобен начин да разберете броя на регистрационните записи и размера, записани в дневника от работата, като запитате състоянията преди работа и след работа на fn_dblog функция.
За нашия пример по-горе, по-долу е частта, която трябва да поставите в раздела за подготовка на тестовия шаблон:
-- PreparationSET NOCOUNT ON;ИЗПОЛЗВАЙТЕ PerformanceV3; ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Деактивирано; ИЗПУСКАНЕ НА ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T1; СЪЗДАВАНЕ НА ТАБЛИЦА dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'PerformanceV3'; ДЕКЛАРИРАЙТЕ @logflushes КАТО INT, @starttime КАТО DATETIME2, @duration КАТО INT;
Следва частта, която трябва да поставите в действителната работна секция:
-- Действителна работаDECLARE @i AS INT =1; ДОКАТО @i <=1000000ЗАПОЧВА ЗАПОЧВА TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN; SET @i +=1;КРАЙ;
Като цяло получавате следния код:
-- Примерен тест с много малки, напълно издръжливи транзакции в потребителска база данни-- ... Подготовката е тук... -- PreparationSET NOCOUNT ON;USE PerformanceV3; ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Деактивирано; ИЗПУСКАНЕ НА ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T1; СЪЗДАВАНЕ НА ТАБЛИЦА dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'PerformanceV3'; ДЕКЛАРИРАНЕ @logflushes КАТО INT, @starttime КАТО DATETIME2, @duration КАТО INT; -- Статистика предиSET @logflushes =( ИЗБЕРЕТЕ cntr_value ОТ sys.dm_os_performance_counters WHERE counter_name ='Log Flushes/sec' И instance_name =@db ); SET @starttime =SYSDATETIME(); -- ... Действителната работа отива тук ... -- Действителна работаDECLARE @i AS INT =1; ДОКАТО @i <=1000000ЗАПОЧВА ЗАПОЧВА TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN; SET @i +=1;КРАЙ; -- Статистика следSET @duration =DATEDIFF(втора, @starttime, SYSDATETIME()); SET @logflushes =( ИЗБЕРЕТЕ cntr_value ОТ sys.dm_os_performance_counters WHERE counter_name ='Log Flushes/sec' И instance_name =@db ) - @logflushes; ИЗБЕРЕТЕ @duration AS durationinseconds, @logflushes AS logflushes;
Завършването на този код отне 193 секунди в моята система и задейства 1 000 036 изтривания на буфер на дневник. Това е много бавно, но може да се обясни с големия брой изтривания на регистрационни файлове.
В типичните OLTP работни натоварвания различните сесии подават малки промени в различни малки транзакции едновременно, така че не е като наистина да имате възможност да капсулирате много малки промени в една голяма транзакция. Въпреки това, ако вашата ситуация е, че всички малки промени се изпращат от една и съща сесия, лесният начин за оптимизиране на работата е да я капсулирате в една транзакция. Това ще ви даде две основни предимства. Едното е, че работата ви ще записва по-малко записи в дневника. С 1 000 000 малки транзакции всяка транзакция всъщност записва три регистрационни записа:един за започване на транзакцията, един за промяната и един за извършване на транзакцията. И така, разглеждате около 3 000 0000 записа в регистъра на транзакциите срещу малко над 1 000 000, когато се изпълняват като една голяма транзакция. Но по-важното е, че с една голяма транзакция повечето изтривания на регистрационните файлове се задействат само когато буферът на дневника се запълни, плюс още едно изтриване на регистрационни файлове в самия край на транзакцията, когато се извършва. Разликата в производителността може да бъде доста значителна. За да тествате работата в една голяма транзакция, използвайте следния код в действителната работна част на тестовия шаблон:
-- Действителна работаBEGIN TRAN; ДЕКЛАРИРАНЕ @i КАТО INT =1; ДОКАТО @i <=1000000ЗАПОЧВАТЕ ВМЕСТЕ ВЪВ dbo.T1(col1) STONYS(@i); SET @i +=1; КРАЙ; COMMIT TRAN;
В моята система тази работа завърши за 7 секунди и задейства 1758 изтривания на лог. Ето сравнение между двете опции:
#transactions log flushes продължителност в секунди-------------- ------------ -------------- ------1000000 1000036 1931 1758 7
Но отново, при типичните OLTP натоварвания всъщност нямате възможност да замените много малки транзакции, изпратени от различни сесии, с една голяма транзакция, изпратена от същата сесия.
Напълно издръжливи срещу забавени трайни транзакции
Започвайки със SQL Server 2014, можете да използвате функция, наречена отложена издръжливост, която ви позволява да подобрите производителността на работните натоварвания с много малки транзакции, дори ако са изпратени от различни сесии, като жертвате нормалната пълна гаранция за издръжливост. Когато извършва забавена трайна транзакция, SQL Server потвърждава записването веднага щом записът на регистрационния запис за записване се запише в буфера на дневника, без да задейства изтриване на буфера на журнала. Буферът на регистрационния файл се изтрива поради някое от другите гореспоменати условия, като например когато се запълни, но не и когато се извърши забавена трайна транзакция.
Преди да използвате тази функция, трябва много внимателно да помислите дали е подходяща за вас. По отношение на производителността въздействието му е значително само при натоварвания с много малки транзакции. Ако в началото работното ви натоварване включва предимно големи транзакции, вероятно няма да видите никакво предимство в производителността. По-важното е, че трябва да осъзнаете потенциала за загуба на данни. Да речем, че приложението извършва забавена трайна транзакция. Записът за комит се записва в буфера на дневника и незабавно се потвърждава (контролът се връща на повикващия). Ако SQL Server претърпи прекъсване на захранването, преди буферът на журнала да бъде прочистен, след рестартиране процесът на възстановяване отменя всички промени, направени от транзакцията, въпреки че приложението смята, че е заето.
И така, кога е добре да използвате тази функция? Един очевиден случай е, когато загубата на данни не е проблем, като този пример от Мелиса Конърс от SentryOne. Друго е, когато след рестартиране имате средствата да идентифицирате кои промени не са попаднали в базата данни и сте в състояние да ги възпроизведете. Ако ситуацията ви не попада в една от тези две категории, не използвайте тази функция въпреки изкушението.
За да работите със забавени трайни транзакции, трябва да зададете опция за база данни, наречена DELAYED_DURABILITY. Тази опция може да бъде зададена на една от трите стойности:
- Деактивирано (по подразбиране):всички транзакции в базата данни са напълно издръжливи и следователно всеки комит задейства изчистване на буфера на журнала
- Принудително :всички транзакции в базата данни са забавени и трайни и следователно ангажиментите не задействат изтриване на буфера на журнала
- Разрешено :освен ако не е упоменато друго, транзакциите са напълно издръжливи и извършването им задейства изчистване на буфера на журнала; ако обаче използвате опцията DELAYED_DURABILITY =ON или в израз COMMIT TRAN, или в атомен блок (от нативно компилиран процес), тази конкретна транзакция е отложена и трайна и следователно нейното ангажимент не задейства изчистване на буфера на журнала
Като тест използвайте следния код в раздела за подготовка на нашия тестов шаблон (забележете, че опцията за база данни е настроена на Forced):
-- PreparationSET NOCOUNT ON;ИЗПОЛЗВАЙТЕ PerformanceV3; -- http://tsql.solidq.com/SampleDatabases/PerformanceV3.zip ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Принудително; ИЗПУСКАНЕ НА ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T1; СЪЗДАВАНЕ НА ТАБЛИЦА dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'PerformanceV3';
И използвайте следния код в действителната секция за работа (забележете, 1 000 000 малки транзакции):
-- Действителна работаDECLARE @i AS INT =1; ДОКАТО @i <=1000000ЗАПОЧВА ЗАПОЧВА TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN; SET @i +=1;КРАЙ;
Като алтернатива можете да използвате разрешен режим на ниво база данни и след това в командата COMMIT TRAN да добавите WITH (DELAYED_DURABILITY =ON).
В моята система работата отне 22 секунди, за да завърши и задейства 95 407 изтривания на дневници. Това е по-дълго от изпълнението на работата като една голяма транзакция (7 секунди), тъй като се генерират повече записи в дневника (запомнете, за всяка транзакция, един за започване на транзакцията, един за промяната и един за извършване на транзакцията); обаче е много по-бързо от 193-те секунди, които отне работата, за да се завърши с 1 000 000 напълно издръжливи транзакции, тъй като броят на изтриванията на регистрационни файлове спадна от над 1 000 000 на по-малко от 100 000. Освен това, с отложена издръжливост, ще получите печалба от производителността, дори ако транзакциите са изпратени от различни сесии, където не е опция да се използва една голяма транзакция.
За да демонстрирате, че няма полза от използването на забавена издръжливост, когато вършите работата като големи транзакции, запазете същия код в подготвителната част на последния тест и използвайте следния код в действителната работна част:
-- Действителна работаBEGIN TRAN; ДЕКЛАРИРАНЕ @i КАТО INT =1; ДОКАТО @i <=1000000ЗАПОЧВАТЕ ВМЕСТЕ ВЪВ dbo.T1(col1) STONYS(@i); SET @i +=1;КРАЙ; COMMIT TRAN;
Получих 8 секунди време за изпълнение (в сравнение със 7 за една голяма напълно издръжлива транзакция) и 1759 дневни изтривания (в сравнение с 1758). Цифрите по същество са едни и същи, но при забавената трайна транзакция има риск от загуба на данни.
Ето обобщение на числата за ефективност и за четирите теста:
durability #transactions log flushes продължителност в секунди ------------- -------------- ------ ------ --------------------пълен 1000000 1000036 193пълен 1 1758 7забавен 1000000 95407 22забавен 1 1759 8
Памет за класове за съхранение
Функцията за отложена издръжливост може значително да подобри производителността на работните натоварвания в стил OLTP, които включват голям брой малки транзакции за актуализиране, които изискват висока честота и ниска латентност. Проблемът е, че рискувате да загубите данни. Ами ако не можете да допуснете загуба на данни, но все пак искате повишаване на производителността, подобно на забавена издръжливост, при което буферът на журнала не се измива за всеки комит, а когато се запълни? Всички обичаме да ядем тортата и да я ядем, нали?
Можете да постигнете това в SQL Server 2016 SP1 или по-нова версия, като използвате памет за класове за съхранение, известна още като NVDIMM-N енергонезависима памет. Този хардуер по същество е модул памет, който ви дава производителност от ниво на памет, но информацията там се запазва и следователно не се губи, когато захранването изчезне. Добавката в SQL Server 2016 SP1 ви позволява да конфигурирате буфера на журнала като постоянен на такъв хардуер. За да направите това, настройвате SCM като том в Windows и го форматирате като том в режим на директен достъп (DAX). След това добавяте регистрационен файл към базата данни, като използвате нормалната команда ALTER DATABASE
За повече подробности относно тази функция, включително номера на производителността, вижте Ускоряване на латентността при извършване на транзакции с помощта на памет на клас за съхранение в Windows Server 2016/SQL Server 2016 SP1 от Кевин Фарли.
Любопитното е, че SQL Server 2019 подобрява поддръжката за памет на клас за съхранение отвъд само сценария за постоянен кеш на регистрационни файлове. Той поддържа поставяне на файлове с данни, регистрационни файлове и OLTP файлове за контролни точки в паметта на такъв хардуер. Всичко, което трябва да направите, е да го изложите като том на ниво OS и да форматирате като DAX устройство. SQL Server 2019 автоматично разпознава тази технология и работи в просветен режим, директен достъп до устройството, заобикаляйки стека за съхранение на операционната система. Добре дошли в бъдещето!
Потребителска база данни срещу tempdb
Базата данни tempdb, разбира се, се създава от нулата като ново копие на моделната база данни всеки път, когато рестартирате SQL Server. Като такъв, никога няма нужда да възстановявате каквито и да е данни, които записвате в tempdb, независимо дали ги записвате във временни таблици, променливи на таблици или потребителски таблици. Всичко е изчезнало след рестартиране. Знаейки това, SQL Server може да облекчи много от изискванията, свързани с регистрирането. Например, независимо от това дали активирате опцията за отложена издръжливост или не, събитията на commit не задействат изтриване на буфера на дневника. Освен това количеството информация, която трябва да бъде регистрирана, е намалена, тъй като SQL Server се нуждае само от достатъчно информация, за да поддържа връщане назад на транзакции или отмяна на работа, ако е необходимо, но не и преместване на транзакции напред или повторно извършване на работа. В резултат на това записите в регистъра на транзакциите, представляващи промени в обект в tempdb, обикновено са по-малки в сравнение с случаите, когато същата промяна се прилага към обект в потребителска база данни.
За да демонстрирате това, ще изпълните същите тестове, които стартирахте по-рано в PerformanceV3, само този път в tempdb. Ще започнем с теста на много малки транзакции, когато опцията за база данни DELAYED_DURABILITY е зададена на Disabled (по подразбиране). Използвайте следния код в раздела за подготовка на шаблона за тест:
-- PreparationSET NOCOUNT ON;ИЗПОЛЗВАЙТЕ tempdb; ALTER DATABASE tempdb SET DELAYED_DURABILITY =Деактивирано; ИЗПУСКАНЕ НА ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T1; СЪЗДАВАНЕ НА ТАБЛИЦА dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'tempdb';
Използвайте следния код в действителната работна секция:
-- Действителна работаDECLARE @i AS INT =1; ДОКАТО @i <=1000000ЗАПОЧВА ЗАПОЧВА TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN; SET @i +=1;КРАЙ;
Тази работа генерира 5095 изтривания на дневници и отне 19 секунди за завършване. Това е в сравнение с над милион изтривания на регистрационни файлове и 193 секунди в потребителска база данни с пълна издръжливост. Това е дори по-добре, отколкото при забавена издръжливост в потребителска база данни (95 407 изтривания на регистрационни файлове и 22 секунди) поради намаления размер на регистрационните записи.
За да тествате една голяма транзакция, оставете раздела за подготовка непроменен и използвайте следния код в действителната работна секция:
-- Действителна работаBEGIN TRAN; ДЕКЛАРИРАНЕ @i КАТО INT =1; ДОКАТО @i <=1000000ЗАПОЧВАТЕ ВМЕСТЕ ВЪВ dbo.T1(col1) STONYS(@i); SET @i +=1;КРАЙ; COMMIT TRAN;
Получих 1228 дневни изтривания и 9 секунди време за изпълнение. Това е в сравнение с 1758 изтривания на регистрационни файлове и 7 секунди време за изпълнение в потребителската база данни. Времето за изпълнение е подобно, дори малко по-бързо в потребителската база данни, но може да има малки вариации между тестовете. Размерите на регистрационните записи в tempdb са намалени и следователно получавате по-малко изтривания на регистрационни файлове в сравнение с потребителската база данни.
Можете също да опитате да стартирате тестовете с опцията DELAYED_DURABILITY, зададена на Forced, но това няма да окаже влияние върху tempdb, тъй като, както беше споменато, събитията за комит така или иначе не задействат изтриване на регистрационни файлове в tempdb.
Ето мерките за производителност за всички тестове, както в потребителската база данни, така и в tempdb:
издръжливост на базата данни #transactions log flushes duration in seconds-------------- ------------------- ----- --------- ------------ --------------------PerformanceV3 пълен 1000000 1000036 193PerformanceV3 пълен 1 1758 7PerformanceV3 забавено 1000000 95407 22PerformanceV3 забавено 1 1759 8tempdb пълен 1000000 5095 19tempdb пълен 1 1228 9tempdb забавено 1000000 5091 18tempdb закъснение 2 предварителна 12Кеширане на обекти на последователност
Може би изненадващ случай, който задейства изтриване на буфера на журнала, е свързан с опцията за кеширане на обекта на последователността. Разгледайте като пример следната дефиниция на последователността:
СЪЗДАВАНЕ НА ПОСЛЕДОВАТЕЛНОСТ dbo.Seq1 КАТО ГОЛЯМА МИНИМАЛНА СТОЙНОСТ 1 КЕШ 50; -- размерът на кеша по подразбиране е 50;Всеки път, когато имате нужда от нова стойност на последователността, използвате функцията NEXT VALUE FOR, както следва:
ИЗБЕРЕТЕ СЛЕДВАЩА СТОЙНОСТ ЗА dbo.Seq1;Свойството CACHE е функция за производителност. Без него всеки път, когато бъде поискана нова стойност на последователността, SQL Server би трябвало да запише текущата стойност на диск за целите на възстановяване. Всъщност това е поведението, което получавате, когато използвате режима БЕЗ КЕШ. Вместо това, когато опцията е зададена на стойност, по-голяма от нула, SQL Server записва стойност за възстановяване на диска само веднъж на всеки брой заявки с размер на кеша. SQL Server поддържа два члена в паметта, оразмерени като типа на последователността, единият съдържа текущата стойност, а единият съдържа броя на стойностите, останали преди следващото записване на стойността за възстановяване на диска. В случай на прекъсване на захранването, при рестартиране SQL Server задава текущата стойност на последователността на стойността за възстановяване.
Това вероятно е много по-лесно да се обясни с пример. Помислете за горната дефиниция на последователността с опцията CACHE, зададена на 50 (по подразбиране). Заявявате нова стойност на последователността за първи път, като изпълнявате горния оператор SELECT. SQL Server задава гореспоменатите членове на следните стойности:
Стойност за възстановяване на диск:50, текуща стойност в паметта:1, оставени стойности в паметта:49, получавате:1Още 49 заявки няма да докоснат диска, а само да актуализират членовете на паметта. След общо 50 заявки, членовете са настроени на следните стойности:
Стойност за възстановяване на диск:50, текуща стойност в паметта:50, оставени стойности в паметта:0, получавате:50Направете друга заявка за нова стойност на последователността и това задейства запис на диск на стойността за възстановяване 100. След това членовете се задават на следните стойности:
Стойност за възстановяване на диск:100, текуща стойност в паметта:51, оставени стойности в паметта:49, получавате:51Ако в този момент системата претърпи прекъсване на захранването, след рестартиране, текущата стойност на последователността се задава на 100 (стойността, възстановена от диска). Следващата заявка за стойност на последователност произвежда 101 (записване на стойността за възстановяване 150 на диска). Загубихте всички стойности в диапазона от 52 до 100. Най-много стойности, които можете да загубите поради нечисто прекратяване на процеса на SQL Server, като в случай на прекъсване на захранването, е толкова стойности, колкото е размерът на кеша. Компромисът е ясен; колкото по-голям е размерът на кеша, толкова по-малко дискът записва стойността за възстановяване и следователно толкова по-добра е производителността. В същото време, толкова по-голяма е разликата, която може да се генерира между две стойности на последователност в случай на прекъсване на захранването.
Всичко това е доста просто и може би сте много добре запознати с това как работи. Това, което може да бъде изненадващо, е, че всеки път, когато SQL Server записва нова стойност за възстановяване на диск (на всеки 50 заявки в нашия пример), той също така втвърдява буфера на журнала. Това не е така със свойството на колоната за идентичност, въпреки че SQL Server вътрешно използва същата функция за кеширане за идентичност, както прави за обекта на последователността, просто не ви позволява да контролирате неговия размер. Включено е по подразбиране с размер 10000 за BIGINT и NUMERIC, 1000 за INT, 100 за SMALLINT и 10 за TINYINT. Ако желаете, можете да го изключите с флаг за проследяване 272 или опцията за конфигурация с обхват на IDENTITY_CACHE (2017+). Причината, поради която SQL Server не трябва да изтрива буфера на журнала, когато записва свързаната с кеша на идентичност стойност за възстановяване на диск е, че нова стойност на самоличността може да бъде създадена само при вмъкване на ред в таблица. В случай на прекъсване на захранването, ред, вмъкнат в таблица от транзакция, която не е записала, ще бъде изтеглен от таблицата като част от процеса на възстановяване на базата данни, когато системата се рестартира. Така че, дори ако след рестартирането SQL Server генерира същата стойност на идентичност като тази, създадена в транзакцията, която не е ангажирана, няма шанс за дубликати, тъй като редът е изтеглен от таблицата. Ако транзакцията беше ангажирана, това би задействало изтриване на регистрационни файлове, което също би продължило записването на свързана с кеша стойност за възстановяване. Следователно Microsoft не се чувстваше принудена да изтрива буфера на журнала всеки път, когато се осъществи свързано с кеша за самоличност дисково записване на стойността за възстановяване.
С обекта последователност ситуацията е различна. Едно приложение може да поиска нова стойност на последователността и да не я съхранява в базата данни. В случай на прекъсване на захранването след създаването на нова стойност на последователността в транзакция, която не е ангажирана, след рестартиране няма начин SQL Server да каже на приложението да не разчита на тази стойност. Следователно, за да избегне създаването на нова стойност на последователност след рестартиране, която е равна на предварително генерирана стойност на последователност, SQL Server принудително изтрива регистрационния файл всеки път, когато нова стойност за възстановяване, свързана с кеш на последователността, бъде записана на диска. Едно изключение от това правило е, когато обектът на последователността е създаден в tempdb, разбира се, няма нужда от такива изтривания на регистрационни файлове, тъй като така или иначе след рестартиране на системата tempdb се създава наново.
Отрицателното въздействие върху производителността на честите изтривания на регистрационни файлове е особено забележимо при използване на много малък размер на кеша на последователността и при една транзакция генериране на много стойности на последователността, например при вмъкване на много редове в таблица. Без последователността, такава транзакция най-вече би втвърдила буфера на регистрационния файл, когато се запълни, плюс още веднъж, когато транзакцията се ангажира. Но с последователността получавате изчистване на журнал всеки път, когато се извърши запис на диск на стойност за възстановяване. Ето защо искате да избегнете използването на малък размер на кеша — да не говорим за режим БЕЗ КЕШ.
За да демонстрирате това, използвайте следния код в раздела за подготовка на нашия тестов шаблон:
-- PreparationSET NOCOUNT ON;ИЗПОЛЗВАЙТЕ PerformanceV3; -- опитайте PerformanceV3, tempdb ПРОМЕНИ БАЗА ДАННИ PerformanceV3 -- опитайте PerformanceV3, tempdb SET DELAYED_DURABILITY =Деактивирано; -- опитайте Disabled, Forced DROP TABLE IF EXISTS dbo.T1; ИЗПУСКАНЕ НА ПОСЛЕДОВАТЕЛНОСТТА, АКО СЪЩЕСТВУВА dbo.Seq1; СЪЗДАВАНЕ НА ПОСЛЕДОВАТЕЛНОСТ dbo.Seq1 КАТО ГОЛЯМА МИНУАЛНА СТОЙНОСТ 1 КЕШ 50; -- опитайте NO CACHE, CACHE 50, CACHE 10000 DECLARE @db AS sysname =N'PerformanceV3'; -- опитайте PerformanceV3, tempdbИ следния код в действителната работна секция:
-- Действителна работаSELECT -- n -- за тестване без seq СЛЕДВАЩА СТОЙНОСТ ЗА dbo.Seq1 AS n -- за тестване на последователностINTO dbo.T1FROM PerformanceV3.dbo.GetNums(1, 1000000) AS N;Този код използва една транзакция, за да запише 1 000 000 реда в таблица с помощта на оператора SELECT INTO, генерирайки толкова стойности на последователността, колкото е броят на вмъкнатите редове.
Както е указано в коментарите, изпълнете теста с NO CACHE, CACHE 50 и CACHE 10000, както в PerformanceV3, така и в tempdb, и опитайте както напълно трайни транзакции, така и отложени трайни такива.
Ето номерата за ефективност, които получих на моята система:
трайност на базата данни Продължителност на изчистването на кеша на дневника в секунди-------------- ------------------- ------ --- ------------ --------------------PerformanceV3 пълен БЕЗ КЕШ 1000047 171PerformanceV3 пълен 50 20008 4PerformanceV3 пълен 10000 339 <1tempdb full NO CACHE 96 4tempdb full 50 74 1tempdb full 10000 8 <1PerformanceV3 delayed NO CACHE 1000045 166PerformanceV3 delayed 50 20011 4PerformanceV3 delayed 10000 334 <1tempdb delayed NO CACHE 91 4tempdb delayed 50 74 1tempdb delayed 10000 8 <1Има доста интересни неща, които трябва да забележите.
С БЕЗ КЕШ вие получавате изчистване на дневника за всяка генерирана стойност на последователността. Затова силно се препоръчва да го избягвате.
С малък размер на кеша на последователността все още получавате много изтривания на регистрационни файлове. Може би ситуацията не е толкова лоша, колкото при БЕЗ КЕШ, но имайте предвид, че натоварването отне 4 секунди за завършване с размер на кеша по подразбиране от 50 в сравнение с по-малко от секунда с размер 10 000. Аз лично използвам 10 000 като предпочитана стойност.
In tempdb you don’t get log flushes when a sequence cache-related recovery value is written to disk, but the recovery value is still written to disk every cache-sized number of requests. That’s perhaps surprising since such a value would never need to be recovered. Therefore, even when using a sequence object in tempdb, I’d still recommend using a large cache size.
Also notice that delayed durability doesn’t prevent the need for log flushes every time the sequence cache-related recovery value is written to disk.
Заключение
This article focused on log buffer flushes. Understanding this aspect of SQL Server’s logging architecture is important especially in order to be able to optimize OLTP-style workloads that require high frequency and low latency. Workloads using In-Memory OLTP included, of course. You have more options with newer features like delayed durability and persisted log buffer with storage class memory. Make sure you’re very careful with the former, though, since it does incur potential for data loss unlike the latter.
Be careful not to use the sequence object with a small cache size, not to speak of the NO CACHE mode. I find the default size 50 too small and prefer to use 10,000. I’ve heard people expressing concerns that with a cache size 10000, after multiple power failures they might lose all the values in the type. However, even with a four-byte INT type, using only the positive range, 10,000 fits 214,748 times. If your system experience that many power failures, you have a completely different problem to worry about. Therefore, I feel very comfortable with a cache size of 10,000.