Когда использовать унаследованные таблицы в PostgreSQL?


84

В каких ситуациях следует использовать унаследованные таблицы? Я попытался использовать их очень кратко, и наследование не было похоже на мир ООП.

Я думал, что это работает так:

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

Единственное использование, о котором я мог думать, - это поля, которые обычно используются, например row_is_deleted , created_at , modified_at . Это единственное использование унаследованных таблиц?

Ответы:


111

Есть несколько основных причин использования наследования таблиц в postgres.

Допустим, у нас есть несколько таблиц, необходимых для статистики, которые создаются и заполняются каждый месяц:

statistics
    - statistics_2010_04 (inherits statistics)
    - statistics_2010_05 (inherits statistics)

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

Так что же делает наследование классной функцией - почему классно разделять данные?

  • ПРОИЗВОДИТЕЛЬНОСТЬ: при выборе данных мы ВЫБИРАЕМ * ИЗ статистики, ГДЕ дата МЕЖДУ x и Y, а Postgres использует только таблицы, где это имеет смысл. Например. SELECT * FROM statistics WHERE date BETWEEN '2010-04-01' AND '2010-04-15' сканирует только таблицу statistics_2010_04, все остальные таблицы не будут затронуты - быстро!
  • Размер индекса: у нас нет большой толстой таблицы с большим жирным индексом на дату столбца. У нас есть маленькие таблицы в месяц, с маленькими индексами - более быстрое чтение.
  • Обслуживание: мы можем запускать вакуумное полное, переиндексирование, кластер для каждой таблицы за месяц, не блокируя все остальные данные.

Чтобы узнать, как правильно использовать наследование таблиц для повышения производительности, смотрите руководство postgresql. Вам необходимо установить ограничения CHECK для каждой таблицы, чтобы сообщить базе данных, по какому ключу ваши данные разделяются (секционируются).

Я активно использую наследование таблиц, особенно когда дело касается хранения данных журнала, сгруппированных по месяцам. Подсказка: если вы храните данные, которые никогда не изменятся (данные журнала), создайте или индексируйте с помощью CREATE INDEX ON () WITH (fillfactor = 100); Это означает, что в индексе не будет зарезервировано место для обновлений - индекс на диске меньше.

ОБНОВЛЕНИЕ: по умолчанию fillfactor равно 100, из http://www.postgresql.org/docs/9.1/static/sql-createtable.html :

Коэффициент заполнения таблицы - это процент от 10 до 100. 100 (полная упаковка) - значение по умолчанию.


13
Еще один пример разделения
Фрэнк Хайкенс

4
В вашем пункте 1, как Postgres понимает, в какой из таблиц нужно искать? Вы выбираете из родительской таблицы, и диапазон дат - лишь удобный пример разделения. Родительская таблица не может знать эту логику. Или я не прав?
Александр Паламарчук

4
Выполнение запроса к родительской таблице фактически аналогично выполнению запроса к UNION ALL для каждой таблицы-потомка в общих строках. Планировщик запросов знает о проверочных ограничениях, которые определяют каждую секцию, и, пока они не перекрывают секции, использует их, чтобы определить, что он может пропустить проверку таблиц, для которых CHECK указывает, что строки не будут возвращены. Postgres docs по этому
поводу

@avesus хех ... Сам по себе взятый выше код достоин такого сарказма. Такие вещи типично превращать в какую-то процедуру обслуживания. Это может быть такая же простая процедура, как хранимая процедура, которая позаботится об этом при определенных условиях, задание cron или что-то еще. Разбиение по дате является обычным делом, но я обнаружил, что время от времени делю разделы по распределению табличных пространств, и для этого требуется некоторая внешняя информация - 30 минут, которые требуются для написания няни для раздела, того стоят для управления. это дает вам.
zxq9 05

Хм. Вы уверены, что он не блокируется? У меня аналогичная установка, но когда я запускаю команду CLUSTER на одном разделе, оператор SELECT для данных, хранящихся в блоках другого раздела!
Э. ван Путтен

37

«Наследование таблиц» означает нечто иное, чем «наследование классов», и они служат другим целям.

Postgres - это все об определениях данных. Иногда действительно сложные определения данных. ООП (в обычном понимании вещей цвета Java) - это подчинение поведения определениям данных в единой атомарной структуре. Назначение и значение слова «наследство» здесь существенно различаются.

В области ООП я мог бы определить (здесь очень нечетко с синтаксисом и семантикой):

import life

class Animal(life.Autonomous):
  metabolism = biofunc(alive=True)

  def die(self):
    self.metabolism = False

class Mammal(Animal):
  hair_color = color(foo=bar)

  def gray(self, mate):
    self.hair_color = age_effect('hair', self.age)

class Human(Mammal):
  alcoholic = vice_boolean(baz=balls)

Таблицы для этого могут выглядеть так:

CREATE TABLE animal
  (name       varchar(20) PRIMARY KEY,
   metabolism boolean NOT NULL);

CREATE TABLE mammal
  (hair_color  varchar(20) REFERENCES hair_color(code) NOT NULL,
   PRIMARY KEY (name))
  INHERITS (animal);

CREATE TABLE human
  (alcoholic  boolean NOT NULL,
   FOREIGN KEY (hair_color) REFERENCES hair_color(code),
   PRIMARY KEY (name))
  INHERITS (mammal);

Но где поведение? Они никуда не подходят. Это не цель «объектов», как они обсуждаются в мире баз данных, потому что базы данных связаны с данными, а не с процедурным кодом. Вы можете писать функции в базе данных для выполнения расчетов за вас (часто очень хорошая идея, но не совсем то, что подходит для этого случая), но функции - это не то же самое, что методы - методы, как они понимаются в форме ООП, о которой вы говорите about намеренно менее гибкие.

О наследовании как схематическом устройстве следует указать еще на одно: в Postgres 9.2 нет возможности ссылаться на ограничение внешнего ключа сразу для всех разделов / членов семейства таблиц. Вы можете написать чеки, чтобы сделать это или обойти это другим способом, но это не встроенная функция (на самом деле это сводится к проблемам со сложной индексацией, и никто не написал биты, необходимые для того, чтобы сделать это автоматическим). Вместо того, чтобы использовать для этой цели наследование таблиц, часто в базе данных для наследования объектов лучшим вариантом является создание схемных расширений таблиц. Что-то вроде этого:

CREATE TABLE animal
  (name       varchar(20) PRIMARY KEY,
   ilk        varchar(20) REFERENCES animal_ilk NOT NULL,
   metabolism boolean NOT NULL);

CREATE TABLE mammal
  (animal      varchar(20) REFERENCES animal PRIMARY KEY,
   ilk         varchar(20) REFERENCES mammal_ilk NOT NULL,
   hair_color  varchar(20) REFERENCES hair_color(code) NOT NULL);


CREATE TABLE human
  (mammal     varchar(20) REFERENCES mammal PRIMARY KEY,
   alcoholic  boolean NOT NULL);

Теперь у нас есть каноническая ссылка на экземпляр животного, которую мы можем надежно использовать в качестве ссылки на внешний ключ, и у нас есть столбец «ilk», который ссылается на таблицу определений xxx_ilk, которая указывает на «следующую» таблицу расширенных данных ( или указывает, что его нет, если подобный тип сам является универсальным). Написание табличных функций, представлений и т. Д. На основе такой схемы настолько просто, что большинство фреймворков ORM делают именно такие вещи в фоновом режиме, когда вы прибегаете к наследованию классов в стиле ООП для создания семейств типов объектов.


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

1
@puk Сначала вам нужно решить, почему вы добавляете всех известных млекопитающих. Форма данных будет определяться тем, как они будут использоваться (в этом случае, вероятно, нет необходимости иметь таблицу для каждого животного - рассмотрите базы данных для игровых бестиариев, где у вас действительно есть все типы мобов ). В приведенном выше случае я обычно добавляю представление, которое является наиболее распространенным случаем mammal JOIN human, просто потому, что создание соединения каждый раз раздражает. Но не избегайте присоединений . Соединения - это то, что помещает R в СУБД. Если вам не нравятся объединения, вам следует использовать другой тип базы данных.
zxq9

@ zxq9: Я предполагаю, что массивные, неэффективные объединения из-за больших таблиц - вот где в игру вступают материализованные представления? (Я так долго не пользуюсь Postgres)
Марк К. Коуэн

1
@MarkKCowan Joins не являются неэффективными. Что неэффективно, так это попытки присоединиться к неиндексированным, неуникальным полям (потому что схема далеко не нормализована) из-за небрежного дизайна. В таких случаях может оказаться полезным материализованное представление. Материализованные представления также полезны в том случае, если вам нужны нормализованные данные в качестве основы схемы (часто это верно), но также необходимо несколько рабочих денормализованных представлений, с которыми легче работать либо для эффективности обработки (предварительная загрузка вычислений), либо для когнитивной эффективности. Однако если вы пишете больше, чем читаете, это пессимизация.
zxq9

1
@MarkKCowan "Медленно" - термин относительный. По моему опыту, в крупных бизнес-системах и игровых серверах, где мы можем принять ~ 50 мсек на возврат запроса, 20 соединений таблиц никогда не были проблемой (по крайней мере, в Postgres 8+). Но в тех случаях, когда руководство хочет, чтобы ответы на <1 мсек на> 10b строк объединялись в 5+ таблицах с неиндексированными данными (или производными значениями!) ... ни одна система в мире не будет чувствовать себя "быстрой", кроме как сделать это соединение в прошлом месяце и спрятать его в быстром магазине K / V (что, по сути, является тем, что материализованное представление может действовать в особых обстоятельствах). Невозможно избежать компромисса ни во время записи, ни во время чтения.
zxq9

6

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

Наследование также можно использовать как инструмент разделения . Это особенно полезно, когда у вас есть таблицы, которые должны постоянно расти (таблицы журналов и т. Д.).


1
Ограничения таблицы не наследуются, поэтому это больше, чем просто внешние ключи. Вы можете применить ограничения таблицы к дочерней таблице (таблицам), поскольку они созданы в вашем DDL, или вы можете написать триггеры для воздействия на те же ограничения.
Wexxor

3

В основном наследование используется для разделения, но иногда оно полезно в других ситуациях. В моей базе данных много таблиц, различающихся только внешним ключом. Моя таблица "изображение" абстрактного класса содержит "ID" (первичный ключ для него должен быть в каждой таблице) и растр PostGIS 2.0. Унаследованные таблицы, такие как site_map или artifact_drawing, имеют столбец внешнего ключа (текстовый столбец site_name для site_map, целочисленный столбец artifact_id для таблицы artifact_drawing и т. Д.) И ограничения первичного и внешнего ключей; остальное наследуется от таблицы «image». Я подозреваю, что в будущем мне, возможно, придется добавить столбец «описание» ко всем таблицам изображений, так что это может сэкономить мне довольно много работы, не создавая реальных проблем (ну,

РЕДАКТИРОВАТЬ: еще одно хорошее применение: при обработке двух таблиц незарегистрированных пользователей у других СУБД есть проблемы с обработкой двух таблиц, но в PostgreSQL это просто - просто добавьте, ONLYкогда вас не интересуют данные в унаследованной таблице «незарегистрированных пользователей».


2

Единственный опыт, который у меня есть с унаследованными таблицами, - это партиционирование. Он отлично работает, но это не самая сложная и простая в использовании часть PostgreSQL.

На прошлой неделе мы искали ту же проблему ООП, но у нас было слишком много проблем с Hibernate (не нравилась наша настройка), поэтому мы не использовали наследование в PostgreSQL.


0

Я использую наследование, когда у меня есть отношения более чем 1 на 1 между таблицами.

Пример: предположим, вы хотите сохранить местоположения на карте объектов с атрибутами x, y, вращением, масштабом.

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

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


-4

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

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


4
Неверно, что функция наследования PostgreSQL нарушает реляционную модель, нарушая принцип информации. Информационный принцип гласит, что все данные в реляционной базе данных представлены значениями данных в отношениях, и все результаты запроса снова представлены как отношения. ( En.wikipedia.org/wiki/Relational_model ) Это всегда так, поскольку все таблицы , которые наследуют другую таблицу, снова являются простыми таблицами. По этой причине также не существует такой вещи, как «сумка», что бы это ни значило.
Роланд

2
Что ж, Википедия вряд ли может служить справочником относительно реляционной модели; он отказывается признать, что SQL нарушает реляционную модель. Сумка - это таблица без ключа, потому что потенциально у нее есть дубликаты, а значит, не связь; отношение должно быть набором.
Леандро

Проблема не в самой функции, а в том, как она используется. Если вы работаете с uuids в качестве идентификаторов, у вас будут уникальные ключи для всех подтаблиц.
Роланд

Вы правы, но проблема здесь в том, что наследование приводит к тому, что разработчик модели игнорирует реляционную модель. UUID - это не настоящие ключи, а суррогатные. Еще нужно объявить естественные ключи.
Леандро
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.