×
Мы обрабатываем cookies, чтобы сделать наш сайт удобнее и персонализированнее для вас. Подробнее: политика использования «cookies» и «политики конфиденциальности».

Для самостоятельной настройки ознакомьтесь с инструкцией

Дополнительные настройки cookies в браузерах

Файлы cookie автоматически загружаются в ваш браузер при посещении веб-сайта. У вас есть возможность управлять этими файлами. Если Вы не согласны с использованием файлов cookies, запретите их сохранение на своём устройстве, удалите уже имеющиеся файлы cookies через настройки браузера или прекратите использование сайта.

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

Инструкция по отключению cookies
Принять
Настроить
Отклонить

ДОКУМЕНТАЦИЯ

Выберите версию, форк и язык для СУБД Digital Q.DataBase, чтобы прочитать или скачать всю документацию.
Техподдержка
Документация
Диасофт
Авторские права © 2016–2025 ООО "Диасофт Экосистема"
Скачать всю документацию:

2.2.12. Секционирование таблиц

2.2.12.1. Обзор
2.2.12.2. Декларативное секционирование
2.2.12.3. Секционирование с использованием наследования
2.2.12.4. Исключение секций
2.2.12.5. Секционирование и
2.2.12.6. Рекомендации по декларативному секционированию

Digital Q.DataBase поддерживает базовое секционирование таблиц. В данном разделе рассматривается назначение и способы реализации секционирования в процессе проектирования базы данных.

2.2.12.1. Обзор #

Под секционированием понимается разделение логически единой большой таблицы на физические сегменты меньшего размера. Секционирование может обеспечить ряд преимуществ:

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

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

  • Массовую загрузку и удаление данных можно выполнять путем добавления или удаления секций, если сценарий использования учтен в схеме секционирования. Удаление отдельной секции с помощью DROP TABLE или выполнение команды ALTER TABLE DETACH PARTITION происходит гораздо быстрее, чем массовая операция. Данные команды также полностью исключают VACUUM издержки, возникающие при массовой DELETE.

  • Редко используемые данные могут быть перенесены на более дешевые и медленные носители данных.

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

Digital Q.DataBase предлагает встроенную поддержку следующих форм секционирования:

Секционирование по диапазонам #

Таблица разбивается на «диапазоны» определяемые ключевым столбцом или набором столбцов, при этом диапазоны значений, назначенные различным секциям, не перекрываются. Например, можно выполнить секционирование по диапазонам дат или по диапазонам идентификаторов для конкретных бизнес-объектов. Границы каждого диапазона включают в себя нижнее значение и исключают верхнее. Например, если диапазон секции начинается от 1 до 10, а диапазон следующей секции — с версии 10 до 20, то значение 10 относится ко второй секции, а не к первой.

Секционирование по списку #

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

Секционирование по хэшу #

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

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

2.2.12.2. Декларативное секционирование #

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

Сама секционированная таблица является «виртуальной» таблицей, не имеющей собственного хранилища. Вместо этого хранилище принадлежит секции, которые в остальном являются обычными таблицами, связанными с секционированной таблицей. Каждая секция хранит подмножество данных, определяемое её границами секции. Все строки, вставляемые в секционированную таблицу, будут перенаправлены в соответствующую секцию на основе значений столбцов ключа секционирования. Обновление ключа секционирования строки приведет к её перемещению в другую секцию, если она больше не соответствует границам своей исходной секции.

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

Обычную таблицу невозможно преобразовать в секционированную или наоборот. Однако можно добавить существующую обычную или секционированную таблицу в качестве секции к секционированной таблице или удалить секцию из секционированной таблицы, превратив её в отдельную таблицу; это позволяет упростить и ускорить многие процессы обслуживания. См. ALTER TABLE для получения дополнительных сведений о ATTACH PARTITION и DETACH PARTITION подкомандах.

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

2.2.12.2.1. Пример #

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

CREATE TABLE measurement (
    city_id         int not null,
    logdate         тип DATE not null,
    peaktemp        int,
    unitsales       int
);

Известно, что большинство запросов будут обращаться к данным только за последнюю неделю, месяц или квартал, так как основное назначение этой таблицы — подготовка онлайн-отчетов для руководства. Чтобы сократить объем хранимых устаревших данных, мы принимаем решение хранить данные только за последние 3 года. В начале каждого месяца данные за самый старый месяц будут удаляться. В данной ситуации можно использовать секционирование, чтобы обеспечить выполнение всех требований к таблице measurement.

Для использования декларативного секционирования в данном случае выполните следующие шаги:

  1. Создайте measurement таблицу как секционированную, указав PARTITION BY , которое включает метод секционирования (RANGE в данном случае) и список столбцов, используемых в качестве ключа секционирования.

    CREATE TABLE measurement (
        city_id         int not null,
        logdate         тип DATE not null,
        peaktemp        int,
        unitsales       int
    ) PARTITION BY RANGE (logdate);
    

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

    Созданные таким образом секции во всех отношениях являются обычными Digital Q.DataBase таблицами (или, возможно, сторонними таблицами). Можно определить табличное пространство и параметры хранения для каждой секции отдельно.

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

    CREATE TABLE measurement_y2006m02 PARTITION OF measurement
        FOR VALUES FROM ('2006-02-01') TO ('2006-03-01');
    
    CREATE TABLE measurement_y2006m03 PARTITION OF measurement
        FOR VALUES FROM ('2006-03-01') TO ('2006-04-01');
    
    ...
    CREATE TABLE measurement_y2007m11 PARTITION OF measurement
        FOR VALUES FROM ('2007-11-01') TO ('2007-12-01');
    
    CREATE TABLE measurement_y2007m12 PARTITION OF measurement
        FOR VALUES FROM ('2007-12-01') TO ('2008-01-01')
        TABLESPACE fasttablespace;
    
    CREATE TABLE measurement_y2008m01 PARTITION OF measurement
        FOR VALUES FROM ('2008-01-01') TO ('2008-02-01')
        WITH (parallel_workers = 4)
        TABLESPACE fasttablespace;
    

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

    Если необходимо реализовать подсекционирование, следует снова указать PARTITION BY выражение PARTITION BY RANGE в командах, используемых для создания отдельных секций, например:

    CREATE TABLE measurement_y2006m02 PARTITION OF measurement
        FOR VALUES FROM ('2006-02-01') TO ('2006-03-01')
        PARTITION BY RANGE (peaktemp);
    

    После создания секций для measurement_y2006m02, любые данные, вставляемые в measurement таблицу measurement, которые перенаправляются в measurement_y2006m02 (или данные, напрямую вставляемые в таблицу measurement_y2006m02, measurement_y2006m02, что разрешено при условии соблюдения соответствующего ограничения секции) будут далее перенаправлены в одну из её секций на основе значений peaktemp Указанный ключ секционирования может перекрываться с ключом секционирования родительского элемента, хотя следует проявлять осторожность при определении границ подсекции так, чтобы набор принимаемых данных являлся подмножеством того, что допускают собственные границы секции; система не пытается проверять, так ли это на самом деле.

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

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

  3. Создайте индекс для ключевого столбца (столбцов), а также любые другие необходимые индексы в секционированной таблице. (Индекс по ключу не является строго обязательным, но в большинстве сценариев он полезен). Это автоматически создает соответствующий индекс в каждой секции, и любые секции, создаваемые или присоединяемые позже, также будут иметь такой индекс. Индекс или ограничение уникальности, объявленные в секционированной таблице представляет собой «виртуальной» таким же образом, как и секционированная таблица следующее: фактически данные находятся в дочерних индексах для отдельных секций таблицы.

    CREATE INDEX ON measurement (logdate);
    

  4. Убедитесь, что enable_partition_pruning параметр конфигурации не отключен в postgresql.conf. В противном случае запросы не будут оптимизированы должным образом.

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

2.2.12.2.2. Обслуживание секций #

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

Самый простой вариант удаления старых данных — удаление секции, которая больше не нужна:

DROP TABLE measurement_y2006m02;

Это позволяет очень быстро удалить миллионы записей, так как не требуется удалять каждую запись по отдельности. Однако обратите внимание, что вышеуказанная команда требует наложения ACCESS EXCLUSIVE блокировки на родительскую таблицу.

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

ALTER TABLE measurement DETACH PARTITION measurement_y2006m02;
ALTER TABLE measurement DETACH PARTITION measurement_y2006m02 CONCURRENTLY;

Это позволяет выполнять дальнейшие операции с данными перед их удалением. Например, это подходящий момент для создания резервной копии данных с помощью COPY, pg_dump, или аналогичных инструментов. Также это время можно использовать для агрегирования данных в форматы меньшего объема, выполнения иных манипуляций с данными или формирования отчетов. Первая форма команды требует ACCESS EXCLUSIVE блокировку родительской таблицы. Добавление CONCURRENTLY квалификатора, как во второй форме, позволяет операции отсоединения запрашивать только SHARE UPDATE EXCLUSIVE блокировку родительской таблицы, но подробности об ограничениях см. в описании ALTER TABLE ... DETACH PARTITION for details on the restrictions.

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

CREATE TABLE measurement_y2008m02 PARTITION OF measurement
    FOR VALUES FROM ('2008-02-01') TO ('2008-03-01')
    TABLESPACE fasttablespace;

В качестве альтернативы созданию новой секции иногда удобнее создать новую таблицу отдельно от структуры секционирования, а затем присоединить её как секцию. Это позволяет загружать, проверять и преобразовывать новые данные до того, как они появятся в секционированной таблице. Более того, команда ATTACH PARTITION операция требует только SHARE UPDATE EXCLUSIVE блокировки секционированной таблицы вместо ACCESS EXCLUSIVE блокировки, требуемой CREATE TABLE ... выражение PARTITION OF, что делает её более подходящей для параллельных операций с секционированной таблицей; см. ALTER TABLE ... ATTACH PARTITION для получения дополнительных сведений. Параметр CREATE TABLE ... LIKE может быть полезен, чтобы избежать утомительного повторения определения родительской таблицы; например:

CREATE TABLE measurement_y2008m02
  (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS)
  TABLESPACE fasttablespace;

ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
   CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );

\copy measurement_y2008m02 from 'measurement_y2008m02'
-- possibly some other data preparation work

ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02
    FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' );

Обратите внимание, что при выполнении команды ATTACH PARTITION команда, таблица будет просканирована для проверки ограничения секции с удержанием ACCESS EXCLUSIVE блокировки этой секции. Как показано выше, рекомендуется избегать этого сканирования путём создания CHECK ограничение, соответствующее ожидаемому ограничению секции в таблице перед её присоединением. Как только ATTACH PARTITION завершится, рекомендуется удалить ставшее избыточным CHECK ограничение. Если сама присоединяемая таблица является секционированной, то каждая из её подсекций будет рекурсивно заблокирована и просканирована до тех пор, пока не будет найдено подходящее CHECK ограничение или не будут достигнуты листовые секции.

Аналогично, если секционированная таблица имеет DEFAULT секцию, рекомендуется создать CHECK ограничение, которое исключает ограничение присоединяемой секции. Если это не будет сделано, то DEFAULT секция будет просканирована для подтверждения того, что она не содержит записей, которые должны находиться в присоединяемой секции. Эта операция будет выполнена при удержании ACCESS EXCLUSIVE блокировки на DEFAULT секции. Если DEFAULT секция сама является секционированной таблицей, то каждая из её секций будет рекурсивно проверена так же, как и присоединяемая таблица, как упоминалось выше.

Как упоминалось ранее, можно создавать индексы для секционированных таблиц так, чтобы они автоматически применялись ко всей иерархии. Это может быть очень удобно, так как проиндексированы будут не только все существующие секции, но и любые будущие. Однако одним из ограничений при создании новых индексов для секционированных таблиц является невозможность использования CONCURRENTLY квалификатора, что может привести к длительному времени блокировки. Чтобы избежать этого, можно использовать CREATE INDEX ON ONLY для секционированной таблицы; это создаст новый индекс, помеченный как недействительный, что предотвратит его автоматическое применение к существующим секциям. Вместо этого индексы можно создать индивидуально для каждой секции с помощью CONCURRENTLY и присоединены к секционированному индексу родительского элемента с использованием ALTER INDEX ... ATTACH PARTITION. Как только индексы всех секций будут присоединены к родительскому индексу, родительский индекс будет автоматически помечен как действительный. Пример:

CREATE INDEX measurement_usls_idx ON ONLY measurement (unitsales);

CREATE INDEX CONCURRENTLY measurement_usls_200602_idx
    ON measurement_y2006m02 (unitsales);
ALTER INDEX measurement_usls_idx
    ATTACH PARTITION measurement_usls_200602_idx;
...

Этот метод можно использовать и с UNIQUE и PRIMARY KEY ограничениями; индексы создаются неявно при создании ограничения. Пример:

ALTER TABLE ONLY measurement ADD UNIQUE (city_id, logdate);

ALTER TABLE measurement_y2006m02 ADD UNIQUE (city_id, logdate);
ALTER INDEX measurement_city_id_logdate_key
    ATTACH PARTITION measurement_y2006m02_city_id_logdate_key;
...

2.2.12.2.3. Ограничения #

Для секционированных таблиц действуют следующие ограничения:

  • При создании ограничения уникальности или ограничения PRIMARY KEY в секционированной таблице ключи секционирования не должны содержать выражений или вызовов функций, а в столбцы ограничения должны входить все столбцы ключа секционирования. Данное ограничение обусловлено тем, что отдельные индексы, составляющие ограничение, могут напрямую обеспечивать уникальность только внутри своих собственных секций; следовательно, сама структура секционирования должна гарантировать отсутствие дубликатов в разных секциях.

  • Аналогично, ограничение исключения должно включать в себя все столбцы ключа секционирования. Более того, данное ограничение должно проверять эти столбцы на равенство (а не, например, &&). Это ограничение также связано с невозможностью обеспечить соблюдение межсекционных правил. Ограничение может включать дополнительные столбцы, не входящие в ключ секционирования, и они могут сравниваться с любыми необходимыми операторами.

  • BEFORE ROW триггеры для INSERT не могут изменять секцию, являющуюся конечным местом назначения для новой строки.

  • Смешивание временных и постоянных отношений в одном дереве секций не допускается. Следовательно, если секционированная таблица является постоянной, такими же должны быть её секции; аналогично, если секционированная таблица является временной. При использовании временных отношений все элементы дерева секций должны относиться к одному и тому же сеансу.

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

Поскольку иерархия секций, состоящая из секционированной таблицы и её секций, всё же является иерархией наследования, tableoid к ней применяются все обычные правила наследования, описанные в Раздел 2.2.11, за некоторыми исключениями:

  • Секции не могут иметь столбцов, отсутствующих в родительском элементе. Невозможно указывать столбцы при создании секций с помощью CREATE TABLE, как невозможно и добавлять столбцы в секции после их создания с помощью ALTER TABLE. Таблицы могут быть добавлены как секции с помощью ALTER TABLE ... ATTACH PARTITION только в том случае, если их столбцы точно соответствуют родительскому элементу.

  • Оба типа CHECK и NOT NULL ограничения секционированной таблицы всегда наследуются всеми её секциями. CHECK ограничения, помеченные как NO INHERIT не допускается создавать в секционированных таблицах. Нельзя удалить NOT NULL ограничение для столбца секции, если аналогичное ограничение присутствует в родительском элементе таблицы.

  • Использование команды ONLY для добавления или удаления ограничения только в секционированной таблице поддерживается до тех пор, пока в ней отсутствуют секции. После создания секций использование команды ONLY приведет к ошибке для любых ограничений, кроме UNIQUE и PRIMARY KEY. Вместо этого ограничения для самих секций могут быть добавлены и (если они отсутствуют в родительском элементе таблицы) удалены.

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

2.2.12.3. Секционирование с использованием наследования #

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

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

  • Наследование таблиц допускает множественное наследование.

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

2.2.12.3.1. Пример #

В данном примере создается структура секционирования, эквивалентная представленному выше примеру декларативного секционирования. Выполните следующие шаги:

  1. Создайте «корневая» таблица, от которой будут наследоваться все «дочерние» таблицы. Данная таблица не будет содержать данных. Не определяйте ограничения CHECK для этой таблицы, только если вы не планируете, чтобы они применялись в равной степени ко всем дочерним таблицам. Также нет смысла определять в ней какие-либо индексы или ограничения уникальности. В нашем примере корневой таблицей является measurement таблица в ее первоначальном виде:

    CREATE TABLE measurement (
        city_id         int not null,
        logdate         тип DATE not null,
        peaktemp        int,
        unitsales       int
    );
    

  2. Создайте несколько «дочерние» таблиц, каждая из которых наследует свойства корневой таблицы. Как правило, в эти таблицы не добавляются новые столбцы в дополнение к набору, унаследованному от корневой таблицы. Так же, как и при декларативном секционировании, эти таблицы во всех отношениях являются обычными Digital Q.DataBase таблицами (или сторонними таблицами).

    CREATE TABLE measurement_y2006m02 () INHERITS (measurement);
    CREATE TABLE measurement_y2006m03 () INHERITS (measurement);
    ...
    CREATE TABLE measurement_y2007m11 () INHERITS (measurement);
    CREATE TABLE measurement_y2007m12 () INHERITS (measurement);
    CREATE TABLE measurement_y2008m01 () INHERITS (measurement);
    

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

    Типичные примеры:

    CHECK ( x = 1 )
    CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
    CHECK ( outletID >= 100 AND outletID < 200 )
    

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

    CHECK ( outletID BETWEEN 100 AND 200 )
    CHECK ( outletID BETWEEN 200 AND 300 )
    

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

    CREATE TABLE measurement_y2006m02 (
        CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
    ) INHERITS (measurement);
    
    CREATE TABLE measurement_y2006m03 (
        CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
    ) INHERITS (measurement);
    
    ...
    CREATE TABLE measurement_y2007m11 (
        CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
    ) INHERITS (measurement);
    
    CREATE TABLE measurement_y2007m12 (
        CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
    ) INHERITS (measurement);
    
    CREATE TABLE measurement_y2008m01 (
        CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
    ) INHERITS (measurement);
    

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

    CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
    CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
    CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
    CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
    CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
    

  5. Необходимо, чтобы приложение могло выполнять команду команда INSERT INTO measurement ... и данные автоматически перенаправлялись в соответствующую дочернюю таблицу. Этого можно добиться путем привязки подходящей функции триггера к корневой таблице. Если данные будут добавляться только в последнюю дочернюю таблицу, можно использовать максимально простую функцию триггера:

    CREATE OR REPLACE FUNCTION measurement_insert_trigger()
    RETURNS TRIGGER AS $$
    BEGIN
        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
        RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
    

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

    CREATE TRIGGER insert_measurement_trigger
        BEFORE INSERT ON measurement
        FOR EACH ROW EXECUTE FUNCTION measurement_insert_trigger();
    

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

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

    CREATE OR REPLACE FUNCTION measurement_insert_trigger()
    RETURNS TRIGGER AS $$
    BEGIN
        IF ( NEW.logdate >= DATE '2006-02-01' AND
             NEW.logdate < DATE '2006-03-01' ) THEN
            INSERT INTO measurement_y2006m02 VALUES (NEW.*);
        ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
                NEW.logdate < DATE '2006-04-01' ) THEN
            INSERT INTO measurement_y2006m03 VALUES (NEW.*);
        ...
        ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
                NEW.logdate < DATE '2008-02-01' ) THEN
            INSERT INTO measurement_y2008m01 VALUES (NEW.*);
        ELSE
            RAISE EXCEPTION 'Дата вне диапазона.  Исправьте функцию measurement_insert_trigger()!';
        END IF;
        RETURN NULL;
    END;
    $$
    LANGUAGE plpgsql;
    

    Определение триггера осталось таким же, как и раньше. Обратите внимание, что каждая IF должна в точности соответствовать CHECK ограничению для её дочерней таблицы.

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

    Примечание

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

    Другой способ перенаправления вставок в соответствующую дочернюю таблицу заключается в создании правил вместо триггера для корневой таблицы. Например:

    CREATE RULE measurement_insert_y2006m02 AS
    ON INSERT TO measurement WHERE
        ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
    DO INSTEAD
        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
    ...
    CREATE RULE measurement_insert_y2008m01 AS
    ON INSERT TO measurement WHERE
        ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
    DO INSTEAD
        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
    

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

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

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

  6. Убедитесь, что constraint_exclusion параметр конфигурации не отключен в postgresql.conf; в противном случае к дочерним таблицам может осуществляться избыточное обращение.

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

2.2.12.3.2. Обслуживание секционирования на основе наследования #

Чтобы быстро удалить старые данные, достаточно просто удалить дочернюю таблицу, которая более не требуется:

DROP TABLE measurement_y2006m02;

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

ALTER TABLE measurement_y2006m02 NO INHERIT measurement;

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

CREATE TABLE measurement_y2008m02 (
    CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' )
) INHERITS (measurement);

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

CREATE TABLE measurement_y2008m02
  (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
   CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
\copy measurement_y2008m02 from 'measurement_y2008m02'
-- possibly some other data preparation work
ALTER TABLE measurement_y2008m02 INHERIT measurement;

2.2.12.3.3. Ограничения #

В отношении секционирования, реализованного с помощью наследования, действуют следующие предостережения:

  • Не существует автоматического способа проверить, что все CHECK ограничения являются взаимно исключающими. Надёжнее создавать код, который генерирует дочерние таблицы, а также создает и/или изменяет связанные объекты, чем прописывать каждый из них вручную.

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

  • Представленные здесь схемы предполагают, что значения ключевых столбцов строки никогда не изменяются или, по крайней мере, изменяются не настолько, чтобы потребовалось ее перемещение в другую секцию. Команда UPDATE которая пытается выполнить это, завершится ошибкой из-за CHECK ограничений. Если необходимо обрабатывать такие случаи, можно добавить соответствующие триггеры обновления на дочерние таблицы, однако это значительно усложняет управление структурой.

  • При использовании ручных VACUUM или ANALYZE команд не забудьте, что их необходимо запускать для каждой дочерней таблицы по отдельности. Такая команда, как:

    ANALYZE measurement;
    

    обработает только корневую таблицу.

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

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

2.2.12.4. Исключение секций #

Исключение секций — это метод оптимизации запросов, который повышает производительность для декларативно секционированных таблиц. Например:

SET enable_partition_pruning = on;                 -- по умолчанию
SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';

Без исключения секций вышеуказанный запрос сканировал бы каждую из секций measurement таблицей. При включенном исключении секций планировщик проанализирует определение каждой секции и докажет, что секцию не требуется сканировать, так как она не может содержать строк, соответствующих WHERE clause. Когда планировщик может это доказать, он исключает (отсекает) секцию из плана запроса.

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

SET enable_partition_pruning = off;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
                                    ПЛАН ЗАПРОСА
-------------------------------------------------------------------​ ----------------
 Aggregate  (cost=188.76..188.77 rows=1 width=8)
   ->  Append  (cost=0.00..181.05 rows=3085 width=0)
         ->  Seq Scan on measurement_y2006m02  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
         ->  Seq Scan on measurement_y2006m03  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
...
         ->  Seq Scan on measurement_y2007m11  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
         ->  Seq Scan on measurement_y2007m12  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)
         ->  Seq Scan on measurement_y2008m01  (cost=0.00..33.12 rows=617 width=0)
               Filter: (logdate >= '2008-01-01'::date)

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

SET enable_partition_pruning = on;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
                                    ПЛАН ЗАПРОСА
-------------------------------------------------------------------​ ----------------
 Aggregate  (cost=37.75..37.76 rows=1 width=8)
   ->  Seq Scan on measurement_y2008m01  (cost=0.00..33.12 rows=617 width=0)
         Filter: (logdate >= '2008-01-01'::date)

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

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

  • Во время инициализации плана запроса. Исключение секций может быть выполнено здесь для значений параметров, которые известны во время фазы инициализации выполнения. Секции, исключенные на этом этапе, не будут отображаться в выводе команды EXPLAIN или EXPLAIN ANALYZE. Можно определить количество секций, которые были удалены на этом этапе, проанализировав свойство «Subplans Removed» в EXPLAIN выводе.

  • Во время фактического выполнения плана запроса исключение секций также может выполняться для удаления секций с использованием значений, которые становятся известны только во время фактического выполнения запроса. Сюда относятся значения из подзапросов и значения параметров времени выполнения, например, получаемые из параметризованных соединений вложенным циклом. Поскольку значения этих параметров могут меняться многократно в процессе выполнения запроса, исключение секций производится всякий раз при изменении одного из используемых параметров выполнения. Определение того, были ли исключены секции на данном этапе, требует тщательного изучения свойства loops в результирующем EXPLAIN ANALYZE выводе. Подпланы, соответствующие различным секциям, могут иметь разные значения для него в зависимости от того, сколько раз каждый из них был исключен в процессе выполнения. Некоторые могут быть помечены как (ни разу не выполнялось) если они исключались при каждом выполнении.

Исключение секций можно отключить с помощью enable_partition_pruning параметра.

2.2.12.5. Секционирование и #

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

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

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

Значение по умолчанию (и рекомендуемое) для constraint_exclusion не является ни on ни off, а представляет собой промежуточную настройку под названием секция, при которой этот метод применяется только к запросам, работающим с таблицами, секционированными через наследование. Настройка on заставляет планировщик проверять CHECK ограничения во всех запросах, даже в простых, где это вряд ли принесет пользу.

В отношении механизма исключения ограничений действуют следующие предостережения:

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

  • Исключение ограничений работает только в том случае, если условие запроса WHERE содержит константы (или внешние параметры). Например, сравнение с изменчивой (non-immutable) функцией, такой как CURRENT_TIMESTAMP не может быть оптимизировано, поскольку планировщик не может заранее определить, в какую дочернюю таблицу попадет значение функции во время выполнения.

  • Рекомендуется делать ограничения секционирования простыми, иначе планировщик не сможет доказать, что определенные дочерние таблицы не требуют обращения к ним. Используйте простые условия равенства для секционирования по списку или простые проверки диапазонов для секционирования по диапазонам, как показано в предыдущих примерах. Согласно общему правилу, ограничения секционирования должны содержать только сравнения столбцов секционирования с константами с использованием операторов, индексируемых методом B-tree, поскольку в ключе секционирования допускаются только столбцы, поддерживающие B-tree-индексы.

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

2.2.12.6. Рекомендации по декларативному секционированию #

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

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

Выбор оптимального количества секций, на которые должна быть разделена таблица, также является критически важным решением. Недостаточное количество секций может привести к тому, что индексы останутся слишком большими, а локальность данных — низкой, что в свою очередь повлечет за собой низкий коэффициент попадания в кэш. Однако разделение таблицы на слишком большое количество секций также может создать определенные проблемы. Избыточное количество секций может привести к увеличению времени планирования запросов и повышенному потреблению памяти как при планировании, так и при выполнении запросов, что более подробно описано ниже. При выборе стратегии секционирования таблицы также важно учитывать возможные изменения в будущем. Например, если выбрана схема «одна секция на одного клиента» и на данный момент имеется небольшое число крупных клиентов, следует учитывать последствия ситуации, когда через несколько лет количество клиентов станет большим, а сами они — мелкими. В этом случае может быть целесообразнее выбрать секционирование по HASH и выбрать разумное количество секций вместо того, чтобы пытаться использовать секционирование по LIST и надеяться, что число клиентов не увеличится сверх того предела, при котором секционирование данных остается практичным.

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

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

При рабочих нагрузках типа хранилищ данных может быть целесообразно использовать большее количество секций, чем при OLTP -типе нагрузки. Как правило, в хранилищах данных время планирования запросов имеет меньшее значение, так как большая часть времени обработки затрачивается на выполнение запроса. Для обоих этих типов нагрузки важно принять правильные решения на ранних этапах, так как повторное секционирование больших объемов данных может выполняться крайне медленно. Моделирование предполагаемой нагрузки часто оказывается полезным для оптимизации стратегии секционирования. Никогда не следует исходить из предположения, что большее количество секций всегда лучше, чем меньшее, или наоборот.

Наверх
свяжитесь
с нами
контакты
Для прямой связи с нами вы можете использовать контакты ниже, либо оставить заявку через форму обратной связи, и мы обязательно свяжемся с вами

*поля обязательные к заполнению