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

Версия сервера, версия базы данных и уровни совместимости. Часть 3/4.

Итак, нам, без сомнения, требуется способ нахождения номера версии базы из имеющегося mdf-файла без подключения последнего к серверу, и даже без попытки выполнить такую процедуру. Вспоминаем, что требуемая цифра находится на девятой странице базы которая, несомненно, содержится среди прочих страниц в имеющемся у нас на руках mdf-файле. Но где именно искать в этом файле данную информацию? Рассуждаем примерно так: любая страница, и boot page не исключение, начинается с 96-байтового заголовка (page header). В этом заголовке нужной нам версии нет. Но вот сразу после него начинается упомянутая в предыдущей части статьи специальная структура DBINFO (см. выдержку из отчета команды DBCC PAGE), где и расположились интересные нам поля — dbi_createVersion и dbi_version. Мы знаем, что корректное значение этих полей для конкретно нашей тестовой базы ~DB~ — 661 (поскольку базы именно такой версии «производит» SQL Server на машине автора), или 0x0295 в шестнадцатиричном формате. В отчете команды DBCC PAGE сначала идет двоичный дамп обсуждаемой структуры, а затем она же в «человеко-читаемом» формате (именно этот участок отчета приведен по ссылке чуть выше). Так вот, очевидно, что значение 0x0295 должно встретиться в дампе той же структуры дважды — по разу для каждого из полей. Не забыв, что работаем мы с вами на аппаратной платформе с порядком записей байт little-endian (если эта фраза показалась вам зело загадочной — обратитесь к части 6-й статьи «Понимаете ли вы коллейшены?»), поищем в этом самом дампе число 9502 (разумеется — шестнадцатиричное). Более того, автор может облегчить вам задачу, сообщив, что обе эти версии записываются всегда подряд, и, стало быть, искать следует уже четырехбайтовое число 95029502. Впрочем, надолго наши поиски не затянутся, требуемое без проблем находится в первой же строке дампа по смещению 0x4. Итого — что у нас в «сухом остатке»?

  • от нулевого байта mdf-файла следует «отступить» на 9*8192=73728 байт что бы «встать» на нулевой байт 9-й страницы;
  • далее еще пропускаем 96 байт, а всего 73728+96=73824, что бы «встать» на нулевой байт структуры DBINFO;
  • и, наконец, пропуск еще 4-х байт (73824+4=73828) приводит нас к искомому четырехбайтовому фрагменту;
  • осталось сконвертировать 73828 десятичных в 0x12064 шестнадцатиричных, и — вуа-ля, любой hex-viewer к нашим услугам.

К примеру автор проверил свой c:\sqlCMD.ru\~DB~.mdf файл:

VersionInHEX

Если же вас интересует что из этой пары есть dbi_version, а что — dbi_createVersion, то и это несложно: всегда сперва (по смещению 4) идет первое поле, затем (по смещению 6) — второе. Напомню, что к причинам того почему обсуждаемый номер версии указывается дважды мы вернемся в заключительном разделе статьи. Как говорится — пользуйтесь на здоровье.

Разумеется, автор не может гарантировать что версии баз всегда были/всегда будут находится именно в указанной точке mdf-файла (хотя это и весьма вероятно), однако он не зря объяснил методику нахождения этой самой точки. Так что если вам доведется работать с базами сервера очень ранних версий (до 6.0), либо в будущем Microsoft решит сменить дислокацию структуры DBINFO (а то и всей boot-страницы) — вы знаете как подойти к вопросу, вам потребуется лишь создать на таком старом/новом сервере тестовую базу и в ней вычислить новое смещение отличное от 0x12064. Вместе с тем, автор может смело констатировать тот факт, что у всех без исключения mdf-файлов у которых ему довелось проверить четыре байта по этому смещению (а таковых, поверьте, было немало, и версии там случались ну очень разные) в них содержались именно значения полей dbi_version/dbi_createVersion и ничего иного.

Уровни совместимости.


366bef3a

Третий участник нашего повествования. Если первые два были атрибутами вещей вполне себе физических (программный продукт типа сервиса SQL Server и mdf-файл), то теперь у нас не более чем опция уровня базы данных и не более того. Тем не менее эта опция, как отмечалось в вводном разделе, крайне близко подходит по смыслу к все той же паре номеров версий. Она тоже сигнализирует нам: появилась новая «фича», особенность! Только на этот раз в фокусе внимания оказывается не функциональный фактор такой «фичи» (вот секций для таблиц не было, а вот они есть), а фактор поведенческий.

Быстрый пример 1. В SQL Server 2000 вы могли написать что-то типа

1
SELECT 1 AS 'One', 2 AS 'Two' ORDER BY 1.78

То есть вы могли указать не целую константу в клаузуле ORDER BY. Да, это не имело никакого эффекта (константа просто игнорировалась движком), но и ошибок времени исполнения не было! Начиная с версии сервера 2005-го та же самая, буква-в-букву, команда выдаст вам:

Msg 408, Level 16, State 1, Line 2
A constant expression was encountered in the ORDER BY list, position 1.

Что изменилось? Поведение кода.

Быстрый пример 2. До SQL Server 2008-го команда

1
SELECT DATEPART (year, '2001-08/11')

возвращала вполне себе правильный результат — 2001, хотя второй аргумент явно нарушал синтаксис функции DATEPART, требующей в этом месте валидный литерал типа datetime. Абсолютно та же строка начиная с сервера 2008-й версии вернет вам:

Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.

Что изменилось? Все то же — поведение кода.

Отсюда совершенно понятны те «кейсы», когда уместно применение этой опции. Допустим, у нас решение на базе сервера 2000. Куча кода, все работает, все довольны. Проапгрейдились до версии 2008 — посыпалась куча ошибок с номером 408. Почему? Да потому, что у нас полно строк содержащих в ORDER BY не целые константы (да, пример натянутый, но, полагаю, суть ясна). Разумеется, код следует привести в соответствие с теми правилами по которым «играет» сервер новой версии, но за час это не сделать. И за день. И даже за три. А работать надо прямо сейчас. И вот тогда, мы указываем нашей рабочей базе/базам уровень совместимости с сервером 2000-м, пускаем на сервер пользователей, и незамедлительно приступаем к исправлению кода. Когда код исправлен — возвращаем опцию уровня совместимости для той базы/баз для которой она была изменена в значение по умолчанию.

Опция уровня совместимости — временная (ключевое слово!) мера, помогающая провести более плавный «переезд» существующего решения на сервер новой версии. Уровень совместимости с SQL Server версии X не гарантирует абсолютной идентичности работы с сервером этой версии. Версия X лишь эмулируется сервером новой версии до некоторой степени достоверности (весьма далекой от 100% совпадения с оригиналом, заметим между прочим). Для отлаженного, протестированного решения услуги этой опции не требуются. Нормальное состояние сервера — это установка данной опции для всех его баз данных без исключения в значение по умолчанию.

Что же до числового формата уровня совместимости, то он представляет из себя «условный код» номера версии того сервера, совместимость с которым требуется обеспечить. «Условный код» образуется так: часть major номера версии пишется полностью и к ней дописывается (без разделяющей точки) первая цифра части minor. Скажем, для сервера 2008R2 major.minor равны 10.50, а «условный код», соответственно, 105 (обратите внимание — ваша способность просчитать «условный код» не означает что уровень совместимости с таким кодом обязан существовать; этот момент поясняется далее). Аналогично — сервер 2000-й: 8.00 → 80. Именно этот условный код применяется как параметр команды ALTER DATABASE, которая и устанавливает требуемый уровень совместимости. Скажем, мы хотим задать для нашей тестовой базы совместимость с сервером 2005-й версии. Сначала просчитываем «условный код» целевого сервера: 9.00 → 90. Ну и даем команду:

1
2
3
USE master
GO
ALTER DATABASE [~DB~] SET COMPATIBILITY_LEVEL = 90

Для любителей работать мышкой, а не клавиатурой, предусмотрен выпадающий список на закладке Options окна свойств базы:

CompatLevel

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

1
2
3
USE [~DB~]
GO
SELECT compatibility_level FROM sys.databases WHERE name=DB_NAME()

В качестве резалт-сета вам вернется «условный код» который вы без труда переведете в коммерческое имя сервера.

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

Небезынтересно отметить, что обсуждаемая эмуляция «совместимости назад» отнюдь не простирается до первых версий SQL Server. Например, если у вас сервер 2008R2, то почувствовать себя «в шкуре» администратора сервера версии 4.0 вам не суждено. Можно сказать, что современные версии SQL Server предлагают всего три уровня:

  • «родной», или уровень по умолчанию; именно на нем должны работать все базы вашего сервера в нормальном режиме;
  • уровень совместимости с предыдущей версией;
  • уровень совместимости с «пред-предыдущей» версией;

Иными словами, ретроспектива предлагается в стиле «не больше двух версий назад». Разумеется, под «версией» в контексте данного абзаца понимается выход продукта с новым коммерческим именем, а не смена версии в силу установки «фикса»/«апдейта». Более того, бывает и так, что новый (именно как коммерческий продукт) сервер вышел, а нового уровня совместимости не появилось. Скажем для серверов 2008 и 2008R2 уровень по умолчанию один и тот же — 100. А уровня 105, который мог бы быть, просто не существует. Связано это с тем, что поведение двух указанных версий настолько близко, что попросту нет смысла «огород городить». А поэтому обе эти версии предлагают такие уровни совместимости:

  • 100 — «родной», «нормальный» уровень;
  • 90 — 2005-й сервер, предыдущий;
  • 80 — 2000-й сервер, «пред-предыдущий»;

А для сервера 2012 та же «линейка уровней» будет: 110 («родной»), 100 (совместимость с 2008 и 2008R2), 90 (совместимость с 2005).

Вывод? Очень простой: обновляйте код ваших решений находящихся в промышленной эксплуатации (и вывод которых из оной не планируется) под новые сервера достаточно оперативно (как минимум — в разумные сроки), не «тяните» с апгрейдом!

Все вместе.

В заключительной части статьи мы рассмотрим совместную работу тех трех сущностей, что в частях предыдущих были нами разобраны персонально. Кроме того, как вы помните, автор оставил рассмотрение целого ряда вопросов именно для этой части — пришло время выполнить обещанное.

Давайте сначала сосредоточимся на противостоянии «версия сервера» vs. «версия базы». Тут мы сразу можем отметить себе правило первое:

Все базы данных конкретного сервера находящиеся в состоянии on-line будут иметь одну и ту же версию базы.

Перефразируя: сервер версии X1 может и будет работать с базами версии X2 и только с ними. Такая версия X2 баз данных называется «родной» (native) версией баз для сервера версии X1. Собственно, таблица приведенная во второй части данного труда является ничем иным как сопоставлением различных X1X2. И, еще раз повторяю, любой сервер работает только с базами «нативной» версии, точка. Не стоит и говорить, что все базы данных созданные на сервере версии X1 командой CREATE DATABASE будут без вариантов иметь версию X2. Означает ли это невозможность поработать на сервере версии 2008R2 с базой данных оригинального происхождения с сервера версии 2005? Отнюдь! Потому что «работа» — это когда база уже в on-line, к ней разрешены запросы пользователей и т.д. Вот тут разночтений никаких — база обязана иметь версию X2, еще раз точка. А есть еще подготовка к такой работе. В ходе этого процесса происходит много чего интересного, но мы можем сосредоточится лишь на небольшой его части, а именно на автоматическом апгрейде версии базы до требуемого. Два выделенных жирным слова заслуживают вашего особого внимания:

  • автоматически — означает, что апгрейд (при условии его теоретической осуществимости, см. далее) будет проведен, во-первых, без каких либо указаний с нашей стороны (нам не нужно инициировать этот процесс), а, во-вторых, без предоставления нам каких либо опций (апгрейд случится, даже если нам хотелось бы от него отказаться);
  • апгрейд — означает повышение, и только повышение номера версии базы.

Отсюда — правило номер два:

«Даунгрейд» версии базы (понижение номера ее версии) исключен. То есть вообще. SQL Server физически не запрограммирован на такую операцию. Чисто теоретически некая третья фирма могла бы «запилить тулзу» на эту тему. Но автору ничего подобного неизвестно и он сильно сомневается в экономическом обосновании расхода тех ресурсов, что потребует создание такой «тулзы». Объем плановых работ в этом направлении он оценивает как колоссальный, возможную прибыль как сомнительную. Кроме того, перед потенциальными создателями подобного инструмента встанут такие «мозговыносящие» вопросы как: при даунгрейде базы данных содержащей фильтрованные индексы до версии где таких индексов быть не может по определению — что делать с последними?

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

Хорошо, можем ли мы утверждать, что если есть база данных версии Z, и эта версия соблюдает правило Z<X2, где X2 есть «нативная» версия баз для сервера версии X1, то сервер этой самой версии X1 безусловно сможет провести апгрейд такой базы до версии X2? Не можем мы это утверждать! Потому что неравенство записано лишь частично. А полностью оно будет выглядеть так: Y<=Z<X2, где Y есть минимально совместимый номер версии базы. Что значит «минимально совместимый»? А это значит, что бесполезно нести базу данных с SQL Server версии 4.0 на сервер 2008R2 — последний просто ее не поймет. Иными словами, у каждого сервера X1 есть (в обсуждаемом контексте) граница «видимости назад», точно так же как она есть у уровней совместимости. У последних она довольно четко определена («минус две версии назад») и, самое главное, официально документирована. Для границ же видимости версий баз данных дело обстоит похуже: границы не указаны и в официальной документации не фигурируют, увы-увы. Снова проходится обращаться к независимым исследователям и энтузиастам. Согласно их изысканиям, граничные значения Y/X2 для различных версий серверов будут такими:

Минимальная и максимальная версия баз данных для различных версий сервера
Имя сервера Минимальная версия базы, Y Максимальная версия базы, X2
SQL Server 7.0 515 515
SQL Server 2000 515 539
SQL Server 2005 515 611
SQL Server 2005 with SP2 515 612
SQL Server 2008 539 655
SQL Server 2008 with SP2 539 655/662
SQL Server 2008R2 539 661
SQL Server 2008R2 with SP1 539 661/663
SQL Server 2012 RTM 611 706

У автора не было возможности самолично убедиться в корректности каждой строки последней таблицы (а только их части) и поэтому он снова с удовольствием воспримет возможные ее дополнения/правки указанные в комментариях читателями блога. После проверки такие исправления будут незамедлительно вносится непосредственно в ячейки таблицы.