Perl для системного администрирования

       

Документирование сервера


Очень много времени и энергии уходит на настройку SQL-сервера и объектов, расположенных на нем. Способ документирования подобной информации может пригодиться в ряде ситуаций. Если база данных будет повреждена, а резервной копии не окажется, вам придется восстанавливать все ее таблицы. При необходимости перенести данные с одного сервера на другой важно знать конфигурацию источника и приемника данных. Даже для собственных баз данных может пригодиться возможность просмотреть карту таблиц.

Чтобы передать всю «прелесть» непереносимой (nonportable) природы администрирования баз данных, приведу пример реализации одной простой задачи для трех различных SQL-серверов с использованием как DBI, так и ODBC. Каждая из этих программ делает одно и то же: выводит список всех баз данных на сервере, их таблицы и структуру каждой таблицы. Эти сценарии можно очень легко расширить для предоставления более подробной информации о каждом объекте. Например, бывает полезно узнать, в каких полях есть значения ti'J.; или NOT NULL. Вывод всех трех программ выглядит одинаково:

— sysad'ii — hosts

i.ame [c:;ar(30)J ipaddr [char(lb)

aliases [char(50)]

owner [char(40)J

dept [char(15)]

bldg [char(10)]

room [char(4)]

manuf [char(10)J

model [char(10)] —hpotter—

customers

cid [char(4)J



cname [varchar(l3)]

city [varchar(20)J

discnt [real(7>] agents

aid [char(3)]

aname [varchar(13)]

city [varchar(20)]

percent [int(10)] products

pid [char(3)]

pname [varchar(13)]

city [varchar(20)]

quantity [int(10)]

price [real(7)] orders

ordno [int(10>]

month [char(3)]

cid [char(4)]

aid [char(3)]

pid [char(3)]

qty [int(K))]

dollars [real(7)

Сервер MySQL и DBI

Вот как выглядит способ получить эту информацию с сервера MySQL с использованием DBI. Существующее в MySQL дополнение команды SHOW очень упрощает эту задачу:

use DBI;

print "Введите имя пользователя: ";

chomp($user = <STDIN>);

print "Введите пароль для $user: chomp($pw = <STDIN>)

Sstart = "mysql"; tf первоначально будем подсоединяться к этой базе данных




О соединяемся с базой данных

$dbh = DBI->connect("DBI:mysql:$start".$user.$pw);

die "Невозможно соединиться: ".$DBI::errstr."\n"

unless (defined $don): ft ищем базы данных на сервере

$sth=$doh->prepartj(q;SHOW DATABASES}) or

die "Невозможно подготовить запрос show dataoaset; ".

die "Невозмохо заполнить запрос

push(@dcs.$a'-e*->[0]): > $sth->finish;

# ищем таблица в каждой базе данных foreach $db (!g>dbs) { print "---$db---\n";

$sth=$dbh->prepare(qq{SHOW TABLES FROM $db}) or

die "Невозможно подготовить запрос

show tables: ". $dbh->t:r rs*

die "Невозможно выполнить запрос show tables: ".

$dbh->crr:

(g>tables=();

while (Saref = $sth->fetchrow_arrayref) {

push(Stables,$aref->[0]); }

$sth->finish;

tt

ищем информацию о полях для каждой таблицы

foreach Stable (^tables) { print "\t$table\n":

$sth=$dbh->prepare(qq[SHOW COLUMNS FROM Stable FROM Sob!) o'

die "Невозможно подготовить запрос show columns: ". $cihh-^errstr."\n";

$sth->execute or die "Невозможно выполнить запрос show columns: ".

while (Saref = $sth->fetchrow_arrayref) {

print "\tAt".Saref->[0]." [". $aref->[1 ]. "]\n":

$stn->finisn \ I

} Sdbr->d;.scor/iec::

Добавим несколько комментариев к приведенной программе:

  • Мы соединяемся с начальной базой данных только для того, чтобы удовлетворить принятой в DBI семантике соединения, но такой контекст возникает не обязательно благодаря командам.


  • Если читатель думает, что команды подготовки и выполнения запросов SHOW TABLES и SHOW COLUMNS являются отличными кандидатами на использование заполнителей, то он совершенно прав. К сожалению, именно эта комбинация DBD драйвера /сервера не поддерживает заполнители в таком контексте (по крайней мере, это было так в период написания данной книги). В следующем примере мы столкнемся с подобной ситуацией.



  • Имя пользователя базы данных и его пароль запрашиваются интерактивно,

    поскольку альтернативы (прописывание их в коде или передача в командной строке, при которых любой, кто просматривает список процессов, сможет их увидеть) еще хуже. В данном случае символы пароля будут отображаться при вводе. Для большей осторожности стоит применять что-то подобное Те г тт. : Reaakey, чтобы подавить отображение символов.




  • Сервер Sybase и DBI



    В этом подразделе представлен аналог для Sybase. Внимательно просмотрите программу, а после этого поговорим о некоторых существенных моментах:

    use DBI;

    print "Введите имя пользователя: "; chomp($user = <STDIM>);

    print "Введите пароль для $user: "; chomp($pw = <STDIN>);

    $dbh = DBl->connect('dbi:Sybase'',Suser,$pw);

    die "Невозможно соединиться: $DBI::errstr\n" unless (defined $dbh);

    ищем базы данных на сервере

    $sth = $dbh->prepare(q{SELECT name from master dbo.sysdatarases}) cr

    die "Невозможно подготовить запрос к sysdatabases: ".

    $db'i->er rst r . "\n", $stli->oxecute or

    die "Невозможно выполнить запрос к sysdarabases: '.

    $dori->errstr. "\п";

    while (Sarof = $sth->fetchrow_arrayref) (

    push((3dbs, $aref->[0]): }

    $sth->finisn:

    foreach $cm (Mbs) {

    $dbh->do("USE $do") or

    die "Невозможно использовать $db: ".

    ®tables=():

    while ($агч'( - $.::;->fotchrow_arrayref) {

    die "Невозможно изменить Sdb: ".

    S'Jor->err-str."' n":

    # ищем поля для каждой таблицы foreach Stable (tables) { print "\t$table\n";

    $sth=$dbh->prepare(qq{EXEC bp_colunns Stable}) or

    die "Невозможно подготовить запрос sp^columns ", $obh-:-err.v

    $sth->execute or

    die "Невозможно выполнить запрос sp^columns: ".$dbh->errstr.

    while ($aref = $sth->fetchrow_arrayref) {

    print "\t\t",$aref->[3]," [",$aref->[5],"(",

    $aref->[6],")]\n": }



    $sth->finish; ! }

    $dbh->discohnect or warn "Невозможно отсоединиться: ".

    $dbh->errstr."\n";

    Вот обещанные заметные моменты:

  • Sybase хранит информацию о базах данных и таблицах в специальных системных таблицах sysdatabases и sysobjects. Каждая база данных содержит таблицу sysobjects, в то время как сервер хранит обобщенную информацию о них в одной таблице sysdatabases, расположенной в основной базе данных. Мы используем более явный синтаксис databases, owner, table в первом запросе SELECT, чтобы недвусмысленно обратиться именно к этой таблице. Для перехода к sysobjects каждой базы данных можно применять этот же синтаксис, вместо того чтобы явно переключаться между ними при помощи USE. Более того, как и при переходе в каталог средствами cd, такой контекст упрощает написание других запросов.


  • Запрос SELECT к sysobjects применяет ключевое слово iVHE-L, чтобы вернуть информацию только о пользовательских таблицах. Это было сделано для ограничения размера вывода. Если бы мы хотели включить также и все системные таблицы.


  • Складывается впечатление, что заполнители в DBD: : Sybase реализованы так для того, чтобы препятствовать их употреблению с хранимыми процедурами. Будь реализация другой, следовало бы использовать заполнители в EXEC sp_columns.




  • Сервер MS-SQL и ODBC



    Наконец, вот код для получения той же информации с сервера MS-SQL через ODBC. Заметьте, что применяемый синтаксис SQL практически идентичен предыдущему примеру благодаря связи Sybase/MS-SQL. Интересны отличия между этим примером и предыдущим:

  • Использование DSN, которое предоставляет нам контекст базы данных по умолчанию, так что нет необходимости указывать, где искать таблицу

    sysdatabases.


  • Употребление $dbh->DropCursor() в качестве грубой аналогии $sth-fi-nish.


  • Неудобный синтаксис, который приходится применять для запуска хранимых процедур. Информацию о хранимых процедурах и других подобных аномалиях ищите на веб-страницах по Win32: : ODBC.


  • Вот как выглядит программа:



    use Win32::ODBC;

    print "Введите имя пользователя: ";

    chomp($user = <STDIN>);

    print "Введите пароль для $user: "; chomp($pw = <STDIN>);

    $dsn="sysadm"; и имя источника данных, которое мы используем

    # ищем доступные DSN, создаем переменную $dsn,

    если она еще не существует

    die "Невозможно запросить доступные DSN",Win32::ODBC::Error()."\n"

    unless (%dsnavail = Win32::ODBC::DataSources());

    if (Idefined $dsnavail{$dsn}) {

    die "невозможно создать DSN:".Win32::ODBC::Error()."\n"

    unless (Win32::ODBC::ConfigDSN(ODBC_ADD_DSN. "SQL Server", ("DSN=$dsn",

    "DESCRIPTION=DSN for PeriSysAdm",

    "SERVER=mssql.happy.edu". "DATABASE=master",

    "NETWORK=DBMSSOCN".

     библиотека сокетов TCP/IP ))); }

    it

    соединение с основной базой данных

    $dbh = new Win32: :ODBC("DSN=$dsn;UID=$iJSer:PWD=$pw: "):

    die "Невозможно соединиться с DSN $dsn:".Win32:

    # ищем базы данных на сервере

    if (defined $dbh->Sql(q{SELECT name from sysdatabases})){

    die "Невозможно послать запрос к базе данных:" .Win32: :ODBC: Error().

    while ($dbh->FetchRow()){

    push(@dbs, $doh->0ata("name"));

    }

    $dbh->DropCursor();

    п

    ищем пользовательские таблицы в каждой базе данных

    foreach $db (@dbs) {

    if (defined $dbh->Sql("use $db")){

    die "Невозможно изменить базу данных на $db:" .

    Win32::ODBC::Error() . "\n"; >

    print "---$db---\n"; @tables=(); if (defined $dbh->

    Sql(q{SELECT name from sysobjects

    WHERE type="U"})){ die "Невозможно запросить таблицы из $db:" .

    Win32::ODBC::Error() . "\n"; } while ($dbh->FetchRow()) {

    push(@tables,$dbh->Data("name")); > $dbh->DropCursor();

    ft ищем информацию о полях для каждой таблицы

     foreach Stable (©tables) { print "\t$table\n";

    if (defined $dbh->Sql(" {call sp_columns (\'$table\')} ")){

    die "Невозможно запросить поля из таблицы Stable:".

    Win32::ODBC::Error() . "\n"; >

    while ($dbh->FetchRow()) { @cols=();

    @cols=$dbh->Data("COLUMN_NAME","TYPE.NAME","PRECISION");

    print "\t\t",$cols[0]," [",$cols[1],"(",$cols[2],")]\n";

    } $dbh->DropCursor();

    }

    }

    $dbh->Close();

    "SQL Server","DSN=$dsn"))


    Содержание раздела