Транзакции являются ключевыми для операций базы данных SQL.Они, безусловно, являются фундаментальными в Oracle.Не существует способа постоянной записи в таблицы Oracle без выдачи коммита, и вот!есть сделка.
Oracle позволяет нам указывать таблицы как NOLOGGING, которые не генерируют журнал повторов.Это предназначено только для массовой загрузки (с подсказкой INSERT /*+ APPEND */
), с рекомендацией переключиться на LOGGING и вернуться назад как можно скорее.Потому что данные, которые не зарегистрированы, не подлежат восстановлению.И если вы не хотите восстанавливать его, зачем вообще писать его?
Альтернативный подход - это группировать записи в память, а затем использовать массовые вставки для их записи.Это довольно быстро.
Вот простая таблица журнала и пакет для проверки концепции:
create table log_table
(ts timestamp(6)
, short_text varchar(128)
, long_text varchar2(4000)
)
/
create or replace package fast_log is
procedure init;
procedure flush;
procedure write (p_short log_table.short_text%type
, p_long log_table.long_text%type);
end fast_log;
/
Записи журнала хранятся в коллекции PL / SQL, которая являетсяструктура памяти с областью действия сеанса .Процедура INIT () инициализирует буфер.Процедура FLUSH () записывает содержимое буфера в LOG_TABLE.Процедура WRITE () вставляет запись в буфер, и, если в буфере содержится необходимое количество записей, вызывается FLUSH ().
create or replace package body fast_log is
type log_buffer is table of log_table%rowtype;
session_log log_buffer;
write_limit constant pls_integer := 1000;
write_count pls_integer;
procedure init
is
begin
session_log := log_buffer();
session_log.extend(write_limit);
write_count := 0;
end init;
procedure flush
is
begin
dbms_output.put_line('FLUSH::'||to_char(systimestamp,'HH24:MI:SS.FF6')||'::'||to_char(write_count));
forall i in 1..write_count
insert into log_table
values session_log(i);
init;
end flush;
procedure write (p_short log_table.short_text%type
, p_long log_table.long_text%type)
is
pragma autonomous_transaction;
begin
write_count := write_count+1;
session_log(write_count).ts := systimestamp;
session_log(write_count).short_text := p_short;
session_log(write_count).long_text := p_long;
if write_count = write_limit
then
flush;
end if;
commit;
end write;
begin
init;
end fast_log;
/
В таблице записи в журнал используется прагма AUTONOMOUS_TRANSACTION, поэтому происходит COMMITбез влияния на окружающую транзакцию, которая вызвала сброс.
Вызов DBMS_OUTPUT.PUT_LINE () позволяет легко отслеживать прогресс.Итак, давайте посмотрим, как быстро он идет ...
SQL> begin
2 fast_log.flush;
3 for r in 1..3456 loop
4 fast_log.write('SOME TEXT', 'blah blah blah '||to_char(r));
5 end loop;
6 fast_log.flush;
7 end;
8 /
FLUSH::12:32:22.640000::0
FLUSH::12:32:22.671000::1000
FLUSH::12:32:22.718000::1000
FLUSH::12:32:22.749000::1000
FLUSH::12:32:22.781000::456
PL/SQL procedure successfully completed.
SQL>
Хммм, 3456 записей за 0,12 секунды, это не так уж плохо.Основная проблема с этим подходом заключается в необходимости очищать буфер для округления свободных записей;это боль, например, в конце сеанса.Если что-то приводит к сбою сервера, непроверенные записи теряются.Другая проблема, связанная с работой в памяти, заключается в том, что она потребляет память (durrrr), поэтому мы не можем сделать кэш слишком большим.
Для сравнения я добавил в пакет процедуру, которая вставляет одну записьнепосредственно в LOG_TABLE каждый раз, когда он вызывается, снова используя автономные транзакции:
procedure write_each (p_short log_table.short_text%type
, p_long log_table.long_text%type)
is
pragma autonomous_transaction;
begin
insert into log_table values ( systimestamp, p_short, p_long );
commit;
end write_each;
Вот его время:
SQL> begin
2 fast_log.flush;
3 for r in 1..3456 loop
4 fast_log.write_each('SOME TEXT', 'blah blah blah '||to_char(r));
5 end loop;
6 fast_log.flush;
7 end;
8 /
FLUSH::12:32:44.157000::0
FLUSH::12:32:44.610000::0
PL/SQL procedure successfully completed.
SQL>
Время настенных часов, как известно, ненадежно, но пакетный подходВ 2-3 раза быстрее, чем за одну запись appraoch.Несмотря на это, я мог бы выполнить более трех тысяч дискретных транзакций менее чем за полсекунды на ноутбуке (далеко не лучшем).Итак, вопрос: сколько узких мест заносится в журнал?
Чтобы избежать недоразумений:
@ JulesLt опубликовал свой ответ, когда я работал над своим PoC.Хотя в наших взглядах есть сходства, я думаю, что различия в предлагаемых обходных решениях заслуживают публикации.
"Каково время для write_each без автономной, но единственной фиксации в конце?это не важно, что увеличение объема - это большой выигрыш "
Мои тайминги предполагают что-то немного другое.Замена COMMIT на запись с одним COMMIT в конце примерно вдвое сокращает прошедшее время.Все еще медленнее, чем громоздкий подход, но не так сильно.
Ключевым моментом здесь является бенчмаркинг .Мое доказательство концепции работает примерно в шесть раз быстрее, чем тест Жюля (у моей таблицы есть один индекс).Причин тому может быть множество - спецификация машины, версия базы данных (я использую Oracle 11gR1), структура таблиц и т. Д. Другими словами, YMMV.
Итак, учение таково: сначала решите, чтото, что нужно сделать для своего приложения, а затем сравнить его для своей среды.Только рассмотрите другой подход, если ваш тест указывает на серьезную проблему производительности.Предупреждение Кнута о преждевременной оптимизации применяется.