Главное меню

Проектирование баз данных для веб-приложений

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

• Системы управления реляционными базами данных (СУРБД) обеспечивают более быстрый доступ к данным, чем двумерные файлы.

• Посредством запросов из СУРБД легко извлекать наборы данных, соответствующие определенным критериям.

• СУРБД обладают встроенным механизмом для работы с параллельным доступом, что позволяет программисту не беспокоиться об этом.

• СУРБД обеспечивают произвольный доступ к данным.

• СУРБД обладают встроенными системами управления полномочиями.

Если говорить более предметно, то использование реляционных баз данных позволяет быстро и без особых усилий ответить на такие вопросы, как: "Где проживают клиенты?", "Какие товары продаются наиболее успешно?" или "Какая категория клиентов приносит наибольшую прибыль?" Эта информация может способствовать улучшению сайта, чтобы не только не потерять клиентов, но и привлечь новых. Но ее очень трудно получить, имея дело с двумерными файлами.

Концепции реляционных баз данных

На сегодняшний день реляционные базы данных (БД) являются наиболее часто используемым типом БД. Они построены на основе строгих законов реляционной алгебры. Чтобы пользоваться реляционными БД (а это весьма неплохая идея), вовсе необязательно досконально разбираться в реляционной теории, однако все же следует овладеть основными понятиями баз данных.

Таблицы

Реляционные БД построены на основе отношений, обычно называемых таблицами (table). Таблица представляет собой именно то, что и подразумевает этот термин – таблицу с данными. Если вам когда-либо приходилось иметь дело с электронной таблицей, значит, вы уже имеете опыт использования таблиц.

Рассмотрим пример таблицы, показанный на рис. 8.1. Эта таблица содержит имена и адреса клиентов книжного магазина "Буквофил".

Проектирование баз данных для веб-приложений

Таблица имеет собственное имя – Customers (Клиенты), несколько столбцов, каждый из которых содержит определенного рода данные, а также строки, в которых записаны сведения о клиентах.

Столбцы

Каждый столбец в таблице имеет уникальное имя и содержит разнообразные данные. Кроме того, каждому столбцу соответствует определенный тип данных. Например, в таблице Customers, которая показана на рис. 8.1, можно видеть, что столбец CustomerID (Идентификатор клиента) хранит целочисленную информацию, а остальные три столбца – строки. Иногда столбцы называются также полями или атрибутами.

Строки

Каждая строка в таблице представляет отдельного клиента. Вследствие использования табличного формата все строки имеют одни и те же атрибуты. Строки также называют записями или кортежами.

Значения

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

Ключи

Клиентов нужно различать. Обычно имена не очень подходят для этого – если ваше имя достаточно распространенное, думается, вполне понятно, почему так сложно отличить одного Сидорова Ивана Петровича от полста других Сидоровых Иванов Петровичей. Вот, например, Слава Моргунов из таблицы Customers. Если открыть телефонную книгу, то окажется, что такое имя и фамилия встречаются в ней слишком часто.

Существует несколько способов отличить конкретного Славу от других. Например, вероятнее всего, он – единственный Слава Моргунов, проживающий по данному адресу. Однако строка "Слава Моргунов, проживающий по адресу 56, пер. Поттера, пгт Хогвартс" слишком длинная и звучит чересчур официально. К тому же при этом требуется несколько столбцов таблицы.

Мы поступили следующим образом (вероятнее всего, вы поступите так же) – каждому клиенту присвоили уникальный идентификатор клиента (CustomerID). При этом используется тот же принцип, что и в банке, где счетам клиентов присваивают уникальные номера, или в клубе, где каждому члену клуба выдают индивидуальную членскую карточку с уникальным номером. Такой подход облегчает хранение сведений в БД, а искусственное присвоение идентификационного номера гарантирует его уникальность. Лишь немногие реальные сведения о клиенте, даже при совместном их использовании, обладают подобным свойством.

Столбец идентификации в таблице называется ключом (key) или первичным ключом (primary key). Ключ может состоять из нескольких столбцов. Например, если бы мы решили идентифицировать Славу Моргунова по строке "Слава Моргунов: 56, пер. Поттера, пгт Хогвартс", то ключ состоял бы из столбцов Name, Address и City. При этом нельзя было бы гарантировать его уникальность.

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

Проектирование баз данных для веб-приложений

Каждая строка в таблице Orders (Заказы) представляет один заказ, сделанный одним клиентом. Клиента можно установить по хранящемуся в таблице идентификатору клиента CustomerID. Например, если взглянуть на заказ с идентификатором заказа OrderlD, равным 2, видно, что его сделал клиент с CustomerID, равным 1. Затем, обратившись к таблице Customers, можно выяснить, что CustomerID=1 присвоен клиенту Саша Валентей.

В соответствие с терминологией реляционных баз данных такая взаимосвязь называется внешним ключом (foreign key). CustomerID – первичный ключ в таблице Customers, однако когда он появляется в другой таблице, например, Orders, его называют внешним ключом.

Не исключено, что наше решение использовать две разные таблицы может вызвать у вас недоумение – почему бы просто не поместить адрес Славы Моргунова в таблицу Orders? Подробнее это объясняется далее.

Схемы

Множество структур всех таблиц базы данных называется схемой (schema) этой базы данных. В чем-то схема подобна чертежу. В ней должны быть изображены таблицы вместе с их столбцами, а также указаны первичный ключ и все внешние ключи для каждой таблицы. Схема не содержит никаких конкретных данных, однако в нее можно поместить образцы данных, чтобы назначение тех или иных столбцов было понятнее. Схемы могут быть представлены в виде неформальных диаграмм, аналогичных используемым в наших материалах, в виде диаграмм "сущность-отношение" или в текстовой форме наподобие следующей:

Customers(CustomerID, Name, Address, City)

Orders(OrderID, CustomerID, Amount, Date)

Подчеркнутые элементы в схеме – это первичные ключи того отношения, где они подчеркнуты. Элементы, выделенные курсивом, представляют собой внешние ключи соответствующего отношения.

Отношения

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

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

Отношение "один к одному" означает, что с каждой стороны в отношении участвует по одному элементу. Например, если бы адреса были помещены не в таблицу Customers, а в какую-нибудь другую, между этими таблицами существовало бы отношение "один к одному". Таблицы Addresses (Адреса) и Customers могли бы быть связаны внешним ключом либо как-то иначе (и то, и другое не обязательно).

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

В отношении типа "многие ко многим" несколько строк одной таблицы связаны с несколькими строками другой. Например, при наличии двух таблиц Books (Книги) и Authors (Авторы) могло бы выясниться, что одна книга была написана несколькими авторами, каждый из которых написал и другие книги, причем некоторые из них могли быть написаны, опять-таки, в соавторстве с другими. Как правило, такой тип отношений требует наличия отдельной таблицы. В результате могли бы существовать таблицы Books, Authors и Books_Authors. Третья таблица содержала бы только ключи из остальных двух таблиц в качестве парных внешних ключей, показывающие, какие авторы принимали участие в написании той или иной книги.

Как спроектировать собственную базу данных для веб-приложения

Знание того, когда требуется новая таблица и какой элемент нужно выбрать в качестве ключа, относится, скорее, к искусству. Темам диаграмм "сущность-отношение" и нормализации баз данных, которые выходят за рамки наших материалов, посвящены буквально горы научной литературы. Однако в большинстве случаев достаточно придерживаться нескольких основных принципов проектирования. Рассмотрим их применительно к нашему проекту "Буквофил".

Думайте о реальных объектах, которые вы моделируете

Как правило, при создании базы данных приходится моделировать объекты и взаимосвязи реального мира и сохранять информацию об этих объектах и взаимосвязях.

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

В примере с магазином "Буквофил" необходимо хранить сведения о клиентах, продаваемых книгах и деталях заказов. У каждого клиента есть имя-фамилия и адрес. Заказы отличаются датой оформления, общей стоимостью и списком заказанных книг. Книги отличаются номером ISBN, автором, названием и ценой.

Исходя из этого, база данных должна содержать, как минимум, три таблицы: Customers (Клиенты), Orders (Заказы) и Books (Книги). Исходная схема представлена на рис. 8.3.

Проектирование баз данных для веб-приложений

В данном случае, глядя на модель, нельзя узнать какие книги были указаны в каждом заказе. Этим вопросом мы займемся несколько позже.

Избегайте хранения избыточной информации

Несколько ранее мы задавались вопросом: "Почему бы просто не хранить адрес Славы Моргунова в таблице Orders?"

Если Слава Моргунов закажет в магазине "Буквофил" несколько книг (на что мы искренне надеемся), сведения о нем придется записывать несколько раз. В итоге таблица Orders может приобрести вид, показанный на рис. 8.4.

Проектирование баз данных для веб-приложений

С таким подходом связаны две основные проблемы.

Во-первых, напрасно тратится пространство на жестком диске. Зачем сохранять информацию о Славе трижды, если достаточно сделать это только один раз?

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

Следует избегать трех типов аномалий обновлений: аномалий модификации, аномалий вставки и аномалий удаления.

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

В этом случае сведения о Славе Моргунове придется вводить каждый раз, принимая заказ. Поэтому необходимо постоянно проверять соответствие между вводимыми сведениями и данными, записанными в таблице. В противном случае в таблице могут появиться две строки с противоречащей друг другу информацией о Славе Моргунове. Например, в одной строке может сообщаться, что Слава Моргунов живет в пгт Хогвартс, а в другой местом его проживания будет указан пгт Дартс. Такое несоответствие называется аномалией вставки, поскольку оно возникает во время вставки данных.

Третий тип аномалий называется аномалией удаления, поскольку проявляется (кто бы мог подумать?) при удалении строк из базы данных. Для примера представим, что после выполнения заказ удаляется из базы данных. То есть, как только все текущие заказы Славы Моргунова выполнены, они удаляются из таблицы Orders. А это означает, что мы больше не располагаем сведениями об адресе Славы. Мы не сможем прислать ему какие-то специальные предложения, и в следующий раз, когда он пожелает заказать что-либо, придется снова собирать все данные о нем.

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

Используйте элементарные значения столбцов

Использование элементарных значений столбцов означает, что в каждом атрибуте каждой строки должен храниться только один элемент. Например, требуется узнать, какие книги отобраны для каждого заказа. Этого можно достичь несколькими путями. В таблицу Orders можно добавить столбец, в котором будет размещаться список всех заказанных книг (рис. 8.5).

Проектирование баз данных для веб-приложений

В силу ряда причин это не очень-то приемлемо. По существу в этом случае в один столбец мы помещаем целую таблицу, связывающую заказы с книгами. Такой подход осложняет ответ на вопрос типа: "Сколько экземпляров книги «Java 2. Руководство разработчика» было заказано?" Система не сможет просто подсчитать количество совпадающих записей. Вместо этого ей придется проанализировать значение каждого атрибута, чтобы найти внутри него возможные совпадения.

Поскольку в этом случае мы создаем таблицу в таблице, то надо просто создать новую таблицу. Назовем ее Order_Items (Элементы заказа). Схема этой таблицы показана на рис. 8.6.

Проектирование баз данных для веб-приложений

Приведенная выше таблица обеспечивает связь между таблицами Orders и Books. Использование таблиц подобного типа весьма характерно для случаев, когда два объекта связаны между собой отношением "многие ко многим" – в данном случае один заказ может включать в себя несколько книг, а каждая из книг может быть заказана несколькими людьми.

Выбирайте подходящие ключи

Убедитесь в том, что выбранные ключи уникальны. В данном случае мы создали специальные ключи для клиентов (CustomerID) и для заказов (OrderID), поскольку у этих реальных объектов может не оказаться идентификатора, который гарантированно является уникальным. Для книг создавать подобный идентификатор не требуется, он у них уже есть – это номер ISBN. Для таблицы Order_Items можно, при желании, добавить один ключ, однако комбинация атрибутов OrderlD и ISBN будет уникальной, если заказ двух и более экземпляров одной и той же книги рассматривается как одна строка. По этой причине в таблицу Order_Items включен столбец Quantity.

Подумайте, какие вопросы потребуется задавать базе данных

Продолжая цепочку рассуждений, подумайте, на какие вопросы желательно получать ответы от базы данных. (Вспомните, о чем говорилось в начале. Например, какие книги магазина "Буквофил" продаются лучше других?) Убедитесь, что база данных содержит всю необходимую информацию, и что между таблицами установлены все нужные связи, позволяющие ответить на поставленные вопросы.

Избегайте проектов с большим количеством пустых атрибутов

Если возникнет необходимость добавить в базу данных рецензии на книги, то существует, по меньшей мере, два варианта, как это сделать. Оба они продемонстрированы на рис. 8.7.

Проектирование баз данных для веб-приложений

Первый вариант подразумевает добавление столбца Review в таблицу Books. В таком случае каждую книгу будет сопровождать поле с рецензией. Если в базе данных много книг и рецензент не собирается делать обзор их всех, во многих строках этот атрибут не будет иметь значения (или, как говорят, будет иметь пустое значение).

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

Большинства проблем с нулевыми значениями можно избежать, воспользовавшись иным подходом к проектированию. Для этого можно использовать второй вариант, представленный на рис. 8.7. Здесь в таблице Book_Reviews перечисляются только книги и приводятся их рецензии.

Обратите внимание, что в основе этого подхода лежит идея рецензирования книг единым рецензентом, т.е. между таблицами книг и рецензий существует отношение "один к одному". Если вы хотите поддерживать несколько рецензий для одной и той же книги, возникнет отношение "один ко многим", и в качестве начального проекта необходимо выбрать второй вариант. Кроме того, если для книги поддерживается одна рецензия, можно использовать ISBN в качестве первичного ключа в таблице Book_Reviews. При наличии многих рецензий для каждой книги необходимо ввести уникальный идентификатор для каждой рецензии.

Типы таблиц

Как правило, базы данных состоят из двух типов таблиц.

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

• Связывающие таблицы, которые описывают отношения типа "многие ко многим" между двумя реальными объектами, например, отношение между таблицами Orders и Books. Такие таблицы часто ассоциируются с некоторой реальной транзакцией.

Архитектура баз данных для веб-приложений

Теперь, когда рассмотрена внутренняя архитектура базы данных, пришло время взглянуть на внешнюю архитектуру системы баз данных для веб-приложений и рассмотреть методологию ее разработки.

Основная работа веб-сервера проиллюстрирована на рис. 8.8. Эта система состоит из двух объектов: веб-браузера и веб-сервера. Между ними должен существовать канал связи. Веб-браузер посылает запрос на сервер, сервер отправляет обратно ответ. Такая архитектура подходит для сервера, отсылающего обычные статические страницы. Архитектура же сайта, который включает в себя базу данных, несколько сложнее.

Проектирование баз данных для веб-приложений

Приложения баз данных для веб-приложениий, которые будут разрабатываться в наших материалах, соответствуют общей структуре баз данных для веб-приложениий, показанной на рис. 8.9. Большая часть этой структуры должна быть вам знакома.

Проектирование баз данных для веб-приложений

Типичная транзакция базы данных для веб-приложениий состоит из этапов, обозначенных на рис. 8.9 цифрами. Мы рассмотрим их на примере магазина "Буквофил".

1. Веб-браузер пользователя отправляет HTTP-запрос определенной веб-страницы. Например, HTML-форма может выдать запрос на поиск в магазине "Буквофил" всех книг, написанных Лорой Томсон. Страница с результатами поиска называется results.php.

2. Веб-сервер принимает запрос на results.php, извлекает файл и передает его на обработку интерпретатору РНР.

3. Интерпретатор РНР начинает синтаксический анализ сценария. Сценарий содержит команду подключения к базе данных и выполнения запроса (на поиск книг). РНР открывает соединение с сервером MySQL и отправляет ему соответствующий запрос.

4. Сервер MySQL принимает запрос к базе данных, обрабатывает его, а затем отправляет результаты – в данном случае, список книг – обратно интерпретатору РНР.

5. Интерпретатор РНР завершает выполнение сценария, что обычно сопряжено с форматированием результатов запроса в виде HTML, после чего возвращает результаты в HTML-формате веб-серверу.

6. Веб-сервер пересылает браузеру HTML-страницу, в которой пользователь может просмотреть список необходимых книг.

В основном процесс остается неизменным независимо от используемого сценарного механизма и сервера баз данных. Зачастую программное обеспечение веб-сервера, интерпретатор РНР и сервер баз данных функционируют на одном компьютере. В то же время, достаточно часто сервер базы данных работает на другом компьютере. Это может быть обусловлено соображениями безопасности, увеличения пропускной способности или более эффективного распределения нагрузки. С точки зрения разработки эти подходы эквивалентны, однако в плане производительности второй вариант может оказаться более предпочтительным.

По мере возрастания размеров и сложности приложения, можно разделить РНР-приложение на уровни – обычно это уровень работы с базами данных, который взаимодействует с MySQL, уровень бизнес-логики, который содержит основную часть приложения, и уровень представления, управляющий HTML-выводом. Тем не менее, базовая архитектура, показанная на рис. 8.9, по-прежнему сохраняется; просто к разделу, относящемуся к РНР, добавляются дополнительные структуры.