SqlCmd все о SQL технологиях.





Все что необходимо для изучения и работы с СУБД Microsoft SQL Server, MySQL, MariaDB, MongoDB. Авторские статьи, библиотека фрагментов T-SQL кода, сборник полезных инструментов.



MS SQL Server, SQL Server, T-SQL, исходники, сниппеты, статьи, учебники SQL, утилиты SQL, инструменты SQL



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

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





Насколько же успешно решается вопрос заданный в последнем предложении предшествующей части статьи? Насколько корректно ведет себя SQL Server версии X при попытке заставить его работать с базой данных созданной на сервере версии Y, причем X<Y? Ну — «футболить» администратора в таком сценарии у сервера версии X получается просто на 5 с плюсом, никаких претензий! С информативностью же касательно причины подобного «футбола» сложнее. Диапазон сообщений которые вы можете получить в описываемом сценарии простирается от более-менее внятного

This database is version YYY. This server is compatible with version XXX or previous.

до загадочных

Could not find row in sysindexes for database ID 10, object ID 1, index ID 1. Run DBCC CHECKTABLE on sysindexes.

или даже

The media family on device 'c:\abc\zyx.bck' is incorrectly formed. SQL Server cannot process this media family.

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

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

Обратите внимание, что даже в сообщении названным автором «внятным» вы получаете «голые» версии баз, без всяких сносок по их связям с версиями сервера. Про сообщения «загадочные» и говорить не приходится, там нет хотя бы даже такой, «сырой» информации. Стало быть, что бы реализовать наше «простое и практичное правило» на практике, нам, в общем случае, требуется решение двух задач:

  • извлечь номер версии базы из mdf/bak файла;
  • соотнести этот номер с той версией сервера, где такой файл гарантировано может быть восстановлен/подключен.

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

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

Соответствие версий баз данных версиям сервера
Имя сервера Версия сервера Версия базы
SQL Server 7.0 7.00.623 515
SQL Server 2000 8.00.194 539
SQL Server 2005 9.00.1399 611
SQL Server 2005 with SP2 9.00.3042 612
SQL Server 2008 10.00.1600 655
SQL Server 2008 with SP2 10.00.4000 655/662
SQL Server 2008R2 10.50.1600 661
SQL Server 2008R2 with SP1 10.50.2500 661/663
SQL Server 2012 RC0 11.00.1750 705
SQL Server 2012 RTM 11.00.2100 706
На сетевых просторах вы можете встретить различные варианты приведенной таблицы. Скажем ряд авторов в своих публикациях полагают, что серверу 2008-му соответствует версия базы 661 (а не 655), а версии 2008R2 — 665 (а не 661). Автор данных строк полагает, что такие расхождения проистекают, во-первых, из искусственной закрытости разбираемого вопроса, и, во-вторых, из-за элементарной человеческой невнимательности. С точки зрения все того же автора именно вариант таблицы представленный выше является корректным, но, разумеется, он с удовольствием выслушает ваши контр-аргументы в комментариях к данный статье. И если последние будут весомыми с не меньшим удовольствием внесет правки в обсуждаемую таблицу. Кроме того, понятное недоумение вызывают версии баз для серверов 2008 и 2008R2 со вторым и первым сервис-паками соответственно. Откуда эта запись через дробь? Разве не должно быть однозначного соответствия база ↔ сервер? В целом — да, такая однозначность быть обязана и в целом, как видите, она выдерживается. Однако тут у нас особый случай (точнее — пара), разбор которых мы отложим до заключительного раздела данной статьи.

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

Какие же выводы может сделать пытливый ум анализируя приведенную таблицу? А вот какие:

  • изменение версии базы полностью «отвязано» от изменений версий сервера. То есть «подъем» номера второй версии не означает безусловного «подъема» номера первого. Версия базы лишь может измениться в этот момент. Но может и нет. Здесь здравый смысл вполне побеждает: если структура mdf-файла для двух версий сервера не менялась — зачем трогать версию этого файла? С другой стороны, «подъем» версии базы безусловно означает выход новой версии сервера (или хотя бы сервис-пака) — иначе с чего бы ему меняться?
  • частота смены версий базы занимает промежуточное положение между частотой смены компонента build и компонентов major/minor в версии сервера. То есть: не каждый новый build «поднимает» версию базы, но некоторые это делают, а вот каждый новый major/minor делают тоже самое безусловно;
  • потенциально версию базы может изменить даже «фикс»/«апдейт» для текущей версии сервера, хотя автор и не готов привести реальные примеры таких изменений. А вот сервис-пак не только может сделать тоже самое, но и проделывал это в реальности неоднократно, см. таблицу. Контр-примеры тоже имеют место быть, и даже значительно более традиционны: «фикс»/«апдейт»/сервис-пак вышли, но не изменили версии базы (хотя, конечно же, изменили версию сервера);
  • есть как минимум один пример (SQL Server 2008 with SP2) когда номер версии базы созданной на сервере младшей версии является большим, чем тот же номер базы созданной на сервере старшей версии, а именно 662>661. И — нет, ошибок в таблице нет, база 2008-го сервера со вторым сервис-паком действительно «старше» (чисто формально, разумеется) базы сервера 2008R2. Понимаю, что этот кажется странным и нелогичным, однако, поверьте, у Microsoft были к такому шагу самые серьезные причины. Впрочем этот момент заслуживает отдельного разбирательства, чем автор и займется в заключительном разделе данной статьи. Пока же констатируем факт: в 99% выполняется интуитивно ожидаемое правило — «младшим» серверам соответствуют «младшие» базы. «Старшим», разумеется — «старшие».

Итак, второй пункт нашего списка из двух задач решается с некоторым «скрипом» и неоднозначностями. Но — решается. Пункт первый (непосредственно нахождение номера версии базы) может быть описан и решен гораздо более четко, хотя и тут не все так просто, как было с версией сервера. В каком состоянии может находится база в тот момент когда мы решим поинтересоваться номером ее версии? Очевидно — в одном из вот таких:

  • быть уже подключенной к серверу и находится в обычном состоянии on-line. Для чего нам может вдруг потребоваться узнать ее версию? Ну вот автору, к примеру, это потребовалось для того что бы написать данную статью, а зачем это нужно вам можете ответить только вы сами :) ;
  • быть в состоянии резервной копии (bak-файл). Версия нас может заинтересовать в момент получения одного из нежданных сообщений, примеры которых были приведены выше. Или мы можем поосторожничать и проверить номер версии «с упреждением», до того как переходить к самому процессу восстановления. А то мало ли что...;
  • быть в состоянии первичного файла данных (mdf-файл), отключенного от сервера эту базу породившего. Здесь так же интерес в номере версии может быть вызван как непонятным сообщением полученным от текущего (не оригинального) сервера в момент подключения такого файла, так и желанием удостовериться в совместимости версий базы и сервера заранее, до попытки подключения первой. Однако именно в этом сценарии есть причина и третья, значительно более серьезная чем даже обе упомянутые. Ее автор пояснит в заключительном разделе, где он вообще рассмотрит вопросы «сочетаемости» различных версий баз и серверов.

Стало быть — по порядку. Если база уже существует, т.е. мы в свое время исполнили нечто вроде:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
USE master
GO
CREATE DATABASE [~DB~]
ON PRIMARY (
    NAME = '~DB~_Data',
    FILENAME = 'c:\sqlCMD.ru\~DB~.mdf',
    SIZE = 20 MB )
LOG ON (
    NAME = '~DB~_Log',
    FILENAME = 'c:\sqlCMD.ru\~DB~.ldf',
    SIZE = 20 MB )
GO
USE [~DB~]
GO
CREATE TABLE T1 (col1 int, col2 int)
insert into T1 VALUES (10, 20)
insert into T1 VALUES (30, 40)
CREATE CLUSTERED INDEX i1 ON T1(col1)
go

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

1
2
3
USE master;
GO
SELECT DatabaseProperty ('~DB~', 'version');

вернет нам

661

что полностью соответствует версии 10.50.2500 сервера установленного на тестовой машине автора. Почему автор называет данный способ быстрым, полагаю, вполне понятно, но почему — «грязным»? А потому, что официальная документация говорит нам о параметре version функции DATABASEPROPERTY вот какую вещь:

Указано только в ознакомительных целях. Не поддерживается. Совместимость с будущими версиями не гарантируется.

То есть особого доверия этому результату нет. Сегодня все работает, а завтра? Гораздо более надежным, а потому предпочитаемым автором данных строк, является метод основанный на том факте, что пометка о номере версии базы вносится на загрузочную страницу (boot page) этой самой базы. Формат именно этой страницы меняется очень не часто, а ее местоположение в mdf-файле не менялось уже «тыщу лет» — это непременно девятая страница в первичной файловой группе. Но что бы воспользоваться этим надежным способом нам надо прибегнуть к небольшому «хаку», а именно нам потребуются услуги недокументированной команды DBCC PAGE. Стартовую информацию по ее применению можно почерпнуть из части второй, статьи «Как перестать называть журнал транзакций SQL Server лог-файлом и прекратить борьбу за его размер». А в нашем примере достаточно исполнить такую пару команд:

1
2
dbcc traceon(3604)
dbcc page([~DB~],1,9,3);

Мы получим довольно-таки внушительный отчет, но нас будет интересовать небольшой блок данных из второй части отчета, сразу за дампом двоичных данных. Этот блок даже имеет собственное имя — DBINFO, и вполне четко выделен в отчете. Говоря технически, DBINFO есть ни что иное как программная структура, несущая в своих полях массу ценной информации по каждой базе данных. В рамках обсуждаемого вопроса нас будут интересовать всего два поля этого блока/структуры. Вот как он выглядит на машине автора:

...
DBINFO @0x0000000014A9A060

dbi_dbid = 15                        dbi_status = 65544                   dbi_nextid = 2121058592
dbi_dbname = ~DB~                    dbi_maxDbTimestamp = 2000            dbi_version = 661
dbi_createVersion = 661              dbi_ESVersion = 0                    
dbi_nextseqnum = 1900-01-01 00:00:00.000                                  dbi_crdate = 2012-02-10 14:19:55.143
dbi_filegeneration = 0              
dbi_checkptLSN
...

Интересующие нас поля: dbi_createVersion (в левой колонке) и dbi_version (в правой). В данном случае, как видите, значения этих полей равны между собой и равны тому значению, что вернула нам функция DatabaseProperty. Однако в общем случае значения обсуждаемых полей могут и различаться, и в заключительном разделе статьи мы обсудим такой случай. Пока же зафиксируем вывод:

Узнать версию подключенной базы надежнее через просмотр ее девятой (boot) страницы командой DBCC PAGE.

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

1
2
3
USE master
go
BACKUP DATABASE [~DB~] TO  DISK = 'C:\sqlCMD.ru\MyDB.bak'

А теперь — извлекаем искомое, не пытаясь, разумеется, восстановить базу:

1
2
3
USE master
go
RESTORE HEADERONLY FROM DISK = 'C:\sqlCMD.ru\MyDB.bak'

Получаем очень «широкий» резалт-сет из одной строки, нас интересует всего одна колонка:

DatabaseVersion
661

Насколько известно автору заголовки резервных копий созданных SQL сервером умышленно поддерживаются в обратно-совместимом состоянии с тем, что бы принеся .bak-файл с сервера 2012-го на сервер 2000-й (или же наоборот, бэкап сделали на 2000-м и пришли с ним на 2012-й) вы, как минимум, смогли увидеть вот эту самую информацию и сделать вывод о совместимости базы данных с текущим сервером. Если такой вывод будет отрицательным вы хотя бы сэкономите время не пытаясь инициализировать процесс восстановления абсолютно бесполезной (для данного конкретного сервера) базы данных. Напомню, что мы с вами тот же самый вопрос совместимости рассмотрим в заключительной части текущей статьи.

Наконец — случай самый сложный: просто mdf-файл. Как узнать версию? Заголовка резервной копии у нас, понятно, нет, база в настоящий момент не подключена... Думаете — просто попробовать подключить базу? Вариант. Но — не идеальный, как минимум. Во-первых, нас могут ожидать сообщения об ошибках из-за несовместимости базы и сервера, что, конечно, переживаемо, хоть и неприятно. Но так мы хотя бы требуемый номер версии узнаем из текста ошибки, если, конечно, сообщение будет «вменяемым». Однако есть куда как более серьезное во-вторых: таким неподготовленным подключением мы можем... испортить базу! Сразу оговорюсь: в данном контексте «испортить» не равно «начисто сломать», после чего базу можно только выкинуть. Нет, после такой «порчи» база, в общем и целом, будет рабочей и все же, в определенном смысле — испорченной. Непонятно? Читайте далее, как обычно все встанет на свои места в заключительном разделе.