Восстановление базы данных — одна из наиболее важных задач, которые приходится решать администратору. При планировании восстановительных мероприятий нужно помнить о том, что в случае возникновения аварийной ситуации будущее целой компании может зависеть от его способности максимально быстро обеспечить доступ к данным. В одной из предыдущих статей («All About Restore,» May 2002, InstantDoc ID 24340) я уже рассказывал о том, как восстанавливать базу данных поверх ее старой версии или в новое место на том же самом SQL Server. Однако иногда требуется восстановить резервную копию базы данных на новый сервер. Например, при создании автономного SQL Server в целях тестирования, обучения или отладки. Кроме того, может понадобиться восстановить базу данных SQL Server 7.0 на сервере SQL Server 2000 и в дальнейшем выполнить ее обновление с Server 7.0 на SQL Server 2000. Но в случае, когда вся система целиком оказывается под угрозой, доступ к данным необходимо предоставлять как можно быстрее.

При перемещении резервной копии базы данных SQL Server с одного сервера на другой, администратор может столкнуться с некоторыми особенностями работы SQL Server. Общая проблема — при проведении процедуры восстановления базы имена пользователей (Usernames) и имена регистрации (Login Names) могут не соответствовать друг другу. Почему же имена пользователей и имена регистрации так важны, почему их несоответствие вызывает проблему и, наконец, как использовать специальную процедуру sp_sidmap во избежание подобного рода затруднений?

Кто есть кто?

Самый важный аспект при восстановлении резервной копии базы данных в новой системе — гарантировать, что доступ к восстановленным данным имеется только у соответствующих пользователей. Решить эту задачу сложно не только технически, но и концептуально. Ведь чтобы убедиться, что к восстановленной базе и к оригинальной базе разрешается доступ одним и тем же пользователям, необходимо ясно представлять себе разницу между именем регистрации SQL Server и именем пользователя базы данных, причем и то и другое важно правильно установить до того момента, как пользователь получит доступ к данным. Одна из причин, почему различие между Login Names и Database Usernames подчас трудно уловимо, заключается в том, что даже в SQL Server Books Online (BOL) не всегда внятно объясняется фактическая разница между этими двумя уровнями доступа к системе SQL Server. Далее, чтобы разобраться в данном вопросе, нужно понимать разницу между аутентификацией системы SQL Server и аутентификацией Windows. Представление об аутентификации у многих зачастую остается расплывчатым, хотя она является определяющим понятием для проведения корректного восстановления базы данных на новом сервере.

Пользователи и процедура восстановления

Понимать, в чем разница между Login Name и Username, очень важно. Имя регистрации (Login Name) для SQL Server — это пропуск только «в прихожую» базы данных. Чтобы начать работать с данными, зарегистрировавшийся в системе пользователь должен иметь в базе данных собственное имя (Username).

Чтобы получить доступ к какому-либо объекту базы данных, с именем пользователя необходимо связать соответствующие разрешения. Дополнительная информация с разъяснением различия Login Name и Username приведена во врезке «Понятие доступа в базе данных».

Итак, почему же так важно понимать различие между именем регистрации и именем пользователя базы данных при проведении восстановления базы? Когда база данных восстанавливается, все ее данные, включая системные таблицы, копируются в новое место. В новую копию попадает в том числе и таблица Sysusers, которая содержит список правильных написаний имен пользователей и соответствующие им значения идентификаторов безопасности. Идентификаторы безопасности в Sysusers соответствуют именам пользователей на оригинальной системе. Обычно SID в таблице Sysusers соответствует SID в таблице Sysxlogins в мастер-базе оригинального сервера, и оригинальная таблица Sysxlogins также содержит имя регистрации, соответствующее данному SID. Проблема, с которой может столкнуться администратор при восстановлении базы данных в новой системе, заключается в том, что имена регистрации (Login Name) и идентификаторы безопасности (SID) исходного сервера могут отличаться от соответствующих объектов в новой системе.

Решить возникшую проблему не так просто, стоит задать себе несколько гипотетических вопросов. Если в исходной базе в таблице Sysusers имеется запись для пользователя Joe, которая соответствует имени регистрации DBAdminDomainjoe_blow, на какое имя регистрации будет отображаться joe после переноса базы в новый домен? Ведь SID, который был у joe на исходном сервере, в новом домене отсутствует. Если в новом домене уже есть такой пользователь — joe или joe_blow, — следует ли предположить, что имя пользователя joe в базе данных должно отображаться на имя joe в новом домене? Если да, то пользователю joe автоматически предоставляется доступ к серверу и базе данных, даже если до этого момента данный пользователь не имел доступа к SQL Server. Или другая ситуация — в восстановленной базе данных имеется пользователь joe, а новый сервер содержит имя Domain1joe и Domain2joe? Что делать, если ни одна запись в таблице Sysxlogins не соответствует имени регистрации в восстановленной таблице Sysusers? Давайте посмотрим, как Microsoft отвечает на поставленные вопросы.

Имена пользователей

Для решения проблем пользователей, регистрация которых проверяется средствами SQL Server, в версиях SQL Server 2000 и 7.0 используется хранимая процедура sp_change_users_login. Параметр запуска процедуры, auto_fix, автоматически обновляет таблицу Sysusers в восстановленной базе данных. Когда для идентификатора SID пользователя, прошедшего аутентификацию SQL Server, в таблице Sysusers не находится соответствия в списке Sysxlogins на новом сервере, сервер SQL проверяет, существует ли в Sysxlogins запись с тем же самым именем пользователя. Если такая запись найдена, происходит обновление SID, чтобы идентификаторы безопасности в таблицах Sysusers и Sysxlogins соответствовали друг другу. Специалисты Microsoft рекомендуют, чтобы при использовании auto_fix администратор проверил результаты работы процедуры и убедился, что список отображений выглядит так, как нужно.

Далее sp_change_users_login позволяет администратору вручную, запись за записью, установить соответствие между именами пользователей базы данных и именами регистрации. Подробное описание хранимой процедуры sp_change_users_login приводится в BOL. Причем разрешается устанавливать отображение как от пользователя к имени регистрации, так и в обратную сторону. Sp_change_users_login служит исключительно для аутентификации в SQL Server, тогда как из соображений безопасности разработчики Microsoft рекомендуют использовать аутентификацию Windows. Если последовать данной рекомендации, как можно гарантировать, что соответствие Login Name и Username в новой базе установлено верно? Летом 2001 г. спустя почти год после выхода в свет SQL Server 2000, Microsoft предоставила нужный инструмент: набор из двух хранимых процедур, которые анализировали и изменяли таблицу Sysusers в восстановленной базе данных. Те, кто полностью разобрался в особенностях Username и Login Name SQL Server и уже имеет опыт непосредственного обновления системных таблиц, могут попробовать самостоятельно разработать подобные решения. Как описано в статьях «INF: How to Resolve Permission Issues When a Database is Moved Between SQL Servers» (http://support.microsoft.com/default.aspx?scid=kb;en-us;q240872) и «SAMPLE: Mapsids.exe Helps Map SIDs Between User and Master Databases When Database is Moved» (http://support.microsoft.com/default.aspx?scid=kb;en-us;q298897), разработчики Microsoft на 90% решили данную проблему. Поскольку предложенное в названных статьях решение нельзя считать полным, администратор SQL Server все же должен самостоятельно разобраться во взаимосвязи Username и Login Name. В статье «INF: How to Resolve Permission Issues When a Database is Moved Between SQL Servers» рассказывается об использовании и ограничениях двух новых хранимых процедур Microsoft — sp_sidmap и sp_prefix_sysusersname. Статья «SAMPLE: Mapsids.exe Helps Map SIDs Between User and Master Databases When Database is Moved» отсылает читателя к самораспаковывающемуся zip-файлу, в котором содержится исходный код двух новых процедур от Microsoft и readme-файл.

Поскольку Microsoft разместила в названных статьях и readme-файле огромный объем документации, нет нужды объяснять в деталях, как использовать эти хранимые процедуры. Но о наиболее важных функциях и о нескольких ограничениях я расскажу. Самая важная из представленных процедур — sp_sidmap. Она может заменить более раннюю хранимую процедуру sp_change_users_login, описанную выше. Sp_sidmap создает новые имена регистрации, аутентифицируемые сервером SQL Server, и устанавливает соответствие с любыми «потерянными» именами в восстановленной базе данных. Для установления соответствия с «потерянными» именами регистрации Windows процедура sp_sidmap дополнительно создает в восстановленной базе новые имена регистрации, аутентификация которых проходит в Windows. Sp_sidmap способна различить пользователей, проверка которых выполняется в SQL Server и в Windows, обращаясь к полю isntname в таблице Sysusers. Если значение isntname равно 1, запись такого пользователя относится к Login Name, проверяемому Windows; если значение равно 0, то запись в Sysusers относится к так называемой роли Database Role — к специальному пользователю, например guest, или к пользователю, проверяемому сервером SQL.

Вторая хранимая процедура, sp_prefix_sysusersname, используется только тогда, когда Username базы данных может отождествляться с двумя возможными именами регистрации (аутентификация Windows) в двух разных доменах или на двух разных машинах. Когда администратор запускает sp_sidmap, процедура сообщает о наличии подобной ситуации и перечисляет Username и «кандидатов» из числа Login Name. После чего можно запустить _prefix_sysusersname и указать префикс во избежание неоднозначности подключения к SQL Server.

При запуске sp_sidmap следует соблюдать осторожность и правильно указать все четыре параметра: имя домена, в котором находится исходная база данных, имя домена, в котором находится восстановленная база, имя сервера, на котором установлена исходная база, и имя сервера, на котором база данных восстановлена. Большая часть работы sidmap связана с поиском в таблице Sysusers исходного домена или имен компьютеров и заменой их новыми именами. Если пользователь на новом сервере в таблице Sysxlogins уже имеет соответствующее имя, процедура обновляет таблицу Sysusers правильным SID. Если для пользователя еще нет соответствующего Login Name, но сам пользователь существует в целевом домене или целевом сервере, процедура sp_sidmap создаст нового пользователя в Windows (аутентификация Windows). Например, если в восстанавливаемой базе данных есть пользователь OldDomainMary, а на новом сервере имеется Login Name вида NewDomainMary, хранимая процедура sp_sidmap обновит запись в Sysusers для OldDomainMary на NewDomainMary и SID в Sysusers для установления соответствия SID в записи Sysxlogins. Если же NewDomainMary в Sysxlogins отсутствует, но в NewDomain имеется пользователь операционной системы Mary, процедура sp_sidmap создаст нового пользователя NewDomainMary и внесет те же самые исправления в таблицу Sysusers. Проблема возникает только в том случае, если в NewDomain нет подходящего пользователя (Username). Тогда sp_sidmap выдает сообщение об ошибке.

В хранимой процедуре sp_sidmap есть недокументированная особенность: коль скоро в таблице Sysxlogins находится подходящее имя регистрации, процедура не спрашивает — относится ли оно к домену или прописано на локальной станции. Если администратор домена до этого удалял имя NewDomainMary, имея в виду пользователя домена, то же самое имя не будет автоматически удаляться из таблицы Sysxlogins на сервере SQL. А поскольку это имя существует в Sysxlogins, процедура sp_sidmap станет использовать это имя и соответствующий SID (ставший теперь бессмысленным) для обновления восстановленной таблицы Sysusers. Если SQL Server сконфигурирован для работы только в режиме Windows-Authentication Mode, «оригинальная» учетная запись Mary из старого домена уже не может быть зарегистрирована в восстановленной базе данных, а sp_sidmap ничего не сообщит о том, что Mary больше не имеет доступа к базе.

В статье «INF: How to Resolve Permission Issues When a Database is Moved Between SQL Servers» специалисты Microsoft строго рекомендуют перед запуском sp_sidmap убедиться, что все учетные записи пользователей в базе данных имеются и в домене или на локальном сервере, и всем им обеспечен доступ к SQL Server, т. е. для них есть свои записи в таблице Sysxlogins. С одной стороны, такая рекомендация не вызывает возражений. Однако если у вас на самом деле должны быть пользователи (Username), для которых не нужно устанавливать соответствия с Login Name, хранимая процедура sp_sidmap в одиночку с подобной ситуацией не справится. Например, если на исходном сервере есть OldDomainCharlie (Login Name), которому соответствует carlos (Username), возможно два решения. Согласно установке Microsoft, администратор базы данных (DBA) должен вручную обновить таблицу Sysusers после запуска sp_sidmap; однако применять такое решение я бы не советовал до тех пор, пока администратора устраивает обновленная системная таблица. Microsoft рекомендует до запуска sp_sidmap перенести во временную базу все объекты, владельцем которых является Charlie удалить учетную запись пользователя carlos, добавить ее для пользователя Charlie и затем перенести обратно все объекты, ранее принадлежавшие пользователю carlos.

Такое решение кажется несколько сложным, особенно с учетом того, что существует хранимая процедура sp_changeobjectowner. Вместо того чтобы использовать описанное выше решение, DBA может просто добавить нового пользователя Charlie в базу данных и воспользоваться sp_changeobjectowner для изменения владельца всех объектов carlos на Charlie. Затем учетная запись для carlos удаляется — до того момента, как начнется резервное копирование базы. Конечно, ни решение Microsoft, ни мой подход не рассматривает ситуацию, когда требуется оставить пользователя carlos в базе, хотя имя регистрации — Charlie. Если такая путаница в именах никому не мешает, нужно без промедления обновить системные таблицы после завершения работы процедуры sp_sidmap. Тогда все прежние пользователи смогут подключиться к базе данных под своими именами регистрации.

Операции с dbo

Документация на sp_sidmap не содержит никаких упоминаний об особенностях работы этой хранимой процедурой с dbo. Если восстановленная база данных в качестве владельца имеет пользователя, прошедшего аутентификацию Windows, при запуске sp_sidmap система выдаст сообщение об ошибке для dbo, поскольку данная хранимая процедура обращается к имени регистрации, которое соответствует имени пользователя, а такое имя регистрации — dbo — отсутствует. Самое правильное решение — убедиться, что пользователь Sa является собственником всех баз данных, перенесенных на новую систему. Таким образом, владельцем баз данных становится пользователь, прошедший аутентификацию сервером SQL, а бороться с этим после переноса базы данных уже гораздо легче. Поскольку на новом сервере всегда имеется запись для регистрации Sa и, следовательно, тот же самый SID, что и на исходном сервере, проблем в работе хранимой процедуры sp_sidmap и определении соответствия имени регистрации и имя пользователя для dbo быть не должно.

Обычные меры предосторожности

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

Несоответствие Username и Login Name — пожалуй, самая типичная проблема, возникающая при переносе базы данных SQL Server на новое место. Если очевидно, каковы последствия упомянутого несоответствия и как можно избежать этой проблемы, то, скорее всего, инструментом для ее решения будет служить хранимая процедура sp_sidmap.


Понятие доступа к базе данных

Как уже упоминалось в основной статье, обладание Login Name для SQL Server — это лишь начало работы с SQL Server. Чтобы получить доступ к базе данных, пользователь должен получить собственное имя — Username — в этой базе, а чтобы начать работать с объектами базы, имени пользователя — Username — необходимо поставить в соответствие определенные разрешения. Кроме того, именем пользователя — Username — «маркируется» свойство Object Ownership — владелец объекта. Если пользователь базы данных имеет разрешение создавать объект и сделает это, именно он становится его владельцем. Например, если пользователь базы Sue имеет разрешение создавать таблицу и воспользуется этим правом, всякий, кто захочет обратиться к созданной таблице, обязан указать имя ее владельца явным образом — например, так: SELECT * FROM sue.NewTable.

Подчеркну еще раз — необходимо раз и навсегда понять, что такое Username, чтобы никогда не сталкиваться с серьезными проблемами при проведении операций восстановления. Я покажу на небольших примерах, как администратор SQL Server предоставляет пользователю доступ к базам данных, как SQL Server хранит имена пользователей в таблице sysusers каждой из баз и, наконец, как использовать специальное имя пользователя — dbo.

Для предоставления доступа к базе данных администратор использует хранимую процедуру sp_grantdbaccess или ее эквивалент в Enterprise Manager. Необходимо запустить эту процедуру в той базе данных, к которой планируется предоставить доступ пользователю, зарегистрированному под именем Login Name. Хранимая процедура Sp_grantdbaccess имеет два параметра, но, поскольку второй параметр указывать не обязательно, большинство администраторов знают только о существовании первого. Первый параметр — это имя регистрации пользователя, его Login Name. Второй параметр — это имя, которое будет применяться зарегистрированным пользователем при работе с базой данных, другими словами, это Username базы для данного Login Name пользователя. Если второй параметр опустить, считается, что Login Name и Username совпадают. Так как многие ничего не знают о втором параметре, то получается, что оба имени становятся одинаковыми, о чем администраторы подчас даже не догадываются. Но этот момент чрезвычайно важен. Вот пример трех операторов, с помощью которых предоставляется доступ к базе mydb:

USE mydb

EXEC sp_grantdbaccess [DBAdminDomainjoe_blow]

EXEC sp_grantdbaccess [MyPrivateMachinesue_hoo], sue

EXEC sp_grantdbaccess andre

В самом первом операторе сообщается, что пользователь, прошедший аутентификацию Windows, получает доступ к базе данных mydb. Поскольку второй параметр не указан, Username в этой базе данных будет совпадать с Login Name — DBAdminDomainjoe_blow. Во втором операторе пользователь, прошедший аутентификацию Windows как MyPrivateMachinesue_hoo, получает доступ к mydb, и с базой он работает через имя — Username — Sue. В третьем операторе SQL Server предоставляет доступ к данным пользователю Andre — тому же имени, под которым этот пользователь зарегистрировался в Windows.

Надо знать, что по умолчанию имя регистрации не всегда совпадает с именем пользователя, прошедшего аутентификацию Windows. Если открыть каталог Security в Enterprise Manager, вызвать контекстное меню Login Name и выбрать вкладку Database Access, на которой добавляются новые пользователи базы, то станет очевидно, что по умолчанию имя базы данных начинается с префикса домена, а именно: имени joe_blow на самом деле соответствует имя DBAdminDomainjoe_blow. Один разработчик SQL Server из Microsoft сообщил мне в частной беседе, что этот факт — отражение не вполне корректного кодирования Enterprise Manager, но он затрудняется сказать, нужно ли считать это ошибкой. В следующей версии SQL Server (кодовое имя — Yukon) Microsoft может изменить соответствующий фрагмент кода Enterprise Manager. Если в имени пользователя базы данных исключить префикс домена, администратор становится хозяином положения и в явном виде может набрать префикс домена. Далее необходимо выбрать в Enterprise Manager каталог с какой-либо конкретной базой данных и затем подкаталог Users внутри этой базы. Для любого имени Login Name, прошедшего аутентификацию Windows, которое администратор добавит в качестве имени базы — Username, будет всегда присутствовать префикс домена. Наверное, легче работать с именами типа joe_blow, чем с DBAdminDomainjoe_blow, для которого при работе с операторами T-SQL всегда необходимо использовать разделитель. Но будет все же правильно, если Microsoft исправит некорректность в кодировке Enterprise Manager, и результат по умолчанию для Enterprise Manager и для операторов T-SQL будет одинаковым. И если используется «смесь» имен, имена регистрации (Login Name) и имена базы (Username), то сохранение префиксов доменов в именах пользователей позволит быстрее разобраться, кто из них «пришел» в базу после аутентификации Windows, а кто — через аутентификацию SQL Server.

Понятие sysusers

Сервер SQL хранит имена пользователей в таблице Sysusers. Администратор может предоставить DBAdminDomainjoe_blow доступ к базе Pubs, указав имя DBAdminDomainjoe_blow, к базе Northwind, указав имя joeB, и отобрать у пользователя разрешения на доступ к базе Inventory. Такая установка будет означать, что в некоторой строке в таблице sysusers в базе Pubs (pubs..sysusers) появится имя DBAdminDomainjoe_blow, которое будет отображено в строку sysxlogins для DBAdminDomainjoe_blow. В таблице Northwind..sysuysers появится запись о joeB, которая также будет отображена на запись sysxlogins для DBAdminDomainjoe_blow. А в таблице Inventory..sysusers не будет записей, отображенных на записи sysxlogins для joe.

Таблица Sysusers использует поле sid для установки отображения на таблицу Sysxlogins. Если вход в систему выполнен через аутентификацию Windows, значение поля sid совпадает с идентификатором безопасности (Security ID, SID), которое операционная система генерирует в момент создания пользователя. Если вход в систему произошел через аутентификацию SQL Server, сервер SQL будет генерировать собственное значение для поля sid — в тот момент, когда администратор SQL Server создает новое имя входа в базу данных. Когда SQL Server выполняет хранимую процедуру sp_helpuser, можно выяснить, какому Login Name соответствует какой пользователь. Это происходит путем объединения таблиц Sysusers и Sysxlogins по полю sid. Существует одно исключение — когда нет ни одного соответствия в таблице Sysxlogins. Если администратор задействует аутентификацию Windows для предоставления доступа к SQL Server целой группе пользователей операционной системы, отдельный пользователь — член этой группы — имеет доступ к базе даже в том случае, когда для него нет в таблице Sysxlogins отдельной записи. Например, если право регистрации в SQL Server предоставлено группе MyDomainSQLUsers и некий пользователь (его имя регистрации — sam) является членом группы SQLUsers, я могу при помощи следующего оператора предоставить sam доступ к базе данных:

EXEC sp_grantdbaccess [MyDomainsam]

В таблице Sysusers появится запись MyDomainsam, которая будет содержать SID операционной системы. В таблице Sysxlogins при этом не будет записи, стыкующейся с данным значением SID; в Sysxlogins окажется только запись о группе SQLUsers. И всякий раз, когда пользователю sam понадобится обратиться в базу, SQL Server будет проверять, по-прежнему ли sam является членом группы SQLUsers.

Пользователь dbo

В каждой таблице Sysusers имеется запись для специального пользователя с именем dbo. Dbo отображается на определенную запись таблицы Sysxlogins — запись с указанием «точки входа» на сервер (Server Login) владельца базы данных. Значение этой записи часто совпадает с именем регистрации Sa или именем регистрации пользователя, назначенного на роль системного администратора, — Sysadmin Fixed Server Role. Альтернативный подход — владелец базы данных может принадлежать встроенной группе операционной системы, такой, как BUILTINAdministrators (которая имеет собственную запись в таблице Sysxlogins). Тогда отображение между каким-то отдельным владельцем базы данных и Sysxlogins будет отсутствовать. Однако нежелательно, чтобы владелец базы данных был одновременно администратором системы. Например, можно предоставить sue разрешение на создание базы и вынудить ее воспользоваться данным разрешением или же с помощью хранимой процедуры sp_changedbowner для выбранной базы данных поменять владельца и назначить на эту роль sue. Добавлю, что не имеет значения — кто именно отображается из Sysusers в Sysxlogins для пользователя dbo: всякий, кто регистрируется на SQL Server с помощью имени регистрации Sa или любого имени, принадлежащего группе Sysadmin Fixed Server Role, будет обладать полномочиями dbo используемой базы. Эта привилегия — часть полномочий администратора. Таблица Sysusers может показать, что dbo отображается на имя регистрации sue, но каждый администратор также имеет полномочия dbo, что легко проверить с помощью Query Analyzer. Если администратор зарегистрировался как Sa или как администратор и использует базу данных, у которой владелец не является администратором, приведенная ниже инструкция подтвердит, независимо от действительно назначенного владельца, что как раз имя администратора является dbo:

SELECT user_name()

Поскольку любой администратор с любым именем всегда dbo, то когда бы он ни использовал базу данных, любой созданный им объект получают владельцы dbo по умолчанию. В отличие от ситуации, когда таблицу создает sue, к таблице (или другому объекту), созданному dbo, может обратиться любой желающий без указания имени владельца. Однако специалисты Microsoft рекомендуют, чтобы имя владельца всегда было составной частью имени объекта и всегда, когда это можно сделать, при ссылках на объект использовалось имя его владельца.

Обычно можно считать, что dbo означает просто владелец базы данных. Понять разницу между именем пользователя dbo и владельцем базы данных проще, если представлять себе dbo как некое специальное имя пользователя. Имя регистрации Sa никогда не означает имя пользователя — это всегда имя регистрации. Таким образом, оператор

SELECT user_name()

никогда не возвратит значение Sa, и Sa не может быть владельцем какого бы то ни было объекта базы данных — владеть объектом может только пользователь (через указание его имени).

Кэлен Дилани — независимый консультант и инструктор по SQL Server. Имеет сертификаты MCT и MCSE. С ней можно связаться по адресу: kalen@sqlmag.com.

Поделитесь материалом с коллегами и друзьями