Эффективно обновлять ОЧЕНЬ БОЛЬШУЮ таблицу базы данных PostgreSQL - PullRequest
14 голосов
/ 22 сентября 2008

У меня очень большая таблица базы данных в PostgresQL и столбец типа «скопировано». Каждая новая строка начинается не скопированной и позже будет реплицирована на другую вещь с помощью фоновой программы. В этой таблице есть частичный индекс "btree (ID) WHERE replicated = 0". Фоновая программа выбирает не более 2000 записей (LIMIT 2000), обрабатывает их и затем фиксирует изменения в одной транзакции, используя 2000 подготовленных sql-команд.

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

Обновленный набор таблиц обновлений = 0;

невозможно:

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

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

Несколько других проблем: Делать

update set replicated=0 where id >10000 and id<20000

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

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

Странно, но

UPDATE table 
  SET replicated=0 
WHERE ID in (SELECT id from table WHERE replicated= LIMIT 10000)

тоже медленно, хотя это должно быть хорошо: просмотрите таблицу в DISK-порядке ...

(Обратите внимание, что в этом случае был также индекс, который охватывал это)

(Обновление LIMIT, такое как Mysql, недоступно для PostgresQL)

Кстати: реальная проблема более сложна, и мы говорим о встроенной системе, которая уже развернута, поэтому удаленные изменения схемы трудны, но возможны Это, к сожалению, PostgresQL 7.4.

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

Сама база данных содержит только 5 таблиц, одна очень большая. Но это неплохой дизайн, потому что эти встроенные блоки работают только с одним видом сущности, это не ERP-система или что-то в этом роде!

Есть идеи?

Ответы [ 6 ]

9 голосов
/ 22 сентября 2008

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

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

3 голосов
/ 22 сентября 2008

Если вы пытаетесь сбросить всю таблицу, а не только несколько строк, обычно быстрее (на очень больших наборах данных - не на обычных таблицах) просто CREATE TABLE bar AS SELECT everything, but, copied, 0 FROM foo, а затем поменяйте местами таблицы и удалите старую один. Очевидно, вам нужно убедиться, что в исходную таблицу ничего не вставляется, пока вы это делаете. Вам также потребуется пересоздать этот индекс.

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

lock ;
create a new table, bar;
swap tables so that all writes go to bar;
unlock;
create table baz as select from foo;
drop foo;
create the index on baz;
lock;
insert into baz from bar;
swap tables;
unlock;
drop bar;

(пусть записи происходят, пока вы делаете копию, и вставляете их постфактум).

2 голосов
/ 22 сентября 2008

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

drop index replication_flag;
update big_table set replicated=0;
create index replication_flag on big_table btree(ID) WHERE replicated=0;
vacuum full analyze big_table;
1 голос
/ 03 марта 2010

Я думаю, что лучше поменять ваш postgres на версию 8.X. Вероятно, причиной является низкая версия Postgres. Также попробуйте этот запрос ниже. Я надеюсь, что это может помочь.

UPDATE table1 SET name = table2.value
FROM table2 
WHERE table1.id = table2.id;
1 голос
/ 22 сентября 2008

Это псевдокод. Вам понадобится временный файл 400 МБ (для целых) или 800 МБ (для больших) (вы можете сжать его с помощью zlib, если это проблема). Для этого потребуется около 100 сканов таблицы на предмет вакуума. Но он не будет раздувать таблицу более чем на 1% (не более 1000000 мертвых строк в любое время). Вы также можете обменять меньшее количество сканов на большее количество таблиц.

// write all ids to temporary file in disk order                
// no where clause will ensure disk order
$file = tmpfile();
for $id, $replicated in query("select id, replicated from table") {
        if ( $replicated<>0 ) {
                write($file,&$id,sizeof($id));
        }
}

// prepare an update query
query("prepare set_replicated_0(bigint) as
        update table set replicated=0 where id=?");

// reread this file, launch prepared query and every 1000000 updates commit
// and vacuum a table
rewind($file);
$counter = 0;
query("start transaction");
while read($file,&$id,sizeof($id)) {
        query("execute set_replicated_0($id)");
        $counter++;
        if ( $counter % 1000000 == 0 ) {
                query("commit");
                query("vacuum table");
                query("start transaction");
        }
}
query("commit");
query("vacuum table");
close($file);
0 голосов
/ 14 августа 2010

Я думаю, что вам нужно сделать, это а. скопировать значение PK 2000 записей во временную таблицу с тем же стандартным пределом и т. д. б. выберите те же 2000 записей и выполните необходимые операции с курсором, как он есть. с. В случае успеха запустите один запрос на обновление записей в временной таблице. Очистите временную таблицу и снова выполните шаг a. д. В случае неудачи очистите временную таблицу, не запуская запрос на обновление. Просто, эффективно и надежно. С Уважением, KT

...