Oracle 10: использование HEXTORAW для заполнения данных больших двоичных объектов

У нас есть таблица в Oracle со столбцом BLOB, который необходимо заполнить небольшим количеством произвольных байтовых данных — мы никогда не будем вводить более 4000 байтов данных.

Я работаю с существующей инфраструктурой на основе C++ OCI, что чрезвычайно затрудняет использование переменных связывания в определенных контекстах, поэтому мне нужно заполнить этот столбец BLOB, используя только простой запрос. (Мы работаем над его модернизацией, но сегодня это не вариант.)

Нам повезло с таким запросом:

UPDATE MyTable
   SET blobData = HEXTORAW('0EC1D7FA6B411DA5814...lots of hex data...0EC1D7FA6B411DA5814')
 WHERE ID = 123;

Сначала это работало отлично. Однако недавно мы столкнулись со случаем, когда нам нужно ввести более 2000 байт данных. В этот момент мы столкнулись с ошибкой Oracle, ORA-01704: string literal too long, потому что строка, передаваемая в HEXTORAW, превышала 4000 символов. Я попытался разделить строку, а затем соединить ее с ||, но это не помогло избежать ошибки.

Итак, мне нужен способ обновить этот столбец и заполнить его данными объемом более 2000 байт с помощью простого запроса. Является ли это возможным?

(Я знаю, что если бы в моем распоряжении были переменные связывания, это было бы тривиально — и на самом деле другие приложения, которые взаимодействуют с этой таблицей, используют именно этот метод — но, к сожалению, я не в состоянии реорганизовать внутренности БД здесь. Просто нужно для ввода данных в таблицу.)

ИЗМЕНИТЬ:

Одним многообещающим подходом, который не сработал, было объединение RAW:

UTL_RAW.CONCAT(HEXTORAW('...'), HEXTORAW('...'), HEXTORAW('...'))

Это позволяет обойти ограничение длины строки, но похоже, что Oracle также имеет соответствующее внутреннее ограничение в 2000 байтов на длину RAW. Поэтому я не могу заполнить большой двоичный объект RAW. Возможно, есть функция, которая объединяет несколько RAW в BLOB.


person StilesCrisis    schedule 08.08.2013    source источник
comment
Если вы никогда не собираетесь хранить более 4000 байт данных, вам действительно следует использовать RAW(4000) (или меньше), а не BLOB.   -  person Justin Cave    schedule 08.08.2013
comment
Верно подмечено. Это не критически важная для производительности таблица, но об этом стоит помнить.   -  person StilesCrisis    schedule 08.08.2013


Ответы (5)


Чтобы обновить BLOB длиннее 16383 байт, можно использовать что-то вроде этого (каждая строка имеет четное количество шестнадцатеричных цифр до 32766):

DECLARE
  buf BLOB; 
BEGIN
  dbms_lob.createtemporary(buf, FALSE);
  dbms_lob.append(buf, HEXTORAW('0EC1D7FA6B411DA58149'));
  --...lots of hex data...
  dbms_lob.append(buf, HEXTORAW('0EC1D7FA6B411DA58149'));
  UPDATE MyTable
     SET blobData = buf
   WHERE ID = 123;
END;

теперь пределом является только размер оператора, который может быть наложен операционной средой (например, SQLPlus, Pro*C, VB, JDBC...). Для очень больших операторов PL/SQL также может завершиться ошибкой "out of Diana nodes".

person mik    schedule 19.01.2016
comment
Что ж, в какой-то момент, если ваш PL/SQL станет слишком длинным, он сообщит вам об отсутствии узлов Дианы и загадочным образом выйдет из строя. Но этого не произойдет, пока вы не введете тысячи и тысячи строк кода. - person StilesCrisis; 19.01.2016
comment
Я пытался использовать это. Если вам не нужно разбивать его на куски, он прекрасно работает. Однако, если вам нужно разбить его, для каждого оператора добавления, который вы делаете, он помещает дополнительный начальный 0 в каждую шестнадцатеричную строку, когда загружает ее в таблицу. Это видно, когда вы пытаетесь извлечь эти байты обратно, если вы преобразуете эти байты в шестнадцатеричный формат и сравните его с тем, что вы вставили. В результате я получил несколько поврежденных файлов/изображений, которые не отображались, потому что объединенная строка шестнадцатеричного были эти лишние нули в нем. - person vapcguy; 10.12.2016
comment
Так что мне понадобился целый день, чтобы понять это, и я еще не пробовал это с чем-то большим, чем 32 КБ (я использовал размер фрагмента 9999, чтобы смоделировать ограничение в 10 000, и использовал файл чуть больше 10 КБ), но то, как я избежал этих ведущих нулей, заключалось в том, чтобы добавить еще одну переменную, cBuf CLOB. Я добавил к этому весь свой шестнадцатеричный код в виде строки кусками, а не к переменной BLOB buf. Затем, в конце, я добавил к buf, используя dbms_lob.append(buf, hextoraw(cBuf)); - person vapcguy; 10.12.2016
comment
@vapcguy, может быть, это потому, что вы используете шестнадцатеричные строки неравной длины? - person mik; 13.12.2016
comment
@mik, большая вероятность. Возможно, я проверю и вернусь к вам. Но я бы дал это как предостережение, если так. - person vapcguy; 13.12.2016
comment
@mik, это сводило меня с ума, думая об этом как о причине, поэтому я проверил и убедился, что он не добавляет дополнительный 0 шестнадцатеричный байт, если фрагмент имеет четный размер! Ваш код отлично работает при использовании четного числа. Отличный вычет. Просто нужно убедиться, что все знают эту маленькую (не) мелочь! - person vapcguy; 13.12.2016
comment
@vapcguy, спасибо, что указали на это, я исправил свой пример (в котором использовалось нечетное количество цифр) и ограничения. - person mik; 15.12.2016

По-видимому, вы можете выйти за эти пределы, если используете PL/SQL. Это не сработает, если вы сделаете HEXTORAW непосредственно в операторе UPDATE — это нужно сделать в отдельном операторе, например:

DECLARE
  buf RAW(4000); 
BEGIN
  buf := HEXTORAW('C2B97041074...lots of hex...0CC00CD00');
  UPDATE MyTable
     SET blobData = buf
   WHERE ID = 462;
END;

На всю жизнь я никогда не пойму некоторые ограничения Oracle. Как будто всё — это отдельный частный случай.

person StilesCrisis    schedule 08.08.2013
comment
Однако приведенное выше решение имеет ограничение в 32767 символов в строковом литерале PL/SQL и еще одно ограничение в 32767 байтов в типе данных PL/SQL RAW (что происходит позже, поскольку два символа представляют один байт RAW). - person mik; 19.01.2016

Это основано на ответе mik, но я нашел в нем дыру, где добавление более одной строки шестнадцатеричного кода вводит дополнительный шестнадцатеричный символ 0 в начале каждой строки, когда вы используете HEXTORAW в каждой строке добавления. Когда вы вытаскиваете этот шестнадцатеричный код обратно из базы данных и сравниваете его с тем, что, по вашему мнению, вы вставляли, вы видите это. Если шестнадцатеричный код был изображением, и вы привязываете эти байты изображения к Image.Source, ноль игнорируется, если к нему добавлена ​​только одна строка, но если у вас есть несколько строк, он вводит этот дополнительный байт для каждого фрагмента и повреждает ваши данные и вы не можете отобразить изображение. Я предполагаю, что то же самое происходит с обычными файлами и другими данными, которые вы хотите загрузить.

Вместо этого я добавил все свои шестнадцатеричные значения в CLOB, который сохраняет их как строку шестнадцатеричных значений, а также имеет тот же предел в 4 ГБ, что и поле BLOB. Таким образом, только эта неповрежденная строка записывается в BLOB как RAW, когда шестнадцатеричная строка больше, чем ограничение в 32767 символов/байт:

DECLARE
  buf BLOB; 
  cBuf CLOB;
BEGIN
  dbms_lob.createtemporary(buf, FALSE);
  dbms_lob.createtemporary(cBuf, FALSE);
  dbms_lob.append(cBuf, '0EC1D7FA6B411DA5814');
  --...lots of hex data...
  dbms_lob.append(cBuf, '0EC1D7FA6B411DA5814');
  -- now we append the CLOB of hex to the BLOB as RAW
  dbms_lob.append(buf, HEXTORAW(cBuf));
  UPDATE MyTable
     SET blobData = buf
     WHERE ID = 123;
END;

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

В качестве более полного ответа о том, как создать этот SQL программно, я подумал, что должен показать это, поскольку я сделал это со своим приложением. Код в моем приложении C# помещал байты файла в шестнадцатеричный формат, затем у меня была строковая переменная с приведенным выше SQL, которую я записывал в файл, а позже служба использовала ее для обновления Oracle при восстановлении соединения. Итак, вот как я разобрался, как я вставил свой шестнадцатеричный код в эту строку SQL и файл (а позже и в Oracle):

// This is all staged so someone can see how you might go from file
// to bytes to hex
string filePath = txtFilePath.Text; // example of getting file path after
    // OpenFileDialog places ofd.FileName in a textbox called txtFilePath
byte[] byteArray = File.ReadAllBytes(filePath);
string hexString = getHexFromBytes(byteArray); // Google: bytes to hex

// Here is the meat...
if (hexString.Length > 0)
{
    string sqlForOracle = "DECLARE buf BLOB; " + 
        "cBuf CLOB; " +
        "BEGIN " + 
            "dbms_lob.createtemporary(buf, FALSE); " + 
            "dbms_lob.createtemporary(cBuf, FALSE); "; + 
            "dbms_lob.open(buf, dbms_lob.lob_readwrite); ";

    int chunkSize = 32766;
    if (hexString.Length > chunkSize)
    {
        sqlForOracle += "dbms_lob.open(cBuf, dbms_lob.lob_readwrite); ";

        int startIdx = 0;
        decimal hexChunks = decimal.Divide(hexString.Length / chunkSize);
        for (int i = 0; i < hexChunks; i++)
        {
            int remainingHex = hexString.Length - (i * chunkSize);
            if (remainingHex > chunkSize)
                sqlForOracle += "dbms_lob.append(cBuf, '" + hexString.Substring(startIdx, chunkSize + "'); ";
            else
                sqlForOracle += "dbms_lob.append(cBuf, '" + hexString.Substring(startIdx, remainingHex) + "'); ";

            startIdx = startIdx + chunkSize;
        }

        sqlForOracle += "dbms_lob.close(cBuf); ";

        // Now we append the CLOB to the BLOB
        sqlForOracle += "dbms_lob.append(buf, HEXTORAW(cBuf)); ";
    }
    else  // write it straight to BLOB as we are below our chunk limit
        sqlForOracle += "dbms_lob.append(buf, HEXTORAW('" + hexString + "')); ";

    sqlForOracle += "dbms_lob.close(buf); ";
    sqlForOracle += "UPDATE MyTable SET blobDate = buf WHERE ID = 123; END;";
}

sqlForOracle позже записывается в файл с использованием FileStream и StreamWriter, и служба видит, существует ли файл, считывает его и обновляет с его помощью Oracle.

ОБНОВЛЕНИЯ

Ответ Мика на самом деле хорош, как есть, если вы используете четное число со своими фрагментами, поэтому мой на самом деле излишне вводит дополнительный шаг, если вам не нужно использовать фрагменты с нечетными номерами. Файл большего размера (однако он должен был бы конкурировать с вашей оперативной памятью) поэтому излишне повлиял бы на производительность, поскольку он также дважды записывается в память (CLOB, затем BLOB) перед преобразованием, так что будьте внимательны, но я хотел показать в С#, как фрагменты будут разбиты и как SQL будет фактически написан программно. Если вы хотите использовать только buf, просто замените все переменные cBuf на buf, за исключением того, что вам нужен только один оператор dbms_lob.createtemporary() и, очевидно, только один набор тегов .open() и .close().

Итак, про эти теги я тоже читал форум AskTom на Oracle.com, где говорится, что добавление dbms_lob.open() и .close() к вашему лобовому объекту является необязательным, но более полезным для производительности при работе с числом добавлений > 2000 (или 2000 * 32766 = 65,532 МБ), где для завершения требуется почти вдвое больше времени (178,19%), и от этого становится только хуже: конечно, это зависит от размеров обрабатываемых файлов, действительно ли это полезно для вас или нет. Я добавил их выше.

person vapcguy    schedule 10.12.2016
comment
Насколько я знаю, вы не можете просто преобразовать CLOB в BLOB с помощью HEXTORAW. Он будет работать только для строк короче 32768 цифр (сначала неявным преобразованием CLOB в VARCHAR2). - person mik; 15.12.2016
comment
Таким образом, вам придется читать строки, как это было сделано с CLOB, только для того, чтобы каким-то образом снова прочитать их кусками в BLOB? Ну что ж. Рад, что вы сообщили мне, и посмотреть на размер куска и сделать его четным числом. У меня он работает без CLOB прямо сейчас, так что я думаю, что это более безопасная ставка. - person vapcguy; 17.12.2016

Другой альтернативой, начиная с Oracle 12c, является использование ALTER SYSTEM SET max_string_size=extended SCOPE=SPFILE;, как описано в https://docs.oracle.com/database/121/REFRN/GUID-D424D23B-0933-425F-BC69-9C0E6724693C.htm#REFRN10321.

Это расширит максимальный размер VARCHAR2 и RAW с 2000 до 32767.

Обратите внимание, что для этого требуются разрешения sys, перезапуск БД и некоторые ошибки: -for-user-who-i">Oracle 12c расширен для поддержки varchar2 › 4000 байт не работает для пользователя, который не является sysdba.

person Vadzim    schedule 02.06.2020

Вот мое решение для объединения нескольких RAW в один BLOB с использованием типа вспомогательной таблицы и хранимой функции:

create or replace type raws as table of raw(2000);

create or replace function concat_raws(parts in raws) return blob
is
    temp blob;
begin
    if parts is null or parts.count = 0 then
       return null;
    end if;
    dbms_lob.createtemporary(temp, false, dbms_lob.CALL);
    for i in parts.first .. parts.last
    loop
        dbms_lob.append(temp, to_blob(parts(i)));
    end loop;
    return temp;
end;

-- usage example:
select concat_raws(raws(hextoraw('CAFE'), hextoraw('BABE'))) from dual;

Этот подход также удобен для автоматической генерации sql, как показано в моем другом ответе на Встроенные типы данных BLOB/BINARY в SQL/JDBC.

См. также объединение нескольких BLOB-объектов в разделе Как объединить поля BLOB (Oracle)?< /а>

person Vadzim    schedule 02.06.2020