Оптимальный метод оптимизации таблицы базы данных - PullRequest
3 голосов
/ 04 марта 2010

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

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

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

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

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

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

Ключевым требованием является ускорение операторов INSERT и SELECT и возможность сохранять данные истории за 1 год без блокировки всей таблицы при удалении.

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

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

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

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

Ответы [ 5 ]

4 голосов
/ 04 марта 2010

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

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

Из руководства:

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

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

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

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

3 голосов
/ 05 марта 2010

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

Я бы разделил на некоторые 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;

РЕДАКТИРОВАТЬ: Я только что проверил это:

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');
<i>                                         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)</i>

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


РЕДАКТИРОВАТЬ: Вы также можете использовать систему правил для автоматических вставок для исправления таблиц:

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 голосов
/ 05 марта 2010

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

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

Любая экспертная помощь, чтобы взвесить вопрос? Можно ли / полезно разделить на оба поля?

0 голосов
/ 04 марта 2010

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

0 голосов
/ 04 марта 2010

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

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

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

...