2010-03-04 6 views
3

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

Это использование:

0. Таблица содержит около 10 столбцов длиной около 20 байтов каждая.

  1. ВСТАВКИ выполняются со скоростью сотни раз в секунду.

  2. Операторы SELECT выполняются на основе столбца 'a' (где a = 'xxxx') несколько раз в час.

  3. Операторы DELETE выполняются на основе столбца DATE. (удалять, если дата старше 1 года) обычно один раз в день.

Основным требованием является, чтобы ускорить INSERT и ВЫБРАТЬ заявления, и быть в состоянии сохранить исторические данные 1 год назад без блокировки всей таблицы вниз во время удаления.

Я бы предположил, что у меня должно быть два индекса: один для столбца 'a', а другой для поля даты. или можно оптимизировать оба?

Будет ли необходимый компромисс между скоростью по выбору и скоростью удаления?

Разделение единственного решения? Каковы хорошие стратегии для разбиения такой таблицы?

Я использую базу данных PostgreSQL 8.4.

ответ

4

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

Разметка может помочь вам избежать проблемы выбора между быстрыми операциями INSERT и Fast DELETE. Вы всегда можете разбивать таблицу на год/месяц и просто отбрасывать разделы, которые вам больше не нужны. Отбрасывание разделов происходит очень быстро, а вставка в небольшие разделы также очень быстро.

Из инструкции:

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

  • производительность запроса может быть значительно улучшена для некоторых видов запросов.
  • Обновление производительности также может быть улучшено, так как каждый кусок таблицы имеет индексы, меньшие, чем индекс , по всему набору данных. Когда индекс больше не подходит в памяти , операции чтения и записи по индексу принимают более дисков.
  • Массовое удаление может быть выполнено простым удалением одного из разделов, если это требование составляет , запланированное в конструкцию разделения. DROP TABLE намного быстрее, чем навалом УДАЛИТЬ, не говоря уже о следующих VACUUM накладных расходов.
  • Редко используемые данные могут быть перенесены на более дешевое и медленное хранение media.

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

В настоящее время PostgreSQL поддерживает разделение через наследование таблицы. Каждый раздел должен быть создан как дочерняя таблица одной родительской таблицы. Родительская таблица сама по себе пустая; он существует только для представления полного набора данных . Вы должны быть , знакомы с наследованием (см. Раздел 5.8) перед попыткой реализовать разбиение на разделы.

+0

но разбиение таблицы Года/Месяц оптимизирует для удаления вместо вставки/выбора, не так ли? – Pat

+0

@Pat: он должен оптимизировать для обоих. Наличие небольших разделов улучшает вставки, и если для большинства запросов нужны только «текущие» данные, они также будут оптимизированы. Что касается выборок, которые действуют против всей таблицы (всех разделов), они уже будут обладать высокой стоимостью, что физическое разбиение должно быть незначительным. – LBushkin

+0

Итак, если я правильно понимаю, разбиение, которое вы предлагаете, будет оптимизировать удаление и вставку, оно не сделает выбор хуже, потому что это уже плохо. Но требование - ускорить «вставляет и выбирает» вместо скорости «удаляет и вставляет». Или я что-то пропустил в вашем ответе? – Pat

0

Одним из решений является разделение на основе даты вставки.

А именно, ваше приложение (или DAO) решает, какую таблицу вставить на основе некоторой логики, которая объединяет текущую дату (или, точнее, время с момента начала последнего раздела разделов) и/или размер «последнего» раздела. Или выгрузите такую ​​логику в ежедневный скрипт, и скрипт заполнит некоторые «это раздел для использования» для использования DAO.

Это немедленно устраняет необходимость удаления «старых» строк (просто отбросьте старый раздел); он также гарантирует, что ваши вставки периодически начинают заполнять небольшой стол, что, среди прочего, ускоряет «среднюю» скорость INSERT/SELECT (сценарии наихудшего сценария по-прежнему так же медленны, конечно)

+0

Это замечательно для удаления, но в соответствии с вопросом «Ключевым требованием является ускорение инструкций INSERT и SELECT», не будет ли в этом случае разбиение на столбец a? – Pat

0

Если вы должны были сломать эта таблица в правильное разбиение на разделы, вы можете использовать truncate вместо delete, что уменьшит затраты на обслуживание, поскольку это не создает мертвое пространство.

+0

Это отлично подходит для удаления, но в соответствии с вопросом «Главное требование - ускорить инструкции INSERT и SELECT», не будет ли в этом случае разбиение на столбец? – Pat

+0

Очистка процесса удаления также поможет SELECT (не INSERT), потому что это уменьшит случайные чтения с аппаратного обеспечения из-за раздувания. Да, правильное разбиение, безусловно, является общим решением. –

0

Я не эксперт, но кажется, что разбиение на столбец «a» ускорит выбор, но разбиение на дату (как показывают все другие ответы) ускорит удаление (отбросит таблицу), но будет быть бесполезным для вашего выбора.

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

Любые экспертные рекомендации по снижению веса? Возможно ли использовать разделение на обоих полях?

3

Разметка ваш ответ, как и другие заявили, но:

Я бы разметить на некотором hash(a). Если a является целым числом, тогда a%256 будет хорошим.Если это текст, то что-то вроде substring(md5(a) for 2).

Это ускорит вставку и выбор.

Для удалений я делаю их запущенными чаще, но меньше, а также разбитыми. Я бы запускать их каждый час (в XX: 30), и как это:

delete from table_name 
where date<(current_date - interval '1 year') 
and 
    hash(a) 
    = 
    (extract(doy from current_timestamp) * 24 
    + extract(hour from current_timestamp))::int % 256; 

EDIT: Я только что проверил это:

create function hash(a text) returns text as $$ select substring(md5($1) for 1) $$ language sql immutable strict; 
CREATE TABLE tablename (id text, mdate date); 
CREATE TABLE tablename_partition_0 (CHECK (hash(id) = '0')) INHERITS (tablename); 
CREATE TABLE tablename_partition_1 (CHECK (hash(id) = '1')) INHERITS (tablename); 
CREATE TABLE tablename_partition_2 (CHECK (hash(id) = '2')) INHERITS (tablename); 
CREATE TABLE tablename_partition_3 (CHECK (hash(id) = '3')) INHERITS (tablename); 
CREATE TABLE tablename_partition_4 (CHECK (hash(id) = '4')) INHERITS (tablename); 
CREATE TABLE tablename_partition_5 (CHECK (hash(id) = '5')) INHERITS (tablename); 
CREATE TABLE tablename_partition_6 (CHECK (hash(id) = '6')) INHERITS (tablename); 
CREATE TABLE tablename_partition_7 (CHECK (hash(id) = '7')) INHERITS (tablename); 
CREATE TABLE tablename_partition_8 (CHECK (hash(id) = '8')) INHERITS (tablename); 
CREATE TABLE tablename_partition_9 (CHECK (hash(id) = '9')) INHERITS (tablename); 
CREATE TABLE tablename_partition_a (CHECK (hash(id) = 'a')) INHERITS (tablename); 
CREATE TABLE tablename_partition_b (CHECK (hash(id) = 'b')) INHERITS (tablename); 
CREATE TABLE tablename_partition_c (CHECK (hash(id) = 'c')) INHERITS (tablename); 
CREATE TABLE tablename_partition_d (CHECK (hash(id) = 'd')) INHERITS (tablename); 
CREATE TABLE tablename_partition_e (CHECK (hash(id) = 'e')) INHERITS (tablename); 
CREATE TABLE tablename_partition_f (CHECK (hash(id) = 'f')) INHERITS (tablename); 
analyze; 
explain select * from tablename where id='bar' and hash(id)=hash('bar'); 
           QUERY PLAN           
--------------------------------------------------------------------------------------------- 
Result (cost=0.00..69.20 rows=2 width=36) 
    -> Append (cost=0.00..69.20 rows=2 width=36) 
     -> Seq Scan on tablename (cost=0.00..34.60 rows=1 width=36) 
       Filter: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text)) 
     -> Seq Scan on tablename_partition_3 tablename (cost=0.00..34.60 rows=1 width=36) 
       Filter: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text)) 
(6 rows)

Вы» d необходимо добавить hash(id)=hash('searched_value') к вашим запросам или Postgres будет искать все таблицы.


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

create rule tablename_rule_0 as 
    on insert to tablename where hash(NEW.id)='0' 
    do instead insert into tablename_partition_0 values (NEW.*); 
create rule tablename_rule_1 as 
    on insert to tablename where hash(NEW.id)='1' 
    do instead insert into tablename_partition_1 values (NEW.*); 
-- and so on 
insert into tablename (id) values ('a'); 
select * from tablename_partition_0; 
id | mdate 
----+------- 
a | 
(1 row) 
+0

Поддерживает ли PostgreSQL хэш-разбиение? – Pat

+0

Несколько. Вам нужно добавить явные хэш (id) = hash (...) к вашим запросам. – Tometzky