Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основное / Письменные лекции по дисциплине «Базы данных»

.pdf
Скачиваний:
98
Добавлен:
29.01.2021
Размер:
939.56 Кб
Скачать

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

Для борьбы с этой проблемой в СУБД реализованы различные формы обнаружения взаимоблокировок и тайм-аутов. Более совершенные подсистемы хранения данных, такие как InnoDB, легко обнаруживают циклические зависимости и немедленно возвращают ошибку. Это очень хорошо, иначе взаимоблокировки проявлялись бы в виде очень медленных запросов. Другие системы откатывают транзакцию по истечении тайм-аута, что не очень хорошо. InnoDB обрабатывает взаимоблокировки откатом той транзакции, которая захватила меньше всего монопольных блокировок строк (приблизительный показатель легкости отката).

Поведение и порядок блокировок зависят от подсистемы хранения данных, так что в одних подсистемах при определенной последовательности команд могут происходить взаимоблокировки, а в других — нет. Взаимоблокировки имеют двойственную природу: некоторые неизбежны из-за конфликта данных, другие вызваны схемой работы конкретной подсистемы хранения.

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

4.7. Ведение журнала транзакций

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

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

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

Лекция 5. Ссылочная целостность данных. Внешние ключи. Индексирование.

Ссылочная целостность данных;

Внешний ключ;

Индекс;

Курсор.

5.1. Ссылочная целостность данных

В самом общем виде понятие целостности БД подразумевает соответствие имеющейся в ней информации внутренней структуре БД, ее логике.

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

Всё это — правила ограничения целостности, которые помогают поддерживать указанное выше соответствие.

Задача. Создадим БД городов мира. В этой БД мы будем хранить название города и название страны, в которой находится город.

ID города

Название города

Страна

 

 

 

1

Киев

1

 

 

 

2

Москва

2

 

 

 

3

Львов

1

Легко понять, что если мы создадим 1 таблицу, то названия стран будут дублироваться, и при этом не раз. Если у нас есть 100 городов одной

страны, то мы должны возле каждого города указать одно и то же название страны.

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

ID города

Название города

Страна

 

 

 

1

Киев

1

 

 

 

2

Москва

2

 

 

 

3

Львов

1

 

 

 

ID страны

Название страны

 

 

1

Украина

 

 

2

Россия

Таким образом, у нас появилось 2 таблицы — таблица стран и городов. При этом таблица стран — это т. н. справочник. Почему? Очень легко понять на примере. Итак, имеем таблицу стран с двумя странами: Украина (id=1) и Россия (id=2). В таблице стран имеем 3 города: Киев (id=1), Москва (id=2), Львов (id=3). Для принадлежности каждого города к конкретной стране поставили идентификаторы: у Киева и Львова — это 1, а у Москвы — 2. А расшифровка этих идентификаторов находится в таблице стран, т. е. в справочнике.

Теперь, если у страны изменится название, то мы меняем его только в справочнике, а таблицу городов не трогаем вообще, поскольку в ней нет названий стран (они в справочнике). Согласитесь, удобно. Фактически мы получили ситуацию, когда идентификатор страны из справочника (родительская таблица) используется в другой таблице (дочерней). Это как раз и есть упоминавшийся выше внешний ключ, который связывает поле дочерней таблицы с полем родителя.

Но пока что эта связь только у нас в уме. Для сервера ее нет. Соответственно, никаких ограничений такая связь не накладывает. Что нам мешает сейчас добавить в таблицу городов город с идентификатором страны 3? Т. е. это страна, не представленная в справочнике. Абсолютно ничего не мешает. Добавив указанную запись в таблицу городов мы как раз нарушаем целостность данных. К примеру, мы выводим на сайт список стран и по клику на страну выводим список городов этой страны. Наш «блудный» город без страны, таким образом, никогда выведен не будет. Получается, что это «лишняя», «болтающаяся» запись.

Чтобы избежать этого мы можем указать серверу связь внешнего ключа с родительским полем в справочнике. Для того, чтобы реализовать это, следует знать определенные правила, при которых такая связь вообще возможна.

5.2. Внешний ключ

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

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

5.2.1. Условия обеспечения целостности данных при помощи внешнего ключа

1.Для использования ограничений внешнего ключа типы обоих таблиц должны быть InnoDB.

2.Типы родительского и дочернего полей должны быть абсолютно идентичными.

3.Связь по внешнему ключу опирается на индексы, поэтому и родительское, и дочернее поля должны содержать индексы.

4.Связь по внешнему ключу должна опираться на поле с ограничениями в родительской таблице, т. е. это поле должно содержать ограничения PRIMARY KEY или UNIQUE.

5.2.2. Практический пример

Теперь мы можем создать 2 таблицы с использованием ограничения по внешнему ключу.

Создаем таблицу городов:

CREATE TABLE country

(

country_id TINYINT UNSIGNED AUTO_INCREMENT NOT NULL, country_name VARCHAR(50) NOT NULL,

PRIMARY KEY (country_id)

) ENGINE=InnoDB;

Создаем таблицу (справочник) стран:

CREATE TABLE city

(

city_id SMALLINT UNSIGNED AUTO_INCREMENT NOT NULL, city_name VARCHAR(50) NOT NULL,

country_id TINYINT UNSIGNED NOT NULL, PRIMARY KEY (city_id),

INDEX ixCity (country_id),

CONSTRAINT country_city FOREIGN KEY (country_id) REFERENCES country (country_id)

) ENGINE=InnoDB;

Теперь давайте разберем указанные правила с учетом запросов. Поле с внешним ключом — это «city.country_id» (для того, чтобы не запутаться, перед названием поля мы указали через точку имя таблицы).

Родительское поле

(на

которое ссылается

внешний ключ)

это

«country.country

_id».

 

 

 

 

 

 

1. Для использования ограничений внешнего ключа типы обоих

таблиц должны быть InnoDB.

 

 

 

 

 

Как

видим,

 

для

обеих

таблиц

указан одинаковый

тип

«ENGINE=InnoDB

».

 

 

 

 

 

 

 

 

2. Типы родительского и дочернего полей должны быть абсолютно

идентичными.

 

 

 

 

 

 

 

 

 

 

Это

правило

также

выполняется.

Тип дочернего

поля

«city.country

_id»

«TINYINT

UNSIGNED». Тип

родителя

«country.country

_id» — также «TINYINT

UNSIGNED».

 

 

3.Связь по внешнему ключу опирается на индексы, поэтому и родительское, и дочернее поля должны содержать индексы. Здесь также все ок. Дочернее поле мы индексируем, добавляя индекс в запросе — «INDEX ixCity (country_id)». Родитель индексируется автоматически. Дело в том, что для родителя мы имеем PRIMARY KEY, что как раз и предусматривает автоматический индекс для него.

4.Связь по внешнему ключу должна опираться на поле с ограничениями в родительской таблице, т. е. это поле должно содержать ограничения PRIMARY KEY или UNIQUE. И здесь все выполняется:

«country.country_id» — «PRIMARY KEY (country_id)».

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

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

Но мы можем улучшить эту связь и сделать так, чтобы если мы что-то меняем в справочнике, то эти изменения касались также дочерней таблицы. Для этого после объявления ограничения по внешнему ключу мы можем записать выражения «ON DELETE CASCADE ON UPDATE CASCADE».

По умолчанию значения этих выражений указаны «ON DELETE RESTRICT ON UPDATE RESTRICT», т. е. запрет. Таким образом, наш запрос станет таким:

CREATE TABLE city

(

city_id SMALLINT UNSIGNED AUTO_INCREMENT NOT NULL, city_name VARCHAR(50) NOT NULL,

country_id TINYINT UNSIGNED NOT NULL, PRIMARY KEY (city_id),

INDEX ixCity (country_id),

CONSTRAINT country_city FOREIGN KEY (country_id) REFERENCES country (country_id)

ON DELETE CASCADE ON UPDATE CASCADE

) ENGINE=InnoDB;

Теперь, если мы удалим из справочника страну, то из дочерней таблицы без всяких дополнительных запросов будут удалены все города этой страны. Удобно, не правда ли? И не нарушается целостность данных.

5.2.3. Синтаксис объявления внешнего ключа

Общий синтаксис установки внешнего ключа на уровне таблицы:

[CONSTRAINT имя_ограничения]

FOREIGN KEY (столбец1, столбец2, ... столбецN)

REFERENCES главная_таблица (столбец_главной_таблицы1,

столбец_главной_таблицы2, ... столбец_главной_таблицыN)

[ON DELETE действие]

[ON UPDATE действие]

Для создания ограничения внешнего ключа после FOREIGN KEY

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

и ON UPDATE, которые задают действие при удалении и обновлении строки из главной таблицы соответственно.

5.3. Индекс Индекс — это специфический объект базы данных, позволяющий

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

Индексы создаются для повышения производительности поиска данных. Таблицы могут иметь огромное количество строк, которые хранятся в произвольном порядке. Без индекса поиск нужных строк идёт по порядку (последовательно), что на больших объемах данных отнимает много времени.

5.3.1. Для каких полей нужно создавать индексы

Индексы, прежде всего, нужно создавать по тем полям, которые часто попадают в условие «WHERE» ваших sql-запросов.

Например, таблица с товарами имеет следующую структуру:

id

product_name

cat_id

price

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

SELECT id, product_name FROM products WHERE cat_id = '5'

В этом случае для оптимизации запросов целесообразно создать индекс для поля cat_id. Первое поле — id всегда имеет уникальное значение и для него целесообразно создать «первичный ключ» (Primary Key).

5.3.2. Принцип работы индексов

Для примера рассмотрим запрос:

SELECT Name FROM Persons WHERE Points < 10

У нас имеется таблица Persons, в которой есть два поля: Name и Points. При поиске в неиндексированной таблице, система последовательно перебирает все строки и сравнивает их с 10. При выполнении условия — выводит их. Даже при наличии в таблице всего 1000 записей, такой запрос может оказаться ресурсоемким, если одновременно поступит от 100 абонентов. Индексирование таблиц MySQL позволяет сократить число операций. Если поле Points будет проиндексировано, поиск будет проводиться по самому индексу, без перебора всех строк таблицы.

5.3.3. Виды индексов

1.Простые. Одному полю таблицы соответствует один индекс.

2.Составные. Один индекс соответствует нескольким полям

таблицы.

3.Покрывающий. Этот вид индексов создается специально под определенный запрос и включает в себя все поля таблицы, фигурирующие

взапросе.

5.3.4.Индексирование таблиц MySQL

Использование индексирования таблиц MySQL имеет свои особенности:

1. Индексировать есть смысл только определенные поля таблицы. Нередки случаи, когда неопытные пользователи индексируют всю БД. На практике такой шаг может привести к обратному результату: в MySQL