Header RSS-подписка на обновления сайта eMail-подписка на обновления сайта

Как перестать называть журнал транзакций SQL Server лог-файлом и прекратить борьбу за его размер. Часть 4/12.

















Журнал транзакций и операция CHECKPOINT.

Мы уже знаем, что означенная процедура завершает собой описанный нами ранее «цикл» модификации данных и сводится к сбросу всех «грязных» страниц на диск. Возможно, упорное желание автора погрузиться в нюансы и этого процесса покажется уходом с главной «колеи» статьи. Был же, вроде, обещан разбор нюансов работы лога, а тут у нас сплошные «страницы данных» — нет? Намерения автора станут вам более понятны если вы учтете:

  • контрольные точки (те самые checkpoints) обязательно что-то записывают в транзакционный лог, причем в подавляющем большинстве случаев записи ими генерируемые являются критическими для «образа жизни» лога в обозримом будущем;
  • физический размер LDF файла зависит от checkpoints. Да — не всецело, и не только от них, и не всегда. Но отчасти — несомненно;
  • при каждом перезапуске сервера (и не важно, что произошло до того — плановый SHUTDOWN или «вилку — из розетки!») контрольные точки и лог работают «в одной упряжке», дабы выведенная в on-line база данных гарантированно содержала согласованные данные;
  • наконец, как подтверждение предыдущих трех пунктов, ни одна статья/заметка/глава книги посвященная логу и принципам его функционирования не обходится без погружения (той или иной степени «глубины») в указанный процесс. Ну или хотя бы краткого упоминания о нем. И причина все та же — слишком плотная связь первого и второго.

Итак — можете не сомневаться: для хорошего понимания работы журнала ясность и готовность ответа на вопрос «как работает checkpoint» является совершенно необходимым условием, обсуждать здесь нечего. Поэтому — просто переходим к делу.

Для начала давайте выясним — а когда он, CHECKPOINT, случается у нас на сервере? На самом деле поводов для инициирования указанной процедуры немало, судите сами:

  • CHECKPOINT может случится (и случается по этой причине он чаще всего) в определенные моменты времени, просчитанные сервером согласно заданному алгоритму;
  • вы можете инициировать его вручную в любой момент времени выполнив одноименную T-SQL команду; учтите, что будут сброшены лишь страницы той базы данных, в контексте которой вы запустили эту команду. Начиная с SQL Server 2008 не возбраняется запуск нескольких таких команд для, соответственно, нескольких баз «в параллель». Однако главное что вам следует учесть, так это то, что причины для выполнения такой команды в 99.99% случаев являются учебно-демонстрационными, как наша статья. Желание выполнить ее на системе находящейся в промышленной эксплуатации должно подавляться без всякой жалости;
  • CHECKPOINT случается в начале создания бэкапа данных (ни тип бэкапа, ни модель восстановления базы чей бэкап запускается, роли не играют), но не бэкапа лога! Впрочем, при бэкапе лога он тоже случается. Но в конце его. :)
  • CHECKPOINT случается если база данных находится в простой модели восстановления и журнал преодолевает отметку «заполнен на 70%»;
  • CHECKPOINT случается если база находящаяся до того в режиме on-line переходит в режим off-line, причем причина такого переключения не важна;
  • а так же если сервер останавливается командой SHUTDOWN без опции WITH NOWAIT. При этом сброс страниц будет произведен для каждой базы данных на отключаемом сервере.

Пара еще менее значимых причин общей картины не меняет — поводов для CHECKPOINT-а много! Важно что причина указанная в последнем списке первой «обходит» по своей значимости и весомости все прочие причины вместе взятые с огромным отрывом. То есть гораздо чаще CHECKPOINT случится в нашей базе в тот самый «определенный/нужный момент времени», нежели будет инициирован иным событием. Ну а тогда, с учетом значимости именно этой причины, более техническим языком пояснить тот самый «нужный момент» можно? Извольте!

Во-первых, не лишним будет знание о том, что контрольные точки случающиеся из-за этой самой первой причины носят название автоматических. И действительно, ни от нас, как администраторов системы, ни от наших пользователей, не требуется никакой прямой активности что бы подобная контрольная точка имела место. А, во-вторых, момент времени когда случится очередная автоматическая контрольная точка определяется параметром recovery interval. Этот параметр может быть легко изменен. Допустим, команда

1
2
3
4
sp_configure 'show advanced option', 1
RECONFIGURE
GO
sp_configure 'recovery interval',25

установит значение данной опции в 25 минут. И что бы понять физический смысл этой цифры нам не остается ничего иного как несколько отвлечься от главной сюжетной канвы нашей беседы и обсудить процесс известный как возобновление (recovery).

Возобновление (recovery) базы vs. восстановление (restore) базы.

К удивлению автора существует очень незначительное число русскоговорящих (ключевое слово!) «SQL-щиков» понимающих, а уж тем более способных внятно объяснить разницу между двумя указанными понятиями. Более того, многие из них (включая и «гуру», причем к ним этот термин относится без малейшей доли сарказма) убеждены что речь идет об одном и том же. Просто два таких слова-синонима. А между тем концепции-то это разные!

Не в укор фирме Microsoft, однако отмечу, что подобное заблуждение идет и с ее подачи тоже. Всеми любимый BOL однозначно переводит оба слова эквивалентно: восстановление. Взять хотя бы всем хорошо известное recovery model переведенное как модель восстановления. И так повсюду! С такой «подачи» эквивалентным переводом пользуются и независимые издательства, публикующие труды по SQL Server. Перевод же однозначно должен быть различен, ибо сами процессы обозначаемые этими словами — различны. Да, они (процессы эти) взаимосвязаны. Да, имеют «точки соприкосновения». Но — различны. Отсюда — правило: старайтесь читать хотя бы первоисточник (BOL) в оригинале, даже при наличии альтернативы в виде той же информации переведенной вручную. Если же альтернатива сводится только к машинному переводу — без раздумий принимайте решение что альтернативы оригиналу просто нет.

По указанным причинам автор везде в рамках данного блога (и текущая статья не исключение) четко обозначает, говорит ли он о процессе первом или втором. Для надежности в скобках указывается оригинальное (английское) его название. Единственное исключение из правила случается когда обсуждаемый вопрос/концепция/технология/и т.п. позволяет игнорировать разницу между двумя процессами и считать их «условно совпадающими». В этом случае используется термин восстановление как наиболее известный и уточняющее английское название не приводится.

Так вот, к сути текущего подраздела: где «порылась» разница?

  • восстановление (restore) — есть результат деятельности одноименной T-SQL команды (RESTORE) и сводится к тому, что база данных «возрождается» на вашем HDD в том состоянии, что она была на момент исполнения соответствующего бэкапа. Данный процесс не имеет никакого отношения к переводу базы из состояния off-line в состояние on-line. Да, чаще всего по завершению инструкции RESTORE база будет в последнем, и готова к нормальной работе. Вот только процесс (или, если хотите, «под-процесс») off-line → on-line не есть restore!
  • возобновление (recovery) — есть, в каком-то смысле, полная противоположность предыдущему. Это ни разу не перенос данных из бэкапа обратно на ваш HDD, а как-раз таки вывод базы из off-line (или, даже более корректно сказать, из состояния restoring) в on-line и, как следствие, «одобрение» на начало нормального функционирования базы.

Можно ли сказать что второе есть часть первого? Безусловно! А можно ли сказать что второе есть неотъемлемая часть первого? Ни в коем случае! Иначе зачем же у команды RESTORE имеется одноименная опция RECOVERY? Тогда, получается, что второе может быть частью первого? Вот именно! А может и нет.

Что еще даже интересней (в свете вопросов поставленных данной статьей), так это то, что возобновление (recovery) не является исключительно дополнением (притом опциональным) к восстановлению (restore). Отнюдь. Например при каждом (пере-)запуске сервера через процесс возобновления (recovery) проходят все и каждая из баз размещенных на этом самом сервере. При этом, очевидно, никаких RESTORE у нас нет и быть еще не может. Сразу же возникает вопрос — а что, эти два возобновления, один при восстановлении базы, другой при рестарте сервера — два эквивалентных процесса или как? К радости для тех кто любит «посложнее»: два возобновления различны. К радости для тех кто любит «попроще»: один является поднабором другого, так что понять и изучить нужно лишь одну версию, а для версии второй можно просто «отрезать лишнее».

Итак, наиболее полный процесс возобновления (recovery) случается как раз таки при (пере-)запуске сервера. Что бы отличать его от второй разновидности ему придумали даже отдельное имя: restart recovery, а иногда даже crash recovery. По-русски это будет что-то вроде «возобновление-на-старте». Только не обманитесь вторым именем (crash)! Обсуждаемая разновидность возобновления (recovery) будет иметь место всегда, не важно завершилась ли предыдущая деятельность сервера аварийным «крэшем», или же он был «тактично» отправлен в перезагрузку командой SHUTDOWN и даже без опции WITH NOWAIT. Разница лишь в том, что при «ласковом» обращении с сервером restart recovery займет ничтожное количество времени, но чисто формально все-таки случится! Ну а при «крэше» ему же придется трудится в «поте лица своего», дабы «причесать» базу (а точнее — все базы сервера). Кстати говоря, начиная с версии 2008-й «возобновления-на-старте» запускаются сразу в параллель для нескольких (по возможности — сразу для всех) баз сервера начинающего свою работу. Что значительно ускоряет процесс даже после крэша, но ни в коем случае не отменяет того факта, что команда SHUTDOWN до сих пор имеет значительные преимущества перед главным рубильником здания. Особенно эти преимущества станут заметны когда рубильник вернут в исходное положение, а вы приступите к запуску сервера.

Так вот, как выглядит это самое «полноформатное» возобновление (recovery)? Выясняется, что оно включает в себя 3 фазы:

  • Фаза 1, анализ (analysis). Довольно сложный алгоритм, подробная механика которого лежит за границами данной статьи, и лишь некоторые его нюансы будут пояснены ближе к ее окончанию. На текущий момент достаточно сказать, что на этой фазе подготавливается информация для двух последующих фаз.
  • Фаза 2, накат (redo). Все транзакции случившиеся с некоторого момента (сейчас не будем вдаваться в подробности какого именно, обозначим этот момент расплывчатой формулировкой «незадолго до крэша/остановки сервера») повторяются на страницах данных. Автор не зря выделил слово все, ибо именно с этой фазой связана одна из мощнейших «мисконцепций» SQL-сообщества, считающего что подобной обработке (накату, он же roll-forward) подвергаются только записи зафиксированные (commit) в журнале транзакций. Так вот, дорогие мои, накатываются все транзакции. То есть абсолютно все что найдено в журнале (не с его начала, конечно, а с той самой «расплывчатой» точки времени). «Зачем?» — спросите вы. «Что бы через 2 миллисекунды, на следующей фазе половину этого отменить?» А вот представьте — и что бы отменить тоже! «Метафизика» процесса сводится к тому что бы в точности (абсолютной!) повторить каждый «чих» произошедший с каждой ячейкой данных. Если, согласно записям журнала, ячейка таблицы T1 менялась так: 4(исходное значение) → 2(транзакция 1) → 4(отмена транзакции 1) → 98(транзакция 2) → 4(отмена транзакции 2), то описываемый процесс аккуратно повторит цепочку: 4-2-4-98-4. Кроме того, учитывайте, что к окончанию этой фазы (а не следующей) страницы данных должны принять абсолютно тот же самый вид и положение, что они имели к моменту крэша/остановки сервера. Чуть модифицируя предыдущий пример: транзакция 1 была отменена явно, в журнале есть запись о ее ROLLBACK. Транзакция же 2 не была отменена явно, то есть есть запись о ее начале и первой из операций — изменении значения ячейки с 4 на 98. Чем она завершилась — неизвестно, сервер «упал». Так вот к окончанию данной фазы на странице данных, в указанной ячейке обязано быть 98. А не 4. Ибо на момент «крэша» там было 98. А не 4.
  • Фаза 3, откат (undo). Все незавершенные транзакции (т.е. не имеющие отметки commit согласно исследованиям первой фазы) индивидуально отменяются. То есть для каждой такой транзакции строится ее «цепочка» (вот когда колонка Previous LSN пригождается-то!) и — звено за звеном, в «обратную» сторону. Только здесь наша ячейка поменяет «нелегитимное» 98 на законную четверку.

Ну и в традициях данного блога — вопрос для самопроверки:

Изменится ли что либо в описанном сценарии с ячейкой имеющей изначально значение 4, если транзакция 2 будет не «повисшей» (т.е. с неизвестным окончанием, как это уже разобрано выше), а будет иметь четкую отметку о своей отмене (ROLLBACK)?

Смотреть ответ
Ячейка, по итогу, обретет тоже финальное значение 4, но случится это уже на второй фазе. Еще раз осознайте факт: redo=применение всех (а не только зафиксированных!) транзакций, информацию по которым удается «вытянуть» из лога. А undo=отмена только «зависших» (а не отмененных явно) транзакций, что практически случается лишь при «насильственном» прекращении работы сервера.

Тут нельзя не воспользоваться случаем и не отметить — какие замечательные возможности скрывает в себе этот превосходный инструмент, имя которому SQL Server. Как мы уже знаем, финальная цель всех трех фаз описанных только что — вывести базу в on-line. Ведь именно это и называется «возобновление» (recovery), не забыли? Кажется логичным, что пока идут эти фазы — база off-line, как завершились — on-line. Тем отраднее будет узнать, что начиная с SQL Server 2005 база «выходит» в on-line после второй фазы (redo). А не после третьей. «И что — велик ли профит?», возможно спросите вы. На что автор порекомендует задать этот вопрос тем SQL-администраторам, чей процесс возобновления (recovery) обязан был откатить (в фазе undo) удаление, скажем, 10 миллионов строк. От них вы узнаете, что разница в том насколько быстро первые пользователи «увидят» первые данные измеряется часами. Нужно ли пояснять что это такое в масштабах крупного концерна? Однако, самые наблюдательные читатели, до того как радоваться, могут подумать: «это все cool, и даже super, но! Если «запустить» пользователей сразу после redo то их первый же SELECT ко все той же ячейке вернет им 98. Поскольку, как мы выяснили выше, в случае «зависшей» транзакции там будет именно это значение. И дальше что?». Что же, опасения вполне резонны, однако почвы под собой не имеют. Не получат наши пользователи 98! События будут развиваться так: на второй фазе процесс возобновления (recovery) постарается с максимальной достоверностью, «шаг-в-шаг», воспроизвести проблемные транзакции. Точнее, все транзакции будут воспроизведены им «доподлинно», но для обсуждаемого вопроса особенно актуальны проблемные, т.е. «зависшие». В нашем сценарии таковой является транзакция №2. Но, раз она успела 4 поменять на 98, то, очевидно, ею была наложена блокировка (lock). На одну ли строку, на всю таблицу ли — не важно. Важно что блокировка была и она блокировала исследуемую ячейку в том числе. Так вот фаза redo воспроизводит и блокировки тоже! Таким образом, при «досрочном» выведение базы в on-line, данные попавшие в «проблемные» транзакции недоступны и ждут снятия блокировок фазой третьей. Все прочие данные доступны с момента окончания второй фазы, причем доступны полноценно — и на выборку, и на модификацию.

Описанная «фича» носит название fast recovery, и, можете не сомневаться, работает четко на повышения «ап-тайма» вашего сервера, вопрос лишь в масштабности этого дополнительного «ап-тайма» — от долей секунд до, как было отмечено, часов. И — да, имейте в виду, что фаза 2 по времени исполнения быстрее (обычно — много быстрее) фазы 3. Это замечание не универсально (зависит от масштаба произошедших транзакций и их «структуры»), но чаще она будет справедлива, и, соответственно, только повысит значимость такой возможности. Ну и не без «ложки дегтя», а как же! Enterprise Edition only, да. :( В 2008/R2 ничего не изменилось. Ну разве что указанная «фича» стала еще Datacenter-ом поддерживаться, если вам от этого легче. :) Все прочие редакции будут ждать полного завершения всех трех фаз.

Ну а что у нас с «усеченной» версией возобновления (recovery)? Напомню, это та ее разновидность что происходит в момент восстановления базы из бэкапа. Так вот и она, вполне ожидаемо, имеет имя собственное! Этот вариант recovery принято обозначать термином restore recovery, а иногда и media recovery, что в вольном переводе будет соответствовать примерно «возобновление-с-носителя», намекая нам о том, что данный процесс случается, когда вы берете носитель (с бэкапом, разумеется) и выполняете по отношению к нему команду RESTORE.

Итак, отличия «возобновления-с-носителя» от «возобновления-на-старте» сводится к числу фаз в том и другом процессе. Последний, как мы твердо усвоили, процесс трехфазный. А первый — только двух, пропадает фаза «откат» (undo). Точнее, как: для любого звена бэкапа, вне зависимости указали мы опцию RECOVERY или NORECOVERY случаются первые две фазы: анализ и накат. Первая фаза создает «рабочую площадку», вторая — применяет все (снова!) записи журнала имеющиеся в данном звене бэкапа. Тут нужно учесть, что записи лога носят всегда прямолинейно-последовательный характер, и каждая последующая (т.е. более поздняя по времени) операция будет упомянута в логе гарантировано позже операции более ранней. Но и правило обратное тоже верно: если при восстановлении мы из записи лога любого звена бэкапа узнали что в точке времени X ячейка сменила свое значение с оригинальных 4 на новые 98 — нам следует немедленно выразить полученное знание в действии, т.е. собственно изменить ячейку. Тянуть и думать здесь совершенно нечего: если в оригинальной базе все происходило «вот так», то и «вот так же» должно быть в базе восстанавливаемой. И у нас получается: анализ → накат → анализ → накат → анализ → и т.д. пока мы не достигнем последнего звена цепочки бэкапов. Тут у нас отличие от звеньев предыдущих только одно, а именно: пока мы находимся не в последнем звене «подвисшая» транзакция — это не проблема. Вполне вероятно звенья последующие «закроют» ее фиксацией (commit) или откатом (rollback). Но как только звено последнее, и указана опция RECOVERY, «подвисшая» транзакция — это, безусловно, проблема. Надо что-то делать. И даже известно что — «резать», то бишь принудительно откатывать. И вот только тут случается фаза третья — undo. Назначение коей абсолютно тоже самое что и в предыдущем варианте возобновления: разобраться с проблемными транзакциями.