Филиппика против баз данных
Базы данных (БД) противоречат идеологии построения глобальной сети. Суть этой идеологии в том, что при ликвидации или нарушении функциональности части целое, состоящее из подобных частей, не перестает функционировать. То есть целое представляет собой набор некоторых элементов, каждый из которых не является основным. Если мы совершаем удачную хакерскую атаку на один сервер (или на десять), сам по себе интернет не перестает работать, хотя бы сервер был Яндексом или Гуглом. Принцип баз данных как раз обратный: вся информация хранится в одном гигантском файле, доступ к разным частям которого и дает нужные фрагменты данных. Вам никогда не приходилось встречать такие заявления на сайте:
«В настоящий момент база данных недоступна» или «Не удается подключиться к базе данных»?
Рассмотрим проблему под разными углами.
Базы данных являются замечательным средством хранения данных. Современные электронные базы данных, кроме того, предоставляют мощный и удобный инструментарий для работы с этими данными, а именно — язык запросов SQL. Благодаря этому языку возможно не только добавлять, удалять, просматривать и редактировать данные, но и представлять их в разной форме. Чего стоят только возможности сортировки, ассоциации разных таблиц базы друг с другом и — одно из самых замечательных свойств — возможность обращения только к части данных. С текстовыми файлами так работать нельзя: можно только прочитать весь файл, и на основе его делать выборку значений, либо читать файл до первого (второго, третьего) вхождения — здесь нет стандартизированного средства запросов к фрагментам файлов.
Представим ситуацию. Нам необходимо размещать на сайте информацию о трех тысячах книг в магазине букинистической литературы. Для выполнения этой задачи есть два пути: записывать данные в БД или в файлы.
Сценарий первый. Открывается база данных, в ней создается таблица с несколькими полями: автор, название, год издания, издательство, тип переплета, бывший владелец, количество на складе. Пишется сравнительно небольшой код на языке серверных сценариев, который позволяет посетителям просматривать книги по списку авторов, расположенных в алфавитном порядке, по алфавитному списку заголовков, по году издания и так далее: вариантов сортировки результатов может быть море. Другой сценарий позволяет добавлять информацию в базу данных, причем разработчик предусмотрительно написал код для кнопки «Добавить ко всем книгам информацию нового типа», чтобы создавать новые поля неизвестного заранее типа:
Alter table [имя таблицы]
Add [имя нового поля] char(50);
Сценарий второй. Разработчик понимает, что информацию о 3000 книг в одном текстовом файле держать неудобно, потому что для поиска сведений о нужных книгах каждый раз сценарий будет его открывать, записывать в строку или, того хуже, в массив (массивы потребляют много ресурсов интерпретаторов языка), а только потом обрабатывать и извлекать данные. Страшно подумать, как это будет медленно работать и из-за скольких нелепых случайностей текстовая БД будет стираться напрочь. Возникает резонный вопрос: по какому принципу разделять информацию на 20 файлов, чтобы при этом скрипт-анализатор сразу обращался к нужному файлу? По фамилии автора? А если у него не было фамилии? (В самом деле, какие фамилии были у Платона и Гомера?) Тогда по первой букве названия книги? Но тогда при поиске сценарий все равно будет считывать все файлы, чтобы найти нужный кусочек текста. А если пойти на крайнюю меру и каждую книгу записывать в отдельный файл? Нет, это уже слишком. Останавливаемся на варианте «30 файлов для начала, в каждом не более 100 записей, при создании новых записей создается новый файл еще на сто записей». Файлы — формата XML или простые текстовые с разделителями для имитации полей БД. От считывания файлов в строки, разделения их на фрагменты по разделителям и занесения фрагментов в массив никуда не деться. Для сортировки пишется длинный и нудный сценарий; подходящего готового в интернете не нашлось, потому что все предпочитают работать с базами данных.
Но вот все готово. Приходит руководство, одобрительно изучает сайт и говорит:
— Да, вот еще что я забыл. У каждой книги должно быть еще указано, когда она поступила в магазин.
Продолжение первого сценария. Разработчик нажимает на кнопку «Добавить ко всем книгам информацию нового типа», называет вновь созданное поле (которое, разумеется, появляется у каждой книги) «Дата поступления в магазин», покупает коробку шоколадных конфет и за такую нехитрую взятку просит знакомую девушку набрать даты в поля, объяснив, какую кнопку после этого нажимать.
Продолжение второго сценария. Директор уходит, грезя электронным магазином, а разработчик шипит нехорошие слова, рвет волосы, покупает пиво и снова садится за код. Он пишет еще фрагмент администраторского сценария, который позволит добавить в каждый
Филиппика против баз данных |
4.8 |
Из тридцати файлов в конце каждой строки еще по одному разделителю, а после него теоретические новые данные. Увлекательную работу по ручному набору дат он оставляет на закуску. Затем — еще кусок сценария для пользователей, чтобы они могли сортировать данные по дате поступления в магазин (именно так наверху рейтинга появляется информация о новых поступлениях). После этого ему приходит в голову, что директор придумает еще одно поле, и тогда придется делать еще одно дополнение. Он скрипит зубами, переписывает фрагмент «админки», чтобы можно было добавлять новые разделители и новые данные через веб-интерфейс. Все готово. Остались даты. Но за окном уже ночь, одна знакомая девушка уже спит, а вторая ужинает в кафе за одним столиком с любителем баз данных... Все следующие сайты разработчик уже строит с помощью БД. Вывод один: базы данных незаменимы для больших объемов однотипных данных. Или небольших объемов, которые со временем грозятся вырасти в большие. Объемов, которые можно структурировать, делать из них выборки, сортировать в разной последовательности. Но в некоторых случаях работа с файлами оказывается удобнее. Чаще всего с файлами работать логичнее, когда структура различных страниц отличается друг от друга. Например, одна страница является сценарием и «обслуживает» порядка двухсот страниц, видимых посетителю (то есть это посетитель думает, что страниц двести, а на самом деле страница одна, только с разным содержимым). Еще тридцать страниц являются текстовыми файлами, содержащими авторские произведения, без какой бы то ни было разметки (просто первая строка у них отвечает за заголовок, остальные разбиваются на абзацы скриптом), которые приводятся в нужный вид общим для них сценарием и добавляют сведения об авторстве. Наконец, один файл представляет собой текстовую базу данных: ради двадцати или тридцати строк полновесную БД заводить неудобно, а текстовый файл такого объема анализируется мгновенно. Текстовые БД часто используются в компактных форумах, в служебных разделах сайта и в других местах, где нужно структурировано хранить сравнительно небольшие объемы информации. По типу организации данных (и надежности) текстовые базы данных можно разделить на три типа. Во-первых, это простые списки данных, каждый фрагмент на новой строке (файл login. txt): Anna Vladimir *Toxa* Solnce Debil |
Cool Girl =Beast=
Serge
Таким списком, например, может быть заполнен файл текстовой БД для беспаролевого чата. Любой скрипт способен «пробежаться» по такому файлу очень быстро и создать массив, с которым можно оперативно работать:
<?
$logins = File("login. txt"); // Создаем массив echo "Все зарегистрировавшиеся: ";
Foreach($logins as $value) і
Echo $value."<br />"; // Вывод всего списка і
?>
Чуть сложнее работать со вторым типом текстовых БД, условно — «текстовые БД с разделителями». Мы захотели сделать в чате пол участника, женский (f), мужской (m) или без указания пола (n) — файл gender. txt
Anna = f Vladimir = m *Toxa* = m Solnce = n Debil = n Cool Girl = f -=Beast=- = m Serge = m
Сценарий не просто заносит в массив каждую строку, но и разбивает ее на имя пользователя и пометку о поле:
<?
$logins = File("login. txt");
// Создаем массив
$gender transform = array("f" => "девушка",'^" => "парень","п" => "существо неясного пола"); echo "Все зарегистрировавшиеся: ";
Foreach($logins as $value) і
List($name,$gender) = explode(" = ",$value);
// Разбивка на имя и пол
Echo $name.", ".$gender transform[$gender]."<br />";
// Вывод всего списка с указанием пола уже по-русски
}
?>
Тут же вскрывается слабое место подобных текстовых БД. Ведь кому-то из пользователей придет в голову использовать в имени знак равенства с пробелами по бокам, который является разделителем. Значит, о такой ситуации нужно позаботиться заранее и при регистрации запретить использование данной последовательности символов.
Наконец, третий тип основан на XML.
По сравнению с текстовыми БД с разделителями этот тип более стабилен с точки зрения возможности запросов информации, его структура более наглядна, и значит, с ней проще работать. Минус же состоит в том, что объем этих файлов больше. Для наглядности рассмотрим один пример. Допустим, в 2007 году сделано 2 сайта, а в 2006 — только один. Данные нужно представить примерно в таком виде:
Работы за 2007 год (2 шт.)
Меловой период Www. cretaceous. ru
Геологический сайт
Сервер задач backstage. erlang. com. ru
Секретный сайт
Работы за 2006 год (1 шт.)
Консалтинг+ Www. konsplus. com
Юридические услуги
Такую верстку несложно сделать вручную, поскольку работы всего три. Но если количество работ увеличивается как минимум каждые две недели и не ограничивается двумя годами, «ручная работа» перестает оправдывать себя. При изменении данных в одном фрагменте портфолио нужно вручную менять данные во всех остальных фрагментах такого же типа. Если определенный класс работ нужно на время скрыть, то приходится делать копию файла с полным списком, а урезанный файл загружать на сервер. Если потребуется использовать фрагмент данных (последняя работа; самая первая работа; данные за определенный год), то эти данные придется переписывать вручную.
При использовании базы данных любого типа (кроме самой простой текстовой, а которой шла речь чуть раньше) эта проблема снимается, поскольку еще не сверстанными данными, разбитыми на семантические фрагменты, гораздо легче оперировать.
Один из вариантов XML-ориентированной текстовой базы данных, где представлена уже упоминавшаяся информация, может выглядеть так:
<2007>
<item>
<addr>www. cretaceous. ru</addr>
<name>Мeловой пepиод</name>
<descг>Гeологичeский сайт</descг>
</item>
<item>
<addг>backstage. eгlang. com. гu</addг>
<name>Сepвep задач</name>
<descr>CeKpeTHbrn сайт</descr>
</item>
</2007>
<2006>
<item>
<addг>www. konsplus. com</addг>
<name>Консалтинг+</name>
<descг>Юpидичeскиe услуги</descr>
</item>
</2006>
Сразу оговорюсь, что это не формат XML, а именно XML-ориентированное представление информации, поскольку данные будут извлекаться не с помощью серверных или клиентских XSLT-преобразований, а с помощью серверного сценария.
Нетрудно понять, что условно созданные «тэги» обозначают (слово «тэги» в кавычках, потому что такие тэги предусмотрены не строгой спецификацией, а скорее фантазией разработчика). Общего тэга нет: он избыточен. Наиболее крупное деление информации — по годам, и имена «верхних» тэгов представляют собой числа. Каждый элемент (<item>) портфолио включает в себя название сайта, его адрес и краткую аннотацию. Естественно, такое скупое портфолио вряд ли уместно на профессиональном сайте; сейчас мы разбираем только пример.
Для того, чтобы выполнить преобразование, считываем файл текстовой базы данных в строку, а потом из этой строки извлекаем нужные данные:
<?
// Считываeм файл в CTpoKy
$str1 = file get contents("file. php");
Филиппика против баз данных |
4.8 |
// Циклом проходим по годам for($j=date("Y"); $j>=2006; $j--) { // Каждый год теперь — отдельная строка: $god = XMLer($str1,$j); // Вычисляем количество работ в году (функция substr count()): Echo '^2>Работы за '.$j.' год ('.substr count($god,"<item>").' шт.)</h2>'; // C помощью своей функции CyXMLer() считываем // содержимое каждого элемента <item> // и преобразовываем в нужный вид с помощью // косвенного вызова функции portfolio() CyXMLer($god,"item","portfolio"); } ?> Эта функция помещается в том месте страницы, где происходит вывод данных в браузер. Естественно, до этого нам надо написать функцию CyXMLer(): она циклически (с помощью функции XMLer() — ее тоже надо описать) извлекает данные из заданных во втором параметре тэгов (задан тэг <item>) в указанной строке (строка передается переменной $god, а до этого определяется как строка — работы за отдельный год). Вот эти две функции: <? // Системные функции Function XMLer($string name,$tag name) // Простое извлечение из тэгов { $tag name = explode("/",$tag name); foreach($tag name as $tag name) { _ _ $tag name = strtolower($tag name); If(strpos($string name,$tag name)===false) { $tag name = strtoupper($tag name); } $tagnu = strlen($tag name)+2; $begi = strpos($string name,"<".$tag name.">")+$tagnu; |
$string name = substr($string name,$begi);
$fini = strpos($string name,"</".$tag name.">"); $fini = trim(substr($string name,0,$fini));
}
Return $fini;
}
Function CyXMLer($string,$tag,$func) //
Циклический перебор {
$item grance = "0";
$tag1_= "</".$tag.">"; while(strpos($string,$tag1))
{
$func($string);
$item grance = strpos($string,$tag1) + strlen($tag1);
$string = substr($string,$item grance);
}
}
// Системные функции закончились?>
Рассмотрим вкратце, как это работает. Функция XMLer() получает в качестве входных параметров имя строки и имя тэга (угловыми скобками она оформляет имя тэга сама). На тот случай, если тэги передаются то в верхнем регистре, то в нижнем, производятся служебные преобразования. Так, если функции передается некая строка (текст) и имя тэга «ТЭГ», то она извлечет из текста «Как ныне сбирается <ТЭГ>вещий Олег</ТЭГ> отмстить <ТЭГ>неразумным</ТЭГ> хазарам» слова «вещий Олег», поскольку встретит это сочетание, обрамленное указанным тэгом, и на этом успокоится. Слово «неразумным» останется за пределами ее видимости.
Но если необходимо учесть все вхождения с этими тэгами (а это актуально для нас, поскольку фрагменты <item>...</item> за год могут встретиться несколько раз), подключается функция CyXMLer(), которой передаются не только имена строки и тэга, но и имя функции (без скобок), которая совершит с извлеченными из тэгов данными нужные операции (выстроит в ряд, оформит иным способом, добавит в массив, уничтожит и т. п. — это зависит от потребностей разработчика). Функция ищет тэг от начала файла, когда находит, передвигает начальную границу поиска за заключающий тэг и ищет после этой границы, передвигая ее, пока не достигнет конца файла. Теперь мы можем быть уверены, что ни одно вхождение не пропущено. Наконец, функция CyXMLer(), находя нужный фрагмент и извлекая его из тэга, вызывает функцию пре
Образования, произвольно названную разработчиком (сейчас она у нас названа «portfolio() »). Для полноты картины приведем и ее:
<?
// Функции преобразования function portfolio($string)
{
Echo "nn";
Echo '<p><a href="http://'. XMler($string,"addr").'" target="_blank">'. XMler($string,"name").'</a> <span style="color:#CCCCCC">'.XMler($string,"addr").'</ span><br />';
Echo XMler($string,"descr").'</p>'; echo "nn";
}
// Функции преобразования закончились ?>
Здесь комментарии почти излишни, но кое-что требует пояснения. Функция принимает в качестве входного параметра строку, но не ту, которую принимала CyXMLer(), а уже ту, которую ей последняя передает. Поскольку во время циклической обработкой функцией CyXMLer () строки функция portfolio() вызывается несколько раз, но каждый раз обрабатывает разные фрагменты этой строки. Такие функции преобразования пишутся под конкретные текстовые базы данных. Общее в них может быть одно: с помощью функции XMLer () они извлекают содержимое разных вложенных тэгов и приводят полученные фрагменты информации в нужный вид. Данную функцию можно усовершенствовать: проверять, содержит ли строка в тэге <addr> название протокола (http: //), и если содержит, удалять, поскольку название протокола уже подставляется функцией. Переводы строк (символы n) нужны только для того, чтобы код получался красивее, а не сливался в один абзац.
Функции преобразования должны предшествовать системным функциям.
Чтобы показать реальную картину, можно представить себе, как будет выглядеть шаблон одного элемента для реального портфолио. Следует учесть, что некоторые тэги в пределах одного элемента списка могут повторяться: дело в том, что в описании сайта может быть несколько скриншотов, а в процессе могут участвовать несколько дизайнеров. Разберитесь в значениях тэгов сами:
<2007>
<item>
<view>
<addr></addr>
<пате></пате>
<favicon></favicon>
<descr></descr>
<Бсгееп>
<ітаде></ітаде>
<addr></addr>
<subscr></subscr>
</зсгееп>
<Бсгееп>
<ітаде></ітаде>
<addг></addг>
<subscг></subscг>
</Бсгееп>
</view>
<stuff>
<diгectoг></diгectoг>
<designeг></designeг>
<designer></designer>
<ргодгаттег></ргодгаттег>
<codeг></codeг>
<flasheг></flasheг>
</stuff>
^ресіа1>
<info></info>
<thanx></thanx>
</special>
<item>
</2007>
При работе как с текстовыми, так и с настоящими базами данных есть свои преимущества и недостатки. Например, полноценные базы данных не очень удобно использовать, если данных мало (некоторые хостеры взимают за использование БД отдельную плату); БД труднее, чем файлы, переносить с сервера на сервер; базы данных чаще отказывают, если велика нагрузка на сервер (при этом текстовые файлы продолжают доставляться в браузер). Но для больших объемов однотипных данных текстовые файлы непригодны. Специализированных функций для обращения к части файлов и сортировки данных внутри файлов (если это не массивы) нет. Если отсутствие файла проверяется простой функцией, а его содержимое бывает недоступным, только если файла нет на самом деле, то БД иногда бывают недоступны по другим причинам, и программисту приходится на каждый фрагмент в месте потенциального отказа писать тексты, которые будут видимы посети-
Польза настроечных файлов |
4.9 |
Телю в случае ошибки. Грамотные сообщения об ошибках написать целая проблема, судя по конкретным сайтам. Кто-то пишет «Troubles with database» или «Can’t connect to MySQL Server» (почему по-английски — неясно), кто-то — «База данных недоступна, повторите попытку позже» (пользователь не обязан знать, в чем на сайте хранят информацию, ему важнее знать, когда он ее получит), а кто-то — «Сервис временно недоступен. Мы знаем. Повторите попытку позднее» (никаких извинений, только высокомерное «Мы знаем»). В первую очередь нужно обеспечить отказоустойчивость системы, а сообщения об ошибках сделать более полезными («Информация станет доступной через 17 минут», «Если вы видите это сообщение, на сайте возникли проблемы. Будьте добры, напишите об этом разработчику: адрес» и т. п.). P. S. Филиппики не получилось. Опять получился призыв сначала работать головой, а потом руками. 4.9. Польза настроечных файлов В разделении данных на фрагменты есть несомненная польза. К примеру, вы делаете сайт для заказчика, и на главной странице указан адрес его электронной почты. Возникает закономерная мысль: а если заказчик поменяет свой электронный адрес, значит ли это, что старый адрес будет висеть на странице до страшного суда? Обратится ли заказчик к разработчику снова или постарается отредактировать файл сам? И случайно сотрет там кавычку... Простейший выход — записать адрес электронной почты в отдельном файле, а на администраторской странице сделать возможность редактировать этот адрес. В «админке» появляется раздел «Настройки сайта», в котором, помимо каких-то мелочей, владелец может поменять и адрес своего сайта. Но на достигнутом остановиться сложно. Есть и другие вещи, которые можно настраивать: — количество новостей (или записей блога), которое будет выводиться на главной странице; — количество символов, максимально допустимое в новости (или записи в блоге) на главной странице, после которого запись обрежется, а на месте скрытого текста появится ссылка на полный текст новости («Полный текст», «Читать целиком» или просто стрелка вправо — →); — количество результатов на поисковой странице; — отображение или скрытие тех или иных страниц на сайте, в том числе отображение или скрытие соответствующих пунктов меню; |
— показывать ли адрес электронной почты или ссылку на страницу, с которой можно отправить письмо, не загружая почтовую программу;
— выбор кодировки для страниц и другие параметры, зависящие от тематики конкретного сайта.
Настроечный файл можно записывать на сервер в любом удобном формате, главное — чтобы из него было удобно автоматически считывать информацию. Например, вид может быть XML-ориентированным:
<mail>
<address>12 3@domain. ru</address>
<show>1</show>
</mail>
<news>
<quantity>15</quantity>
<length>4 0 0</length>
</news>
<search>
<quantity>10<quantity>
</search>
Если использовать сценарий, предложенный при описании PHP, то вызов настроек будет происходить примерно так:
$addr = XMLer("prefs. txt","mail/address");
Можно сделать настройки экономнее, чтобы чтение этого файла происходило быстрее:
Mail: 123@domain. com mail-show: 1
News-q: 15 news-l: 400 search: 10
В этом случае сценарий чтения настроечного файла разбивает этот файл на строки или элементы массива (пропуская пустые), а каждую строку разрезает надвое по разделителю (двоеточие). Вызов параметра будет происходить, например, так:
$addr = prefs["mail"];
Сценарии, для которых настраиваются параметры, немного модифицируются. Перед выполнением сценарий проверяет значение па-
Польза настроечных файлов |
4.9 |
Раметра, и в зависимости от этого выполняется в разных вариантах либо вообще не выполняется. Вообще фрагментарность хранения информации заложена в принципы построения веб-страниц изначально: отдельно можно хранить HTML-файлы, изображения, ролики, аудиофайлы, стилевые файлы и сценарии. А при использовании файлов сценариев страницы вообще можно составлять на лету из десятков файлов. Именно этот принцип и можно использовать при работе с настройками. |
Приложения