View
547
Download
1
Tags:
Embed Size (px)
Citation preview
Работа со схемой БД в проектах
Interlabs
17 декабря 2013
1 / 27
Миграции
Первое, о чем все говорят, когда речь заходит о работе с базой:
• версионность схемы на уровне базы данных• в теории — автоматический переход между версиями• в реальности — не все так просто, откат назад в рядеслучаев невозможен
• обычно — привязка к конкретному языку или фреймворку• обычно — база как набор таблиц, поддержка триггеров,view, индексов — неудобна или отсутствует
2 / 27
Миграции: пример
use DoctrineDBALMigrationsAbstractMigration,DoctrineDBALSchemaSchema;
class Version20130112132011 extends AbstractMigration {public function up(Schema $schema) {
$this->addSql( "CREATE TABLE ...");}
public function down(Schema $schema) {$this->addSql("DROP TABLE ...");
}}
3 / 27
Миграции: плюсы и минусы
• могут генерироваться автоматически (в т.ч. по diff)• можно использовать API для типовых операций• универсально для разных серверов БД
В то же время:
• в основном решает только задачу модификации схемы• компоновка из стандартных модулей — ООП-сценарий• документирование и визуализация — отдельная задача• хранимый код и view — неудобно, база = набор таблиц.
4 / 27
Что мы хотим?//////////Ничего.• простой формат описания схемы, низкий порог вхождения• миграции на SQL• набор преобразований описания в различные форматы —HTML документация, SVG для диаграмм, SQL для миграций
• возможность компоновки схемы из стандартных модулей• поддержка библиотеки стандартных триггеров,расширяющих поведение объектов базы
• минимум зависимостей
Все банально, хотя сейчас уже не модно:
XML→ XSL→ SQL, SVG, HTML . . .
5 / 27
Это миграции?
• нет, это описание схемы на определенный момент• но с помощью этого мы можем сгенерировать код длямиграций
• миграция — просто сценарий SQL в отдельном файле• роль обратных миграций несколько преувеличена• работаем только на уровне скриптов, перед выполнениемскрипт можно поправить, дописать и т.д.
• не работаем непосредственно с базой, хотя описаниеможет быть сгенерировано из существующей базы
• миграция данных — вручную
6 / 27
Почему XML?
Simplest Thing Possible
• не очень удобно писать руками, но жить можно• легко валидировать• легко преобразовывать в различные форматы• легко расширять• легко написать прототип в виде набора XSL-стилей• может быть легко сгенерирован по реальной базе
database.xml7 / 27
database.xml<Database>
<Description><p>Демонстрация database.xml.</p>
</Description><Table name="test" comment="Тестовая таблица">
<Description><p>Подробное описание тестовой таблицы.</p>
</Description><Column name="id" type="bigint" primary="true" /><Column name="title" type="varchar" size="255" comment="Имя" /><Index name="title" comment="Сортировка по имени">
<IndexColumn name="title" /></Index><Trigger when="before" action="insert">
<SQL>SET NEW.id = UUID_SHORT();</SQL></Trigger>
</Table></Database>
8 / 27
ТаблицаИспользуем разумные умолчания (ENGINE=InnoDB), требуемналичия описания.
<Table Определение таблицы:name="имя" - имя таблицыcomment="комментарий" - комментарий, сохраняемый в базе>
Элементы таблицы в любом порядке:<Column ../> - колонка<Index ../> - индекс<ForeignKey ../> - внешний ключ<Trigger ../> - триггер
</Table>
9 / 27
КолонкаИспользуем разумные умолчания (NOT NULL), требуем наличияописания.
<Column Определение колонки:name="имя" - имя колонкиtype="тип" - тип данных MySQLsize="размер" - необязательный размерdefault="значение" - значение по умолчаниюprimary="true|false" - использование в PRIMARY KEYnull="true|false" - по умолчанию всегда NOT NULLcomment="комментарий" - сохраняемый в базе комментарий/>
10 / 27
ИндексДля уникальных индексов генерируется CONSTRAINT.
<Index Определение индекса:name="имя" - имя индексаunique="true|false" - уникальный индексcomment="комментарий" - сохраняемый в базе комментарий>
<IndexColumn Определение колонки индекса:name="имя" - имя колонкиdesc="true|false" - направление сортировки/>
...</Index>
11 / 27
ТриггерМожно декларировать несколько триггеров на одно и то жесобытие, хотя поддержка на уровне SQL появилась только в 5.7.
<Trigger Определение триггераwhen="before|after" - момент срабатыванияaction="update|insert|delete" - событие срабатывания><SQL>Код триггера</SQL>
</Trigger>
В SQL может понадобиться использовать CDATA.
12 / 27
Компоновка триггеров: проблема
• MySQL до версии 5.7 не поддерживает несколькотриггеров на одно событие
• код триггеров зачастую однотипен, динамический SQL неподдерживается, хочется компоновать из стандартныхфрагментов
• в стандартных фрагментах возможны конфликтыпеременных
• если в библиотечном триггере ошибка — нужноперегенерировать все триггеры
13 / 27
Компоновка триггеров: решениеОборачиваем каждый фрагмент в отдельный BEGIN/END - блок:
<Trigger when="before" action="delete"><SQL>...</SQL>
</Trigger><Trigger when="before" action="delete"><SQL>...</SQL>
</Trigger>
CREATE TRIGGER ‘name‘ BEFORE DELETE ON ‘table‘ FOR EACH ROWBEGINBEGIN ... END; // каждый блок — отдельный scopeBEGIN ... END; // для своих переменных
END;
14 / 27
Внешний ключНе заморачиваемся с именованием CONSTRAINT, простоназываем ключ:
<ForeignKey Определение ForeignKeyname="имя" - имя ключаtable="таблица" - таблица, на которую ссылаемсяdelete="cascade" - поведение при delete (update,update="cascade" - поведение при update cascade,> setnull)<Reference Определение ссылкиlocal="колонка" - ссылающаяся колонкаforeign="колонка" - колонка, на которую ссылаются/>...
</ForeignKey>
15 / 27
Представление
Явно описываем колонки для документирования.
<View Определение VIEWname="имя" - имя VIEW><ViewColumn Определение колонки VIEW
name="имя" - имя колонки/>
<SQL>SQL-код VIEW</SQL></View>
16 / 27
Создание схемыСценарий тривиально генерируется из описания, при этом:
• отключаем внешние ключи на время выполнения —избавляемся от проблем с порядком создания таблиц
• перед каждой таблицей добавляем IF NOT EXISTS навсякий случай
• создание индексов и внешних ключей — внутри CREATETABLE
Неудобный момент в синтаксисе MySQL: невозможностьпроверки существования индекса:
CREATE INDEX IF NOT EXISTS
17 / 27
Модификация схемы
• отключаем внешние ключи на время выполнения —избавляемся от проблем с порядком изменения таблиц
• для колонок, индексов, внешних ключей — ALTER TABLE• синтаксис ALTER TABLE для создания/модификацииэлементов практически совпадает с CREATE TABLE —проще написать преобразование
• триггеры и хранимый код безусловно перегенерируем прикаждом обновлении (пока)
ALTER желательно генерировать автоматически!
18 / 27
Генерация ALTERАвтоматически если есть cтарое и новое описание:
Для каждой таблицы единый ALTER TABLE:
• удаление индексов, отсутствующих в старой версии• удаление внешних ключей, отсутствующих в старой версии• удаление колонок, отсутствующих в новой версии• создание новых колонок• создание новых индексов• создание новых внешних ключей
Проблема: переименование колонок.
19 / 27
Расширяемость
Некоторым объектам схемы хотелось бы добавлять некоестандартное поведение:
• таблицы с иерархической структурой данных• денормализованные счетчики для 1→ N• генерация идентификаторов
Как правило, однотипные триггеры, отличающиеся толькоименами колонок и таблиц.
20 / 27
Использование расширенийРасширяем поведение объектов схемы с помощьюспециальных атрибутов ext:
ext:расширение="значение"ext:расширение.параметр="значение"
<Column name="id" ext:auto="uuid" ... /><Column name="parent" ext:tree="item_tree" ... /><ForeignKey name="category" ext:count="numOfItems" ... >
Каждое расширение реализуется как отдельный XSL.
21 / 27
Примеры расширений
ext:auto Автоматическая генерация значений поля,например, uuid, rand.Триггеры на добавление записи.
ext:count Автоматические счетчики связанных записей дляотношения 1→ N.Триггеры на добавление, изменение и удалениезаписи.
ext:tree Сlosure table для иерархических данных.Создание таблиц closure, триггеры на добавление,изменение и удаление записи.
22 / 27
Реализация: baser
• пока тестовый прототип, посмотрим что получится• shell + набор XSL-шаблонов• функциональность в XSL — можно интегрировать в PHP• пока минимальная функциональность• генерация SQL — 500 строк, closure table — 80 строк
23 / 27
baser: создание схемы
# По умолчанию из database.xml в текущем каталоге:$ baser schema
# Или из указанного файла:$ baser -c db.xml schema
# Можно не всю схему, а отдельную таблицу:$ baser schema mytable
24 / 27
baser: изменение схемы# Сохраняем текущее состояние:$ baser freeze# Вносим изменения:$ vim database.xml# Генерируем ALTER:$ baser schema# Вносим повторные изменения, если нужно:$ vim database.xml# И так пока не надоест:$ baser schema# Результат нас устраивает,# удаляем сохраненное состояние:$ baser boil
25 / 27
$ baser graph
category
id bigintparent biginttitle varchar(255)numOfProductsmediumint
BI AI AU BD
parent_title
+ parent+ title
parent_title
product
id bigintcategory bigintnumOfOfferssmallinttitle varchar(255)
BI AI AU AD
category
(id)
(category)
category_title
+ category+ title
category_title
category_offers
+ category+ numOfOffers
category_offers
category_detail
id bigintbody text
category
(id)
(id)
product_detail
id bigintbody text
product
(id)
(id)
offer
id bigintproductbiginttitle varchar(128)price decimal(10,2)
BI AI AU AD
product
(id)
(product)
product_price
+ product+ price
product_price
product_title
+ product+ title
product_title
category_tree
p bigintc bigintd tinyint unsigned
d
+ d
d
26 / 27
Что дальше
• исправление ошибок• скрипт для генерации определения по базе• больше расширений• больше семантики в описании (группы, зависимости и т.д.)• именованные freeze (несколько различных для одногоопределения)
• генерация HTML документации по описанию базы• (возможно) визуальный diff
27 / 27