В света на SQL Server има постоянен мит, че и двете DROP TABLE
и TRUNCATE TABLE
командите не са регистрирани.
Те не са. И двете са напълно регистрирани, но ефективно регистрирани.
Можете лесно да докажете това на себе си. Изпълнете следния код, за да настроите тестова база данни и таблица и покажете, че имаме 10 000 реда в нашата таблица:
CREATE DATABASE [TruncateTest]; GO ALTER DATABASE [TruncateTest] SET RECOVERY SIMPLE; GO USE [TruncateTest]; GO CREATE TABLE [TestTable] ( [c1] INT IDENTITY, [c2] CHAR (8000) DEFAULT 'a'); GO SET NOCOUNT ON; GO INSERT INTO [TestTable] DEFAULT VALUES; GO 10000 SELECT COUNT (*) AS N'RowCount' FROM [TestTable]; GO
Резултати:
Брой редове———–
10000
И след това следния код, който съкращава таблицата в транзакция и проверява броя на редовете:
BEGIN TRAN; GO TRUNCATE TABLE [TestTable]; GO SELECT COUNT (*) AS N'RowCount' FROM [TestTable]; GO
Резултати:
Брой редове———–
0
Сега масата е празна. Но мога да върна транзакцията и да върна всички данни обратно:
ROLLBACK TRAN; GO SELECT COUNT (*) AS N'RowCount' FROM [TestTable]; GO
Резултати:
Брой редове———–
10000
Очевидно TRUNCATE
операцията трябва да бъде регистрирана, в противен случай операцията за връщане назад няма да работи.
И така, откъде идва погрешното схващане?
Идва от поведението на DROP
и TRUNCATE
операции на големи маси. Те ще завършат почти мигновено и ако погледнете в регистъра на транзакциите с помощта на fn_dblog
веднага след това ще видите само малък брой регистрационни записи, генерирани от операцията. Това малко число не корелира с размера на таблицата, която се съкращава или изпуска, така че изглежда сякаш DROP
и TRUNCATE
операциите не са регистрирани.
Но те са напълно регистрирани, както демонстрирах по-горе. И така, къде са регистрационните записи за операциите?
Отговорът е, че регистрационните записи ще бъдат създадени, но не веднага, чрез механизъм, наречен „отложено изпускане“, който беше добавен в SQL Server 2000 SP3.
Когато дадена таблица бъде изпусната или съкратена, всички страници с файлове с данни, разпределени за таблицата, трябва да бъдат освободени. Механизмът за това преди SQL Server 2000 SP3 беше следният:
За всеки екстент, разпределен към таблицатаЗапочнете
Придобийте изключително заключване на разпределението на екстента
Проверете заключване на страница за всяка страница в степента (получете заключването в ексклузивен режим и незабавно го пуснете, като се уверите, че никой друг не е заключил страницата)
НЕ освободете заключването на екстент, като гарантирате, че никой друг не може да използва този екстент
Преминете към следващия екстент
Край
Тъй като всички заключвания на степента бяха задържани до края на операцията и всяко заключване отнема малко количество памет, беше възможно диспечера на заключване да свърши без памет, когато DROP
или TRUNCATE
на много голяма маса. Някои клиенти на SQL Server започнаха да откриват, че са изпаднали в условия на липса на памет на SQL Server 2000, тъй като таблиците станаха много големи и значително изпревариха растежа на системната памет.
Механизмът за отложено пускане симулира DROP
или TRUNCATE
операцията завършва незабавно, като се откачат разпределенията за таблицата и се поставят в „опашката с отложено изпускане“, за по-късна обработка от фонова задача. Тази операция откачване и прехвърляне генерира само няколко регистрационни записи. Това е операцията, която се извършва и връща обратно в моя пример с код по-горе.
„Фоновата задача с отложено пускане“ се завърта на всеки няколко секунди и освобождава всички страници и екстенти в опашката с отложено пускане на малки пакети, гарантиращи, че операцията няма да остане без памет. Всички тези освобождавания са напълно регистрирани, но не забравяйте, че освобождаването на страница, пълна с данни или индексни записи, не регистрира отделни изтривания на тези записи; вместо това цялата страница просто е маркирана като освободена в съответната байт-карта за разпределение на PFS (Page Free Space).
От SQL Server 2000 SP3 нататък, когато изпълнявате DROP
или TRUNCATE
от таблица, ще видите само няколко генерирани регистрационни записи. Ако изчакате около минута и след това отново погледнете в дневника на транзакциите, ще видите, че хиляди регистрационни записи са генерирани от операцията за отложено изпускане, като всеки освобождава страница или екстент. Операцията се регистрира напълно и ефективно.
Ето пример, използвайки сценария, който създадохме по-горе:
CHECKPOINT; GO TRUNCATE TABLE [TestTable]; GO SELECT COUNT (*) AS N'LogRecCount' FROM fn_dblog (NULL, NULL); GO
Резултати:
LogRecCount———–
25
Както можете да видите, очевидно няма регистрационни записи, които освобождават 10 000 страници плюс 1250 екстента в таблицата TestTable.
Ако изчакам няколко секунди и след това стартирам fn_dblog
отново код, получавам:
———–
3811
Може да се чудите защо няма поне 10 000 регистрационни записи – по един за всяка освободена страница. Това е така, защото освобождаванията на страници дори се регистрират ефективно – с един регистрационен запис, отразяващ промените в разпределението на PFS страници за 8 последователни страници на файл с данни, вместо един регистрационен запис за всяка страница с файл с данни, отразяващ промяната на състоянието на разпределението в страницата на PFS.
SQL Server винаги се опитва да създаде възможно най-малко дневник на транзакциите, като същевременно се придържа към правилата за пълно или минимално регистриране въз основа на текущия модел за възстановяване. Ако искате да разгледате действителните регистрационни записи, генерирани от механизмите за откачване и прехвърляне и отложено пускане, просто заменете * за COUNT (*) в кода на fn_dblog по-горе и потърсете транзакция с име на транзакция, зададено на DeferredAllocUnitDrop::Process
.
В бъдещи публикации ще обсъдя вътрешните елементи, които са в основата на други устойчиви митове около аспектите на производителността на SQL Server Storage Engine.