вторник, 30 мая 2006 г.

Oracle: работа с LOB-ами через OCI

В СУБД Oracle есть встроенный тип данных под названием LOB (Large Object), что означает набор неструктурированной информации, текстовой (CLOB) или бинарной (BLOB) произвольного объёма. Речь пойдёт о том, как обеспечить приемлемое быстродействие операций чтения/записи при работе через интерфейс OCI.

Создание таблицы с колонкой из LOB

На этом этапе следует определить, потребуется ли встроенный кэш Oracle для чтения данных из LOB. Если собираетесь читать одни и теже данные несколько раз, то кэш имеет смысл использовать; скорость чтения некэшированного LOB-а очень мала. Ключевые слова — CACHE/NOCACHE/CACHE READS. CACHE влияет и на чтение, и на запись, а CACHE READS — только на чтение.

Есть ещё ряд параметров, в меньшеё степени влияющих на быстродействие: LOGGING/NOLOGGING, PCTVERSION, ENABLE/DISABLE STORAGE IN ROW, о которых подробно рассказано в документации. Размер кэша определяется параметром базы db_cache_size.

Запись (OCILobWrite())

Для многократных разбросанных записей следует включить CACHE, NOLOGGING и PCTVERSION 0; видимо, это всё, что можно сделать.
Для многократной записи маленькими кусочками в конец LOB-а можно и нужно использовать буферизацию. С ней процесс становится быстрее, но и обработка ошибок гораздо «интереснее». Режим буферизации LOB-а включается вызовом OCILobEnableBuffering(). Интересно, что этого вызова нет ни в PL/SQL, ни в JDBC, ни в OCCI-обёртке. Есть только в C и (?) в Visual Basic.

О недостатках режима буферизации: во-первых, запрещён ряд стандартных операций, например, нельзя узнать размер LOB-а или сделать «добавление в конец». Приходится сохранять размер до буферизации, после чего заниматься ручным подсчётом.

Во-вторых, OCILobWrite() может возвращать ошибки:

  1. ORA-22280: no more buffers available for operation
  2. Рецепт следующий: получив эту ошибку, надо выключить буферизацию (OCILobDisableBuffering()) и повторить запись. После успеха включить буферизацию снова.
  3. ORA-22282: non-contiguous append to a buffering enabled LOB not allowed
  4. Это редкая ошибка, т.е. появляется даже когда не должна, но в этом случае редко. Она перестала появляться совсем, только когда я усвоил простое правило: надо записывать в LOB только чётное количество байт. Похоже, что на нечётном Oracle просто сбивается со счёта. Это касается и 9-й, и 10-й версий.

Что же касается режима без буферизации, то в Oracle 9 продедура записи явно работает по алгоритму маляра Шлемиэля: чем длинее LOB, тем больше времени тратится на позиционирование. В Oracle 10, где это исправили, скорость записи от размера LOB-а не зависит.

И ещё один момент: OCILobWrite не обязан записывать сразу все данные, которые ему дали на вход. Количество записанных байт он возвращает, и надо дозаписывать снова, пока не кончится.

Чтение (OCILobRead())

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

То же самое, что и с записью: не стоит рассчитывать на то, что OCILobRead() прочтёт все данные за один раз.