2008-09-21 2 views
13

У меня очень большая таблица базы данных в PostgresQL и столбец типа «скопирован». Каждая новая строка начинается без загрузки и позже будет воспроизведена на другую. В этой таблице есть частичный индекс «btree (ID), WHERE replicated = 0». Фоновая программа делает выбор для не более 2000 записей (LIMIT 2000), работает над ними, а затем фиксирует изменения в одной транзакции с использованием 2000 подготовленных SQL-команд.Обновление ОЧЕНЬ БОЛЬШОЙ таблицы базы данных PostgreSQL эффективно

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

Набор таблиц обновления, реплицированный = 0;

не представляется возможным:

  • Это занимает очень много времени
  • Он дублирует размер Tabel из-за 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-система или что-то в этом роде!

Любые идеи?

ответ

8

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

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

+0

Это очень хорошая идея, хотя, к сожалению, для нее требуется изменение схемы (процедура обновления по длине). Что мне действительно нравится в этом подходе, так это то, что текущий частичный индекс внутренне очень похож на эту идею! Только более гибкий и управляемый. – Christian 2008-09-21 21:52:42

3

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

Edit: Простое улучшение во избежание блокировки таблицы во время копирования 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; 

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

1

Это псевдокод. Вам понадобится 400MB (для ints) или 800MB (для bigints) временный файл (вы можете сжать его с помощью 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); 
2

В то время как вы не можете, вероятно, решить проблему использования пространства (оно временно, только до тех пор, вакуум), вы, вероятно, может действительно ускорить процесс с точки зрения времени часов. Тот факт, что 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

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

UPDATE table1 SET name = table2.value 
FROM table2 
WHERE table1.id = table2.id; 
0

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

Смежные вопросы