2012-04-17 3 views
1

Мой случай:Append бинарное содержимое в колонке BYTEA в базе данных PostgreSQL

У меня есть некоторые двоичные данные, которые были разбиты на множество частей. Специальная таблица FileParts содержит fileId, partNo и data.

Мне нужно собрать все детали в другой стол MyFilesStorage.

Теперь я буду выбирать между двумя реализациями:

DO $CODE$ 
declare 
    r record; 
begin 
    UPDATE public.MyFilesStorage SET mainFileData = E''::bytea WHERE id = 'e14a26c0-db4b-47e1-8b66-e091fb3ba199'::uuid; 

    for r in (select data 
      from public.FileParts 
      where fileId = '89cb8598-436b-49b3-bb1c-34534c6d068e'::uuid 
      order by partNo) loop 
    UPDATE public.MyFilesStorage SET mainFileData = mainFileData || r.data WHERE id = 'e14a26c0-db4b-47e1-8b66-e091fb3ba199'::uuid; 
    end loop; 

end; 
$CODE$ 

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

Другой вариант:

DO $CODE$ 
declare 
    r record; 
    p_result bytea; 
begin 
    p_result = E''::bytea; 

    for r in (select data 
      from public.FileParts 
      where fileId = '89cb8598-436b-49b3-bb1c-34534c6d068e'::uuid 
      order by partNo) loop 
    p_result = p_result || r.data; 
    end loop; 

    UPDATE public.MyFilesStorage SET mainFileData = p_result WHERE id = 'e14a26c0-db4b-47e1-8b66-e091fb3ba199'::uuid; 
end; 
$CODE$ 

Здесь я использую переменную TEMP. Во-вторых, гораздо быстрее, но я не знаю, что займет больше памяти? Сначала мне нужна память для загрузки всего файла в ОЗУ, и как насчет первого? Будет ли postgre загружать весь контент здесь: mainFileData = mainFileData || r.data?

Возможно, существует другой способ сделать это, потому что оба варианта: veeeeery slow? В oracle я использую DBMS_LOB.APPEND для этой операции.

+2

Вы можете использовать функцию больших объектов PostgreSQL, и в этом случае вы можете быстро добавить приложение, стремясь к концу и письму. http://www.postgresql.org/docs/9.1/interactive/lo-interfaces.html#LO-SEEK У этого есть некоторые ограничения по сравнению с bytea, поэтому внимательно изучите его.С другой стороны, то, что вы делаете с частями файла, очень похоже на то, что PostgreSQL делает внутренне через систему TOAST, за исключением того, что она также пытается выполнить сжатие по умолчанию. http://www.postgresql.org/docs/9.1/interactive/storage-toast.html Это полностью автоматическое и прозрачное. – kgrittn

+0

Большое спасибо. Это очень полезный совет! Я попробую интерфейс LO. – Yavanosta

ответ

3

Первая версия медленнее, потому что PostgreSQL не делает обновления на месте на уровне хранилища, он создает новую версию строки для каждого UPDATE. Итак, для строки, которая будет идти от 0Mb до 100MB с шагом 10Mb, то, что действительно будет записано на диск, не будет 10x10Mb, а скорее: 10Mb + 20Mb + 30Mb + ... + 90Mb + 100Mb = 550Mb. С другой стороны, потребление памяти будет оставаться низким с объемом памяти не более 10 Мбайт в секунду.

Вторая версия быстрее с записью всего 100 Мб, но ей необходимо выделить 100 Мб в памяти.

Строение таблицы FileParts с упорядоченными кусками, как правило, проще в управлении для большого содержимого, зачем его конвертировать в монолитную структуру?

+0

Это часть большой системы, и мы переходим на PostgreSQL. Поэтому я не могу изменить это поведение. Я должен сказать, что все другие базы данных (Oracle, Sql server) имеют специальные инструменты для управления большими двоичными объектами и хранения их вне основного файла данных таблицы. Но postgre нет, и очень жаль. – Yavanosta

+1

Я не согласен. Тип BYTEA Pg следует сравнивать с LONG RAW от Oracle. BLOB-тип Oracle и его пакет DBMS_LOB следует сравнивать/заменять большим объектом Pg (столбец Oid как «локатор») с его функциями lo_ *. –

+0

Да, вы правы, но нет возможности общаться с LargeObject throw SQL. Например, я могу получить доступ к lo_size только с помощью вызова из драйвера, и я не могу просто сделать 'SELECT lo_size (111)' Если я ошибаюсь, это будет очень хорошо. – Yavanosta

3

Ваш подход выглядит правильно, проверьте PostgreSQL manual here.

Вы можете также define your own aggregate, чтобы сделать работу для вас:

CREATE AGGREGATE bytea_add(bytea) 
(
    sfunc = byteacat, 
    stype = bytea, 
    initcond = E'' 
); 

и использование общей SQL, например:

UPDATE public.MyFIlesStorage SET mainFileData = (
    SELECT bytea_add(data) FROM public.FileParts 
    WHERE fileId = '89cb8598-436b-49b3-bb1c-34534c6d068e'::uuid 
    -- ORDER BY partNo -- ORDER BY partNo will not work 
) 
WHERE id = 'e14a26c0-db4b-47e1-8b66-e091fb3ba199'::uuid; 

EDIT:

UPDATE public.MyFilesStorage mfs SET mainFileData = fp.all_data 
    FROM (
    SELECT bytea_add(data) OVER (ORDER BY partNo) AS all_data, 
      rank() OVER (ORDER BY partNo DeSC) AS pos 
     FROM public.FileParts 
    WHERE fileId = '89cb8598-436b-49b3-bb1c-34534c6d068e'::uuid 
) AS fp 
WHERE fp.pos = 1 
    AND mfs.id = 'e14a26c0-db4b-47e1-8b66-e091fb3ba199'::uuid; 

Вы можете проверить вывод внутреннего SQL отдельно.

+0

Большое спасибо за ваш ответ. Как я могу гарантировать, что 'bytea_add' будет агрегировать части в правильном orger, если Order by не будет работать? – Yavanosta

+0

@ Yavanosta, из PostgreSQL 8.4 можно использовать 'bytea_add (data ORDER BY partNo)' для точного заказа. – vyegorov

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