Уважаемые подписчики! Этот выпуск посвящен "знаменитому" сообщению об ошибке ORA-1555: snapshot too old. Как обычно, по мотивам Тома Кайта...
Том!
Не мог бы ты объяснить, что означает сообщение об ошибке snapshot too old.
Когда возникает эта ошибка? По каким причинам? Как избавиться от этой ошибки?
Большое спасибо.
Мне кажется, документ службы поддержки <Note:40689.1> очень хорошо раскрывает эту тему:
В этой статье обсуждаются условия, при которых запрос может вернуть сообщение об ошибке ORA-01555 "snapshot too old (rollback segment too small)". Затем в статье будут обсуждаться действия, которые можно предпринять во избежание этой ошибки и, наконец, будет представлен ряд простых сценариев PL/SQL, иллюстрирующих рассмотренные проблемы.
Предполагается, что читатель знаком со стандартными терминами Oracle, такими как "сегмент отката" и "SCN". В противном случае, необходимо сначала прочитать руководство Oracle Server Concepts и другую соответствующую документацию Oracle.
Кроме этого, ниже кратко рассмотрены два ключевых понятия, которые помогут понять причины возникновения ошибки ORA-01555:
Это понятие описано с руководстве Oracle Server Concepts и поэтому детально не обсуждается. Однако для понимания этой статьи соответствующий раздел руководства надо прочитать и понять, если вы его еще не поняли.
Сервер Oracle обеспечивает многоверсионную согласованность по чтению, ценную возможность, гарантирующую получение согласованного представления данных (отсутствие "грязных чтений").
Это лучше всего проиллюстрировать примером: рассмотрим транзакцию, изменяющую таблицу с миллионом строк. При этом для изменения данных, очевидно, надо посетить большое количество блоков данных. Когда пользователь фиксирует транзакцию, сервер Oracle НЕ проходит повторно по всем этим блокам, чтобы зафиксировать изменения. Это действие выполнит следующая транзакция, которая обратится к любому блоку, затронутому изменением, - она "очистит" его (отсюда и термин "отложенная очистка блока").
При любом изменении блока базы данных сервером Oracle (блока индекса, таблицы, кластера), он сохраняет указатель в заголовке блока данных, который идентифицирует сегмент отката, использовавшийся для хранения данных отката для изменений, выполненных транзакцией. (Они понадобятся, если в дальнейшем пользователь решит не фиксировать изменения и захочет их "отменить".)
При фиксации сервер просто помечает соответствующую запись в заголовке сегмента отката как зафиксированную. Теперь, когда один из измененных блоков посещается снова, сервер Oracle проверяет заголовок блока данных, показывающий, что блок был изменен в определенный момент. Серверу надо разобраться, было ли это изменение зафиксировано или оно еще не зафиксировано. Для этого сервер Oracle оперделяет, какой сегмент отката использовался предыдущей транзакцией (по заголовку блока), а затем определяет по заголовку этого сегмента отката, была ли транзакция зафиксирована или нет.
Если оказывается, что блок зафиксирован, то заголовок блока данных изменяется так, чтобы при последующем доступе к блоку такая обработка не понадобилась.
Это поведение сервера в очень упрощенном виде проиллюстрировано ниже. Мы пройдем по стадиям изменения блока данных.
| Описание: | Это начальное состояние. В начале блока данных имеется область, используемая
для привязки активных транзакций к сегменту отката (часть 'tx'),
а в заголовке сегмента отката имеется таблица, в которой хранится информация
о всех последних транзакциях, использовавших этот сегмент отката. В нашем примере имеется два активных слота транзакций (01 и 02), а следующий свободный слот - слот 03. (Поскольку можно переписывать зафиксированные транзакции.) |
Блок данных 500 Заголовок сегмента отката 5 +----+--------------+ +----------------------+---------+ | tx | Нет | | запись транзакции 01 |ACTIVE | +----+--------------+ | запись транзакции 02 |ACTIVE | | строка 1 | | запись транзакции 03 |COMMITTED| | строка 2 | | запись транзакции 04 |COMMITTED| | ... .. | | ... ... .. | ... | | строка n | | запись транзакции nn |COMMITTED| +-------------------+ +--------------------------------+
| Описание: | Мы изменили строку 2 блока 500. Обратите внимание, что заголовок блока данных изменен и указывает на сегмент отката 5, слот транзакции 3 (5.3), и что транзакция помечена как незафиксированная (Active). |
Блок данных 500 Заголовок сегмента отката 5 +----+--------------+ +----------------------+---------+ | tx |5.3uncommitted|-+ | запись транзакции 01 |ACTIVE | +----+--------------+ | | запись транзакции 02 |ACTIVE | | строка 1 | +--->| запись транзакции 03 |ACTIVE | | строка 2 *изм.* | | запись транзакции 04 |COMMITTED| | ... .. | | ... ... .. | ... | | строка n | | запись транзакции nn |COMMITTED| +-------------------+ +--------------------------------+
| Описание: | Затем пользователь выполняет фиксацию. Учтите, что при этом изменяется только слот соответствующей транзакции в заголовке сегмента отката - транзакция помечается как зафиксированная. С данными в блоке не делается ничего. |
Блок данных 500 Заголовок сегмента отката 5 +----+--------------+ +----------------------+---------+ | tx |5.3uncommitted|--+ | запись транзакции 01 |ACTIVE | +----+--------------+ | | запись транзакции 02 |ACTIVE | | строка 1 | +--->| запись транзакции 03 |COMMITTED| | строка 2 *изм.* | | запись транзакции 04 |COMMITTED| | ... .. | | ... ... .. | ... | | строка n | | запись транзакции nn |COMMITTED| +-------------------+ +--------------------------------+
| Описание: | Через некоторое время другой пользователь (или тот же)
снова обращается к блоку данных 500. Оказывается, что, в соответствии с
заголовком блока, в блоке есть незафиксированное изменение. Сервер Oracle затем использует заголовок блока данных для поиска соответствующего слота таблицы транзакций сегмента отката, проверки, зафиксирована ли транзакция, и изменения блока данных 500 так, чтобы в нем отражалось действительное состояние данных. (Т.е., он выполняет отложенную очистку). |
Блок данных 500 Заголовок сегмента отката 5 +----+--------------+ +----------------------+---------+ | tx | Нет | | запись транзакции 01 |ACTIVE | +----+--------------+ | запись транзакции 02 |ACTIVE | | строка 1 | | запись транзакции 03 |COMMITTED| | строка 2 | | запись транзакции 04 |COMMITTED| | ... .. | | ... ... .. | ... | | строка n | | запись транзакции nn |COMMITTED| +-------------------+ +--------------------------------+
Есть две основные причины возникновения ошибки ORA-01555, которые являются следствием попыток сервера Oracle получить "согласованный по чтению" образ данных:
Обе эти ситуации рассматриваются ниже, вместе с последовательностями шагов, вызывающих получение сообщения об ошибке ORA-01555. При описании этих шагов упоминается "QENV". "QENV" (сокращение от "Query Environment" - среда запроса) - это среда, существовавшая на момент начала запроса и по отношению к которой сервер Oracle пытается получить согласованный по чтению образ. С этой средой связано значение SCN (System Change Number - номер системного изменения) в соответствующий момент времени, так что QENV 50 - это среда запроса для значения SCN 50.
Эту ситуацию можно разбить на две: когда другой сеанс переписывает данные отката, необходимые текущему сеансу, и когда сам текущий сеанс переписывает данные отката, которые ему же необходимы. Именно последняя ситуация обсуждается в этой статье, поскольку обычно ее сложнее понять.
Шаги:
Теперь сервер Oracle может понять по заголвоку блока, что блок был изменен, причеми, позже, чем требуемая QENV (которая соответствовала SCN 50). Поэтому необходимо получить образ блока по состоянию на эту QENV.
Если достаточно старую версию блока можно найти в буферном кэше, она и будет использована, иначе необходимо откатить текущий блок, чтобы сгенерировать другую его версию, соответствующую требуемой среде QENV.
Именно в этом случае сервер Oracle может не найти необходимые данные отката, поскольку другие изменения в сеансе 1 сгенерировали данные отката, которые переписали необходимые серверу данные, и тогда сервер возвращает сообщение об ошибке ORA-1555.
Каждая из этих транзакций использует слот в таблице транзакций сегмента отката, так что со временем придется использовать слоты с начала таблицы (слоты в таблице используются циклически) и все их переписывать. Учтите, что сервер Oracle свободно может повторно использовать эти слоты, потому что все транзакции зафиксированы.
Затем, сервер Oracle пытается найти слот транзакции в заголовке сегмента отката, на который указывает заголовок блока данных. При этом сервер понимает, что это слот был преписан и пытается откатить изменения, выполненные в заголовке сегмента отката, чтобы получить исходную запись слота транзакции.
Если сервер Oracle не сможет откатить таблицу транзакций сегмента отката до нужного момента времени, то вернет сообщение об ошибке ORA-1555, поскольку не может более получить требуемую версию блока данных.
Можно также обнаружить вариант слота транзакции, переписанный при очистке блока. Эта ситуация кратко описана ниже:
Сеанс 1 начинает запрос при QENV 50. После этого другой процесс изменяет блоки, которые понадобятся сеансу 1. Когда сеанс 1 встречает эти блоки, он определяет, что блоки изменились и еще не были очищены (с помощью отложенной очистки блоков). Сеанс 1 должен определить, были ли в дальнейшем изменены строки блока, существовавшие при QENV 50.
Для этого сервер Oracle должен просмотреть соответствующий слот таблицы транзакций сегмента отката, чтобы определить значение SCN для фиксации. Если этот SCN - после QENV, сервер Oracle должен попытаться построить прежнюю версию блока, а если до, достаточно просто выполнить очистку блока, и он будет вполне соответствовать QENV.
Если слот транзакции был переписан и таблицу транзакций нельзя откатить до достаточно давней версии, сервер Oracle не может получить необходимый образ блока, и возвращает сообщение об ошибке ORA-1555.
(Примечание: Обычно сервер Oracle может использовать алгоритм определения значения SCN блока в ходе очистки блока, даже если транзакции в сегменте отката был переписан. Но в этом случае сервер Oracle не может гарантировать, что версия блока не изменилась с момента начала запроса).
В этом разделе представлен ряд решений, которые можно использовать, чтобы избежать проблем с получением сообщения об ошибке ORA-01555, представленных в этой статье. Они относятся к случаям, когда данные в сегменте отката переписываются тем же сеансом, который их сгенерировал, и когда переписывается запись в таблице транзакций сегмента отката.
Следует отметить, что если один сеанс приводит к получению сообщения об ошибке ORA-01555, и оно не связано со специальными случаями, перечисленными в конце этой статьи, значит, сеанс заведомо использует расширение Oracle, разрешающее выбирать данные в нескольких транзакциях (fetch across commit). Это не согласуется с моделью ANSI, и в тех редких случаях, когда возвращается сообщение об ошибке ORA-01555, необходимо использовать одно из представленных ниже решений.
alter session set optimizer_goal = rule;
select count(*) from table_name;
select index_column from table_name where index_column > 24;
Ниже представлены примеры на языке PL/SQL, иллюстрирующие описанные выше ситуации examples, приводящие к получению сообщения об ошибке ORA-1555. Чтобы данные примеры выдавали это сообщение об ошибке, сервер необходимо сконфигурировать следующим образом:
rem * 1555_a.sql -
rem * Пример получения сообщения ora-1555 "Snapshot too old"
rem * сеансом, переписывающим данные отката, необходимые
rem * ему самому.
drop table bigemp;
create table bigemp (a number, b varchar2(30), done char(1));
drop table dummy1;
create table dummy1 (a varchar2(200));
rem * Наполнение таблиц данными.
begin
for i in 1..4000 loop
insert into bigemp values (mod(i,20), to_char(i), 'N');
if mod(i,100) = 0 then
insert into dummy1 values ('ssssssssssss');
commit;
end if;
end loop;
commit;
end;
/
rem * Гарантируем "очистку" таблицы.
select count(*) from bigemp;
declare
-- Необходимо использовать такое условие, чтобы мы снова обратились к
-- измененному блоку в другой момент времени.
-- Если другая транзакция изменяет таблицу, это условие может и не
-- понадобиться.
cursor c1 is select rowid, bigemp.* from bigemp where a < 20;
begin
for c1rec in c1 loop
update dummy1 set a = 'aaaaaaaa';
update dummy1 set a = 'bbbbbbbb';
update dummy1 set a = 'cccccccc';
update bigemp set done='Y' where c1rec.rowid = rowid;
commit;
end loop;
end;
/
rem * 1555_b.sql - Пример получения сообщения ora-1555 "Snapshot too old"
rem * при перезаписи слота транзакции в заголовке сегмента отката.
rem * При этом используется всего один сеанс.
drop table bigemp;
create table bigemp (a number, b varchar2(30), done char(1));
rem * Заполняем данными демонстрационную таблицу.
begin
for i in 1..200 loop
insert into bigemp values (mod(i,20), to_char(i), 'N');
if mod(i,100) = 0 then
commit;
end if;
end loop;
commit;
end;
/
drop table mydual;
create table mydual (a number);
insert into mydual values (1);
commit;
rem * Очищаем демонстрационную таблицу.
select count(*) from bigemp;
declare
cursor c1 is select * from bigemp;
begin
-- Следующее изменение необходимо, чтобы проиллюстрировать проблему,
-- возникающую, если была выполнена очистка блоков таблицы bigemp. Если
-- закомментировать (представленный выше) оператор, выполняющий очистку,
-- то можно будет закомментировать также операторы update и commit, и
-- сценарий закончится с ошибкой ORA-1555 из-за очистки блока.
update bigemp set b = 'aaaaa';
commit;
for c1rec in c1 loop
for i in 1..20 loop
update mydual set a=a;
commit;
end loop;
end loop;
end;
/
Есть и другие специальные случаи, которые могут привести к выдаче сообщения об ошибке ORA-01555. Они перечислены ниже, но крайне редки и в данной статье не обсуждаются:
В этой статье обсуждаются причины возникновения сообщения об ошибке ORA-01555 "Snapshot too old", представлен список возможных методов предотвращения этой ошибки, а также даны простые сценарии на языке PL/SQL, иллюстрирующие рассмотренные ситуации.
Оригинал обсуждения этого вопроса можно найти здесь.
Copyright © 2002 Oracle Corporation
Результаты опроса подписчиков. Возможно, первый из серии авторских выпусков, посвященных индексам. Или перевод очередного блестящего ответа Тома Кайта... Следите за новостями на сайте проекта Open Oracle.
С наилучшими пожеланиями,
В.К.
|
|