WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 | 2 || 4 | 5 |   ...   | 24 |

«Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с ...»

-- [ Страница 3 ] --

Буфер журнала повторного выполнения Буфер журнала повторного выполнения используется для временного кэширования данных активного журнала повторного выполнения перед записью на диск. Поскольку перенос данных из памяти в память намного быстрее, чем из памяти — на диск, использование буфера журнала повторного выполнения позволяет существенно ускорить работу сервера. Данные не задерживаются в буфере журнала повторного выполнения надолго. Содержимое этого буфера сбрасывается на диск: • раз в три секунды;

• при фиксации транзакции;

• при заполнении буфера на треть или когда в нем оказывается 1 Мбайт данных журнала повторного выполнения. Поэтому создание буфера журнала повторного выполнения размером в десятки Мбайт — напрасное расходование памяти. Чтобы использовать буферный кэш журнала повторного выполнения размером 6 Мбайт, например, надо выполнять продолжительные тран Архитектура закции, генерирующие по 2 Мбайт информации повторного выполнения не более чем за три секунды. Если кто-либо в системе зафиксирует транзакцию в течение этих трех секунд, в буфере не будет использовано и 2 Мбайт, — содержимое буфера будет регулярно сбрасываться на диск. Лишь очень немногие приложения выиграют от использования буфера журнала повторного выполнения размером в несколько мегабайт. Стандартный размер буфера журнала повторного выполнения, задаваемый параметром LOG_BUFFER в файле init.ora, определяется как максимальное из значений 512 и (128 * количество процессоров) Кбайт. Минимальный размер этой области равен максимальному размеру блока базы данных для соответствующей платформы, умноженному на четыре. Если необходимо узнать это значение, установите LOG_BUFFER равным 1 байт и перезапустите сервер. Например, на моем сервере под Windows 2000 я получил следующий результат:

SVRMGR> show parameter log_buffer NAME TYPE log_buffer integer VALUE SVRMGR> select * from v$sgastat where name = 'log_buffer';

POOL NAME BYTES log_buffer Теоретически минимальный размер буфера журнала повторного выполнения, независимо от установок в файле init.ora, в данном случае — 65 Кбайт. Фактически он немного больше:

tkyte@TKYTE816> s e l e c t * from v$sga where name = 'Redo B u f f e r s ' ;

NAME VALUE Redo Buffers To есть размер буфера — 76 Кбайт. Дополнительное пространство выделено из соображений безопасности, как "резервные" страницы, защищающие страницы буфера журнала повторного выполнения.

Буферный кэш До сих пор мы рассматривали небольшие компоненты области SGA. Теперь переходим к составляющей, которая достигает огромных размеров. В буферном кэше сервер Oracle хранит блоки базы данных перед их записью на диск, а также после считывания с диска. Это принципиально важный компонент SGA. Если сделать его слишком маленьким, запросы будут выполняться годами. Если же он будет чрезмерно большим, пострадают другие процессы (например, выделенному серверу не хватит пространства для создания области PGA, и он просто не запустится). Блоки в буферном кэше контролируются двумя списками. Это список "грязных" блоков, которые должны быть записаны процессом записи блоков базы данных (это DBWn;

его мы рассмотрим несколько позже). Есть еще список "чистых" блоков, организованный в Oracle 8.0 и предыдущих версиях в виде очереди (LRU — Least Recently Used). Блоки упорядочивались по времени последнего использования. Этот алгоритм был не 110 Глава много изменен в Oracle 8i и последующих версиях. Вместо физического упорядочения списка блоков, сервер Oracle с помощью счетчика, связанного с блоком, подсчитывает количество обращений ("touch count) при каждом обращении (hit) к этому блоку в буферном кэше. Это можно увидеть в одной из действительно "магических" таблиц Х$. Эти таблицы не описаны в документации Oracle, но информация о них периодически просачивается. Таблица X$BH содержит информацию о блоках в буферном кэше. В ней можно увидеть, как "счетчик обращений" увеличивается при каждом обращении к блоку. Сначала необходимо найти блок. Мы используем блок таблицы DUAL — специальной таблицы, состоящей из одной строки и одного столбца, которая есть во всех базах данных Oracle. Необходимо найти соответствующий номер файла и номер блока в файле:

tkyte@TKYTE816> select file_id, block_id 2 from dba_extents 3 where segment_name = 'DUAL' and owner = 'SYS';

FILE_ID 1 BLOCK_ID Теперь можно использовать эту информацию для получения "счетчика обращений" для этого блока:

sys@TKYTE816> select tcn from x$bh where TCH 10 sys@TKYTE816> select * from dual;

D X sys@TKYTE816> select tch from x$bh where f i l e # = 1 and d b a b l k = 4 6 5 ;

TCH 11 sys@TKYTE816> select * from dual;

D X sys@TKYTE816> select tch from x$bh where file# = 1 and dbablk = 465;

TCH 12 file#=1 and d b a b l k = 4 6 5 ;

При каждом обращении к блоку увеличивается значение счетчика. Использованный буфер больше не переносится в начало списка. Он остается на месте, а его "счетчик Архитектура обращений" увеличивается. Блоки со временем перемешаются по списку естественным путем, поскольку измененные блоки переносятся в список "грязных" (для записи на диск процессом DBWn). Кроме того, если несмотря на повторное использование блоков буферный кэш заполнился, и блок с небольшим значением "счетчика обращений" удаляется из списка, он возвращается с новыми данными примерно в середину списка. Полный алгоритм управления списка довольно сложный и меняется с каждой новой версией Oracle. Подробности его работы несущественны для разработчиков, достаточно помнить, что интенсивно используемые блоки кэшируются надолго, а редко используемые — долго в кэше не задерживаются. Буферный кэш в версиях до Oracle 8.0 представлял собой один большой кэш. Все блоки кэшировались одинаково, никаких средств деления пространства буферного кэша на части не существовало. В Oracle 8.0 добавлена возможность создания буферных пулов. С ее помощью можно зарезервировать в буферном кэше место для сегментов (как вы помните, сегменты соответствуют таблицам, индексам и т.д.). Появилась возможность выделить место (буферный пул) достаточного размера для размещения целиком в памяти, например, таблиц-справочников". При чтении сервером Oracle блоков из этих таблиц они кэшируются в этом специальном пуле. Они будут конфликтовать за место в пуле только с другими помещаемыми в него сегментами. Остальные сегменты в системе будут "сражаться" за место в стандартном буферном пуле. При этом повышается вероятность их кэширования: они не выбрасываются из кэша как устаревшие при считывании других, не связанных с ними блоков. Буферный пул, обеспечивающий подобное кэширование, называется пулом KEEP. Блоками в пуле KEEP сервер управляет так же, как в обычном буферном кэше. Если блок используется часто, он остается в кэше;

если к блоку некоторое время не обращались и в буферном пуле не осталось места, этот блок выбрасывается из пула как устаревший. Можно выделить еще один буферный пул. Он называется пулом RECYCLE. В нем блоки выбрасываются иначе, чем в пуле KEEP. Пул KEEP предназначен для продолжительного кэширования "горячих" блоков. Из пула RECYCLE блок выбрасывается сразу после использования. Это эффективно в случае "больших" таблиц, которые читаются случайным образом. (Понятие "большая таблица" очень относительно;

нет эталона для определения того, что считать "большим".) Если в течение разумного времени вероятность повторного считывания блока мала, нет смысла долго держать такой блок в кэше. Поэтому в пуле RECYCLE блоки регулярно перечитываются. Итак, уточняя схему SGA, ее можно представить так:

Глава Разделяемый пул Разделяемый пул — один из наиболее важных фрагментов памяти в области SGA, особенно для обеспечения производительности и масштабируемости. Слишком маленький разделяемый пул может снизить производительность настолько, что система будет казаться зависшей. Слишком большой разделяемый пул может привести к такому же результату. Неправильное использование разделяемого пула грозит катастрофой. Итак, что же такое разделяемый пул? В разделяемом пуле сервер Oracle кэширует различные "программные" данные. Здесь кэшируются результаты разбора запроса. Перед повторным разбором запроса сервер Oracle просматривает разделяемый пул в поисках готового результата. Выполняемый сеансом PL/SQL-код тоже кэшируется здесь, так что при следующем выполнении не придется снова читать его с диска. PL/SQL-код в разделяемом пуле не просто кэшируется, — появляется возможность его совместного использования сеансами. Если 1000 сеансов выполняют тот же код, загружается и совместно используется всеми сеансами лишь одна копия этого кода. Сервер Oracle хранит в разделяемом пуле параметры системы. Здесь же хранится кэш словаря данных, содержащий информацию об объектах базы данных. Короче, в разделяемом пуле хранится все, кроме продуктов питания. Разделяемый пул состоит из множества маленьких (около 4 Кбайт) фрагментов памяти. Память в разделяемом пуле управляется по принципу давности использования (LRU). В этом отношении она похожа на буферный кэш: если фрагмент не используется, он теряется. Стандартный пакет DBMS_SHARED_POOL позволяет изменить это и принудительно закрепить объекты в разделяемом пуле. Это позволяет загрузить часто используемые процедуры и пакеты при запуске сервера и сделать так, чтобы они не выбрасывались из пула как устаревшие. Обычно, если в течение определенного периода времени фрагмент памяти в разделяемом пуле не использовался, он выбрасывается как устаревший. Даже PL/SQL-код, который может иметь весьма большой размер, управляется механизмом постраничного устаревания, так что при выполнении кода очень большого пакета необходимый код загружается в разделяемый пул небольшими фрагментами. Если в течение продолжительного времени он не используется, то в случае переполнения выбрасывается из разделяемого пула, а пространство выделяется для других объектов. Самый простой способ поломать механизм разделяемого пула Oracle — не использовать связываемые переменные. Как было показано в главе 1, отказавшись от использования связываемых переменных, можно "поставить на колени" любую систему, поскольку: • система будет тратить много процессорного времени на разбор запросов;

• система будет тратить очень много ресурсов на управление объектами в разделяемом пуле, т.к. не предусмотрено повторное использование планов выполнения запросов. Если каждый переданный серверу Oracle запрос специфичен, с жестко заданными константами, это вступает в противоречие с назначением разделяемого пула. Разделяемый пул создавался для того, чтобы хранящиеся в нем планы выполнения запросов использовались многократно. Если каждый запрос — абсолютно новый и никогда ранее Архитектура не встречался, в результате кэширования только расходуются дополнительные ресурсы. Разделяемый пул начинает снижать производительность. Обычно эту проблему пытаются решить, увеличивая разделяемый пул, но в результате становится еще хуже. Разделяемый пул снова неизбежно заполняется, и его поддержка требует больших ресурсов, чем поддержка маленького разделяемого пула, поскольку при управлении большим заполненным пулом приходится выполнять больше действий, чем при управлении маленьким заполненным пулом. Единственным решением этой проблемы является применение разделяемых операторов SQL, которые используются повторно. В главе 10 мы опишем параметр инициализации CURSOR_SHARING, который можно использовать для частичного решения подобных проблем, но наиболее эффективное решение — применять повторно используемые SQL-операторы. Даже самые большие из крупных систем требуют от 10000 до 20000 уникальных SQL-операторов. В большинстве систем используется лишь несколько сотен уникальных запросов. Следующий практический пример показывает, насколько все осложняется при неправильном использовании разделяемого пула. Меня пригласили поработать над системой, стандартной процедурой обслуживания которой была остановка экземпляра каждую ночь для очистки области SGA и последующий перезапуск. Это приходилось делать, поскольку в течение дня в системе возникали проблемы, связанные с избыточной загрузкой процессора, и, если сервер работал больше одного дня, производительность начинала падать. Единственная причина этого была в том, что за период с 9 утра до 5 вечера они полностью заполняли разделяемый пул размером 1 Гбайт в области SGA общим размером 1,1 Гбайт. Да, именно так: 0,1 Гбайта было выделено под буферный кэш и другие компоненты, а 1 Гбайт — для кэширования запросов, которые никогда не выполнялись повторно. Систему приходилось перезапускать, потому что свободная память в разделяемом пуле исчерпывалась в течение одного дня. На поиск и удаление устаревших структур (особенно из такого большого разделяемого пула) расходовалось столько ресурсов, что производительность резко падала (хотя она и до этого была далека от оптимальной, ведь приходилось управлять разделяемым пулом в 1 Гбайт). Кроме того, пользователи этой системы постоянно требовали добавления новых процессоров, поскольку жесткий разбор SQL-операторов требовал больших вычислительных ресурсов. Когда, после внесения исправлений, в приложении стали использоваться связываемые переменные, удалось не только снизить требования к ресурсам машины (у них и так вычислительные мощности намного превышали необходимые), но и появилась возможность пересмотреть распределение памяти. Вместо разделяемого пула размером в 1 Гбайт оказалось достаточно выделить 100 Мбайт, причем за много недель непрерывной работы он не заполнился. И последнее, что хотелось бы сказать о разделяемом пуле и параметре инициализации SHARED_POOL_SIZE. Нет никакой связи между результатами выполнения запроса:

sys@TKYTE816> select sum (bytes) from v$sgastat where pool = 'shared pool';

SUM (BYTES) 18322028 1 row selected.

Глава и значением параметра инициализации SHARED_POOL_SIZE: sys@TKYTE816> show parameter shared_pool_size NAME TYPE VALUE shared_pool_size SVRMGR> string кроме того, что значение SUM(BYTES) FROM V$SGASTAT всегда больше, чем значение параметра SHARED_POOL_SIZE. В разделяемом пуле хранится много других структур, не охватываемых соответствующим параметром инициализации. Значение SHARED_POOL_SIZE обычно является основным, но не единственным фактором, определяющим размер разделяемого пула SUM(BYTES). Например, параметр инициализации CONTROL_FILES задает управляющие файлы, а для каждого управляющего файла в разделе "прочее" разделяемого пула требуется 264 байта. Жаль, что показатель 'shared pool' в представлении V$SGASTAT и параметр инициализации SHARED_POOL_SIZE получили похожие названия, поскольку параметр инициализации влияет на размер разделяемого пула, но не задает его полностью.

Большой пул Большой пул назван так не потому, что это "большая" структура (хотя его размер вполне может быть большим), а потому, что используется для выделения больших фрагментов памяти — больших, чем те, для управления которыми создавался разделяемый пул. До его появления в Oracle 8.0, выделение памяти выполнялось в рамках разделяемого пула. Это было неэффективно при использовании средств, выделяющих "большие" объемы памяти, например, при работе в режиме MTS. Проблема осложнялась еще и тем, что при обработке, требующей больших объемов памяти, эта память используется не так, как предполагает управление памятью в разделяемом пуле. Память в разделяемом пуле управляется на основе давности использования, что отлично подходит для кэширования и повторного использования данных. При выделении же больших объемов памяти фрагмент выделяется, используется и после этого он не нужен, т.е. нет смысла его кэшировать. Серверу Oracle требовался аналог буферных пулов RECYCLE и KEEP в буферном кэше. Именно в таком качестве сейчас и выступают большой пул и разделяемый пул. Большой пул — это область памяти, управляемая по принципу пула RECYCLE, а разделяемый пул скорее похож на буферный пул KEEP: если фрагмент в нем используется часто, он кэшируется надолго. Память в большом пуле организована по принципу "кучи" и управляется с помощью алгоритмов, аналогичных используемым функциями malloc() и free() в языке С. После освобождения фрагмента памяти он может использоваться другими процессами. В разделяемом пуле отсутствует понятие освобождения фрагмента памяти. Память выделяется, используется, а затем перестает использоваться. Через некоторое время, если эту память необходимо использовать повторно, сервер Oracle позволит изменить содержимое устаревшего фрагмента. Проблема при использовании только разделяемого пула состоит в том, что все потребности в памяти нельзя подогнать под одну мерку. Большой пул, в частности, используется: • сервером в режиме MTS для размещения области UGA в SGA;

Архитектура • при распараллеливании выполнения операторов — для буферов сообщений, которыми обмениваются процессы для координации работы серверов;

• в ходе резервного копирования для буферизации дискового ввода/вывода утилиты RMAN. Как видите, ни одну из описанных выше областей памяти нельзя помещать в буферный пул с вытеснением небольших фрагментов памяти на основе давности использования. Область UGA, например, не будет использоваться повторно по завершении сеанса, поэтому ее немедленно надо возвращать в пул. Кроме того, область UGA обычно — достаточно большая. Как было показано на примере, где изменялось значение параметра SORT_AREA_RETAINED_SIZE, область UGA может быть очень большой, и, конечно, больше, чем фрагмент в 4 Кбайт. При помещении области UGA в разделяемый пул она фрагментируется на части одинакового размера и, что хуже всего, выделение больших областей памяти, никогда не используемых повторно, приведет к выбрасыванию из пула фрагментов, которые могли бы повторно использоваться. В дальнейшем на перестройку этих фрагментов памяти расходуются ресурсы сервера. То же самое справедливо и для буферов сообщений. После того как сообщение доставлено, в них уже нет необходимости. С буферами, создаваемыми в процессе резервного копирования, все еще сложнее: они большие и сразу после использования сервером Oracle должны "исчезать". Использовать большой пул при работе в режиме MTS не обязательно, но желательно. Если сервер работает в режиме MTS в отсутствие большого пула, вся память выделяется из разделяемого пула, как это и было в версиях Oracle вплоть до 7.3. Изза этого производительность со временем будет падать, поэтому такой конфигурации надо избегать. Большой пул стандартного размера будет создаваться при установке одного из следующих параметров инициализации: DBWn_IO_SLAVES или PARALLEL_AUTOMATIC_TUNING. Рекомендуется задавать размер большого пула явно. Однако стандартное значение не может использоваться во всех без исключения случаях.

Java-пул Java-пул — это самый новый пул памяти в Oracle 8i. Он был добавлен в версии 8.1.5 для поддержки работы Java-машины в базе данных. Если поместить хранимую процедуру на языке Java или компонент EJB (Enterprise JavaBean) в базу данных, сервер Oracle будет использовать этот фрагмент памяти при обработке соответствующего кода. Одним из недостатков первоначальной реализации Java-пула в Oracle 8.1.5 было то, что он не отображался командой SHOW SGA и не был представлен строками в представлении V$SGASTAT. В то время это особенно сбивало с толку, поскольку параметр инициализации JAVA_POOL_SIZE, определяющий размер этой структуры, имел стандартное значение 20 Мбайт. Это заставляло людей гадать, почему область SGA занимает оперативной памяти на 20 Мбайт больше, чем следует. Начиная с версии 8.1.6, однако, Java-пул виден в представлении V$SGASTAT, а также в результатах выполнения команды SHOW SGA. Параметр инициализации JAVA_POOL_SIZE используется для определения фиксированного объема памяти, отводящегося для Java-кода и данных сеансов. В Oracle 8.1.5 этот параметр мог иметь зна Глава чения от 1 Мбайт до 1 Гбайт. В Oracle 8.1.6 и последующих версиях диапазон допустимых значений уже 32 Кбайт—1 Гбайт. Это противоречит документации, где по-прежнему указан устаревший минимум — 1 Мбайт. Java-пул используется по-разному, в зависимости от режима работы сервера Oracle. В режиме выделенного сервера Java-пул включает разделяемую часть каждого Java-класса, использованного хоть в одном сеансе. Эти части только читаются (векторы выполнения, методы и т.д.) и имеют для типичных классов размер от 4 до 8 Кбайт. Таким образом, в режиме выделенного сервера (который, как правило, и используется, если в приложениях применяются хранимые процедуры на языке Java) объем общей памяти для Java-пула весьма невелик;

его можно определить исходя из количества используемых Java-классов. Учтите, что информация о состоянии сеансов при работе в режиме разделяемого сервера в области SGA не сохраняется, поскольку эти данные находятся в области UGA, а она, если вы помните, в режиме разделяемого сервера является частью области PGA. При работе в режиме MTS Java-пул включает: • разделяемую часть каждого Java-класса и • часть области UGA для каждого сеанса, используемую для хранения информации о состоянии сеансов. Оставшаяся часть области UGA выделяется как обычно — из разделяемого пула или из большого пула, если он выделен. Поскольку общий размер Java-пула фиксирован, разработчикам приложений необходимо оценить общий объем памяти для приложения и умножить на предполагаемое количество одновременно поддерживаемых сеансов. Полученное значение будет определять общий размер Java-пула. Каждая Java-часть области UGA будет увеличиваться и уменьшаться при необходимости, но помните, что размер пула должен быть таким, чтобы части всех областей UGA могли поместиться в нем одновременно. В режиме MTS, который обычно используется для приложений, использующих архитектуру CORBA или компоненты EJB (об этом говорилось в главе 1), может потребоваться очень большой Java-пул. Его размер будет зависеть не от количества используемых классов, а от количества одновременно работающих пользователей. Как и большой пул, размеры которого становятся очень большими в режиме MTS, Java-пул тоже может разрастаться до огромных размеров. Итак, в этом разделе была рассмотрена структура памяти сервера Oracle. Мы начали с уровня процессов и сеансов, поговорили об областях PGA (Process Global Area — глобальная область процесса) и UGA (User Global Area — глобальная область пользователя) и разобрались в их взаимосвязи. Было показано, как режим, в котором пользователь подключается к серверу Oracle, определяет организацию памяти. Подключение к выделенному серверу предполагает использование памяти серверным процессом в большем объеме, чем подключение в режиме MTS, но работа в режиме MTS требует создания намного большей области SGA. Затем мы описали компоненты самой области SGA, выделив в ней шесть основных структур. Были описаны различия между разделяемым и большим пулом, и показано, почему большой пул необходим для "сохранения" разделяемого пула. Мы описали Java-пул и его использование в различных условиях. Был рассмотрен буферный кэш и способ деления его на меньшие, более специализированные пулы.

Архитектура Теперь можно переходить к физическим процессам экземпляра Oracle.

Процессы Осталось рассмотреть последний элемент "головоломки". Мы изучили организацию базы данных и набор образующих ее физических файлов. Разбираясь с использованием памяти сервером Oracle, рассмотрели половину экземпляра. Оставшийся компонент архитектуры — набор процессов, образующий вторую половину экземпляра. Некоторые из этих процессов, например процесс записи блоков в базу данных (DBWn) и процесс записи журнала (LGWR), уже упоминались. Здесь мы более детально рассмотрим функцию каждого процесса: что и почему они делают. В этом разделе "процесс" будет использоваться как синоним "потока" в операционных системах, где сервер Oracle реализован с помощью потоков. Так, например, если описывается процесс DBWn, в среде Windows ему соответствует поток DBWn. В экземпляре Oracle есть три класса процессов. • Серверные процессы. Они выполняют запросы клиентов. Мы уже затрагивали тему выделенных и разделяемых серверов. И те, и другие относятся к серверным процессам. • Фоновые процессы. Это процессы, которые начинают выполняться при запуске экземпляра и решают различные задачи поддержки базы данных, такие как запись блоков на диск, поддержка активного журнала повторного выполнения, удаление прекративших работу процессов и т.д. • Подчиненные процессы. Они подобны фоновым процессам, но выполняют, корме того, действия от имени фонового или серверного процесса. Мы рассмотрим все эти процессы и постараемся выяснить, какую роль они играют в экземпляре.

Серверные процессы Мы уже бегло рассматривали эти процессы ранее при обсуждении выделенных и разделяемых серверов. Здесь мы еще раз опишем два вида серверных процессов и более детально рассмотрим их архитектуру. Выделенные и разделяемые серверы решают одну и ту же задачу: обрабатывают передаваемые им SQL-операторы. При получении запроса SELECT * FROM EMP именно выделенный/разделяемый сервер Oracle будет разбирать его и помещать в разделяемый пул (или находить соответствующий запрос в разделяемом пуле). Именно этот процесс создает план выполнения запроса. Этот процесс реализует план запроса, находя необходимые данные в буферном кэше или считывая данные в буферный кэш с диска. Такие серверные процессы можно назвать "рабочими лошадками" СУБД. Часто именно они потребляют основную часть процессорного времени в системе, поскольку выполняют сортировку, суммирование, соединения — в общем, почти все. В режиме выделенного сервера имеется однозначное соответствие между клиентскими сеансами и серверными процессами (или потоками). Если имеется 100 сеансов на UNIX-машине, будет 100 процессов, работающих от их имени. Графически это можно представить так:

Глава клиентское приложение С клиентским приложением скомпонованы библиотеки Oracle. Они обеспечивают функциональный интерфейс (Application Program Interface — API) для взаимодействия с базой данных. Функции API "знают", как передавать запрос к базе данных и обрабатывать возвращаемый курсор. Они обеспечивают преобразование запросов пользователя в передаваемые по сети пакеты, обрабатываемые выделенным сервером. Эти функции обеспечивает компонент Net8 — сетевое программное обеспечение/протокол, используемое Oracle для клиент/серверной обработки (даже в n-звенной архитектуре есть место для клиент/серверного взаимодействия). Сервер Oracle использует такую архитектуру, даже если протокол Net8 не нужен. То есть, когда клиент и сервер работают на одной и той же машине, используется эта двухпроцессорная (известная также как двухзадачная — two-task) архитектура. Эта архитектура обеспечивает два преимущества. • Удаленное выполнение. Клиентское приложение, естественно, может работать не на той машине, где работает СУБД. • Изолирование адресных пространств. Серверный процесс имеет доступ для чтения и записи к области SGA. Ошибочный указатель в клиентском процессе может повредить структуры данных в области SGA, если клиентский и серверный процессы физически взаимосвязаны. Ранее в этой главе мы рассматривали "порождение", или создание, этих серверных процессов процессом прослушивания Oracle Net8 Listener. He будем снова возвращаться к этому процессу, но коротко рассмотрим, что происходит, если процесс прослушивания не задействован. Механизм во многом аналогичен, но вместо создания выделенного сервера процессом прослушивания с помощью вызовов fork()/exec() в ОС UNIX или вызова IPC (Inter Process Communication), как это происходит в Windows, процесс создается непосредственно клиентским процессом. Это можно четко увидеть в ОС UNIX:

ops$tkyte@ORA8I.WORLD> select a.spid dedicated_server, 2 b.process clientpid 3 from v$process a, v$session b 4 where a.addr = b.paddr Архитектура 5 6 and b.audsid = u s e r e n v ( ' s e s s i o n i d ' ) / CLIENTPID DEDICATED ops$tkyte@ORA8I.WORLD> !/bin/pa -lp 7055 FS DID PID PPID С PRI HI ADDR SZ WCHAN TTY 8 S 30174 7055 7054 0 41 20 61ac4230 36815 639b1998 ? ops$tkyte@ORA8I.WORLD> !/bin/pa -lp 7054 F S UID PID PPID С PRI N1 ADDR 8 S 12997 7054 6783 0 51 20 63eece TIME CMD 0:00 oracle SZ WCHAN TTY TIME CMD 1087 63eece30 pts/7 0:00 sqlplus Я использовал запрос для определения идентификатора процесса (PID) моего выделенного сервера (столбец SPID в представлении V$PROCESS — это идентификатор процесса операционной системы, использовавшегося для выполнения запроса). Кроме того, в столбце PROCESS представления V$SESSION находится идентификатор клиентского процесса, подключившегося к базе данных. С помощью команды ps можно явно показать, что PPID (Parent Process ID — идентификатор родительского процесса) моего выделенного сервера соответствует процессу SQL*Plus. В данном случае именно утилита SQL*Plus создала выделенный сервер с помощью системных вызовов fork() и exec(). Теперь давайте более детально рассмотрим другой тип серверных процессов — разделяемый серверный процесс. Для подключения к серверному процессу этого типа обязательно используется протокол Net8, даже если клиент и сервер работают на одной машине, — нельзя использовать режим MTS без процесса прослушивания Net8. Как уже описывалось ранее в этом разделе, клиентское приложение подключается к процессу прослушивания Net8 и перенаправляется на процесс-диспетчер. Диспетчер играет роль канала передачи информации между клиентским приложением и разделяемым серверным процессом. Ниже представлена схема подключения к базе данных через разделяемый сервер:

Глава Как видите, клиентские приложения со скомпонованными в них библиотеками Oracle физически подключаются к диспетчеру MTS. Диспетчеров MTS для любого экземпляра можно сгенерировать несколько, но часто для сотен и даже тысяч пользователей используется один диспетчер. Диспетчер отвечает за получение входящих запросов от клиентских приложений и их размещение в очереди запросов в области SGA. Первый свободный разделяемый серверный процесс, по сути, ничем не отличающийся от выделенного серверного процесса, выберет запрос из очереди и подключится к области UGA соответствующего сеанса (прямоугольника с 'S' на представленной выше схеме). Разделяемый сервер обработает запрос и поместит полученный при его выполнении результат в очередь ответов. Диспетчер постоянно следит за появлением результатов в очереди и передает их клиентскому приложению. С точки зрения клиента нет никакой разницы между подключением к выделенному серверу и подключением в режиме MTS, — они работают одинаково. Различие возникает только на уровне экземпляра.

Выделенный и разделяемый сервер Прежде чем перейти к остальным процессам, давайте обсудим, почему поддерживается два режима подключения и когда лучше использовать каждый из них. Режим выделенного сервера — наиболее широко используемый способ подключения к СУБД Oracle для всех приложений, использующих SQL-запросы. Режим выделенного сервера проще настроить и он обеспечивает самый простой способ подключения. При этом требуется минимальное конфигурирование. Настройка и конфигурирование режима MTS, хотя и несложный, но дополнительный шаг. Основное различие между этими режимами, однако, не в настройке. Оно связано с особенностями работы. При использовании выделенного сервера имеется соответствие один к одному между клиентским сеансом и серверным процессом. В режиме MTS соответствие — многие к одному (много клиентов и один разделяемый сервер). Как следует из названия, разделяемый сервер — общий ресурс, а выделенный — нет. При использовании общего ресурса необходимо стараться не монополизировать его надолго. Как было показано в главе 1, в примере с компонентами EJB, запускавшими продолжительную хранимую процедуру, монополизация этого ресурса может приводить как бы к зависанию системы. На представленной выше схеме имеется два разделяемых сервера. При наличии трех клиентов, более-менее одновременно пытающихся запустить 45-секундный процесс, два из них получат результат через 45 секунд, а третий — через 90 секунд. Правило номер один для режима MTS: убедитесь, что транзакции выполняются быстро. Они могут выполняться часто, но должны быть короткими (что обычно и бывает в системах ООТ). В противном случае будут наблюдаться все признаки замедления работы системы из-за монополизации общих ресурсов несколькими процессами. В экстремальных случаях, если все разделяемые серверы заняты, система "зависает". Поэтому режим MTS очень хорошо подходит для систем класса ООТ, характеризующихся короткими, но частыми транзакциями. В системе класса ООТ транзакции выполняются за миллисекунды, — ни одно действие не требует для выполнения более чем доли секунды. Режим MTS не подходит, однако, для хранилища данных. В такой системе выполняются запросы продолжительностью одна, две, пять и более минут. Для режима MTS это "смертельно". В системе, где 90 процентов задач относятся к классу ООТ, Архитектура а 10 процентов — "не совсем ООТ", можно поддерживать одновременно выделенные и разделяемые серверы в одном экземпляре. В этом случае существенно сокращается количество процессов для пользователей ООТ, а "не совсем ООТ"-задачи не монополизируют надолго разделяемые серверы. Итак, какие же преимущества дает режим MTS, если учитывать, для какого типа транзакций он предназначен? Режим MTS позволяет добиться следующего. Сократить количество процессов/потоков операционной системы В системе с тысячами пользователей ОС может быстро оказаться перегруженной при попытке управлять тысячами процессов. В обычной системе одновременно активна лишь небольшая часть этих тысяч пользователей. Например, я недавно работал над системой с 5000 одновременно работающих пользователей. В каждый момент времени в среднем активны были не более 50. Эта система могла бы работать с 50 разделяемыми серверными процессами, на два порядка (в 100 раз) сокращая количество процессов в операционной системе. При этом существенно сокращается количество переключений контекстов на уровне операционной системы. Искусственно ограничить степень параллелизма Как человеку, участвовавшему во многих тестированиях производительности, преимущества ограничения степени параллелизма мне очевидны. При тестировании клиенты просят запустить как можно больше пользователей, пока система не перестанет работать. Одним из результатов такого рода тестирования является диаграмма, показывающая зависимость количества транзакций от количества одновременно работающих пользователей:

Транзакции в секунду Максимальный параллелизм Одновременно работающие пользователи.

Сначала при добавлении одновременно работающих пользователей количество транзакций растет. С какого-то момента, однако, добавление новых пользователей не увеличивает количества выполняемых в секунду транзакций: оно стабилизируется. Пропускная способность достигла максимума, и время ожидания ответа начинает расти (каждую секунду выполняется то же количество транзакций, но пользователи получают резуль Глава таты со все возрастающей задержкой. При дальнейшем добавлении пользователей пропускная способность начинает падать. Количество одновременно работающих пользователей перед началом этого падения и является максимально допустимой степенью параллелизма в системе. Дальше система переполняется запросами, и образуются очереди. С этого момента система не справляется с нагрузкой. Не только существенно увеличивается время ответа, но и начинает падать пропускная способность системы. Если ограничить количество одновременно работающих пользователей до числа, непосредственно предшествующего падению, можно обеспечить максимальную пропускную способность и приемлемое время ответа для большинства пользователей. Режим MTS позволяет ограничить максимальную степень параллелизма в системе до этого количества одновременно работающих пользователей. Сократить объем памяти, необходимый системе Это одна из наиболее часто упоминаемых причин использования режима MTS: сокращается объем памяти, необходимой для поддержки определенного количества пользователей. Да, сокращается, но не настолько, как можно было бы ожидать. Помните, что при использовании режима MTS область UGA помещается в SGA. Это означает, что при переходе на режим MTS необходимо точно оценить суммарный объем областей UGA и выделить место в области SGA с помощью параметра инициализации LARGE_POOL Поэтому размер области SGA при использовании режима MTS обычно очень большой. Эта память выделяется заранее и поэтому может использоваться только СУБД. Сравните это с режимом разделяемого сервера, когда процессы могут использовать любую область памяти, не выделенную под SGA. Итак, если область SGA становится намного больше вследствие размещения в ней областей UGA, каким же образом экономится память? Экономия связана с уменьшением количества выделяемых областей PGA. Каждый выделенный/разделяемый сервер имеет область PGA. В ней хранится информация процесса. В ней располагаются области сортировки, области хешей и другие структуры процесса. Именно этой памяти для системы надо меньше, если используется режим MTS. При переходе с 5000 выделенных серверов на 100 разделяемых освобождается 4900 областей PGA — именно такой объем памяти и экономится в режиме MTS. Конечно, используют в этих целях режим MTS только при отсутствии выбора. Если необходимо взаимодействовать с компонентами EJB в базе данных, придется использовать режим MTS. Есть и другие расширенные возможности подключения, требующие использования режима MTS. Если необходимо централизовать связи нескольких баз данных, например, также придется использовать режим MTS.

Рекомендация Если система не перегружена и нет необходимости использовать режим MTS для обеспечения необходимой функциональности, лучше использовать выделенный сервер. Выделенный сервер проще устанавливать, и упрощается настройка производительности. Есть ряд операций, которые можно выполнять только при подключении в режиме выделенного сервера, так что в любом экземпляре надо поддерживать либо оба режима, либо только режим выделенного сервера.

Архитектура С другой стороны, если необходимо поддерживать большое количество пользователей и известно, что эксплуатировать систему придется в режиме MTS, я рекомендую разрабатывать и тестировать ее тоже в режиме MTS. Если система разрабатывалась в режиме выделенного сервера и никогда не тестировалась в режиме MTS, вероятность неудачи повышается. Испытывайте систему в рабочих условиях;

тестируйте ее производительность;

проверьте, хорошо ли она работает в режиме MTS. То есть, проверьте, не монополизирует ли она надолго разделяемые серверы. Обнаруженные на стадии разработки недостатки устранить гораздо проще, чем при внедрении. Для сокращения времени работы процесса можно использовать средства расширенной обработки очередей (Advanced Queues — AQ), но это надо учесть в проекте приложения. Такие вещи лучше делать на этапе разработки. Если в приложении уже используется пул подключений (например, пул подключений компонентов ЛЕЕ) и размер этого пула определен верно, использование режима MTS только снизит производительность. Размер пула подключений уже рассчитан с учетом максимального количества одновременных подключений, поэтому необходимо, чтобы каждое из этих подключений выполнялось непосредственно к выделенному серверу. Иначе один пул подключений будет просто подключаться к другому пулу подключений.

Фоновые процессы Экземпляр Oracle состоит из двух частей: области SGA и набора фоновых процессов. Фоновые процессы выполняют рутинные задачи сопровождения, обеспечивающие работу СУБД. Есть, например, процесс, автоматически поддерживающий буферный кэш и при необходимости записывающий блоки данных на диск. Есть процесс, копирующий заполненный файл активного журнала повторного выполнения в архив. Еще один процесс отвечает за очистку всех структур, которые использовались завершившимися процессами, и т.д. Каждый из этих процессов решает конкретную задачу, но работает в координации с остальными. Например, когда процесс, записывающий файлы журнала, заполняет один журнал и переходит на следующий, он уведомляет процесс, отвечающий за архивирование заполненного журнала, что для него есть работа. Есть два класса фоновых процессов: предназначенные исключительно для решения конкретных задач (как только что описанные) и решающие множество различных задач. Например, есть фоновый процесс, обеспечивающий работу внутренних очередей заданий в Oracle. Этот процесс контролирует очередь заданий и выполняет находящиеся в ней задания. Во многом он похож на выделенный сервер, но без подключения к клиенту. Сейчас мы рассмотрим все эти фоновые процессы, начиная с тех, которые выполняют конкретную задачу, а затем перейдем к процессам "общего назначения".

Фоновые процессы, предназначенные для решения конкретных задач На следующей схеме представлены фоновые процессы экземпляра Oracle, имеющие конкретное назначение:

Глава Вы не обязательно увидите все эти процессы сразу после запуска своего экземпляра, но большинство из них работает в каждом экземпляре. Процесс ARCn (архиватор) запускается только при работе в режиме архивирования журналов (Archive Log Mode) при включенном автоматическом архивировании. Процессы LMD0, LCKn, LMON и BSP (подробнее о них — ниже) запускаются только при работе с Oracle Parallel Server (конфигурация сервера Oracle, поддерживающая несколько экземпляров на различных машинах в кластерной среде), если открывается одна и та же база данных. Для простоты на схеме не показаны процессы диспетчеров MTS (Dnnn) и разделяемых серверов (Snnn). Поскольку мы только что детально их рассмотрели, они не показаны, чтобы упростить схему. Предыдущая схема показывает, что можно "увидеть" при запуске экземпляра Oracle, если база данных смонтирована и открыта. Например, в моей UNIX-системе сразу после запуска экземпляра имеются следующие процессы:

/bin/ps ora816 ora816 ora816 ora816 ora816 ora816 ora816 ora816 ora816 ora816 -aef | grep 'ora * ora8i$' 10 Jan 17 ? 5:02 ora_arcO_ora8i 20642 265:44 ora_snp0_ora8i 10 Jan 17 ? 20636 10 92:17 ora_lgwr_ora8i Jan 17 ? 20628 10 9:23 ora_dbw0_ora8i Jan 17 ? 20626 Jan 17 ? 0:00 ora_s000_oraBi 10 20638 Jan 17 ? 0:04 ora_reco_ora8i 20634 10 10 Jan 17 ? 6:56 ora_ckpt_ora8i 20630 10 Jan 17 ? 186:44 ora_smon_ora8i 20632 0:00 ora_d000_ora8i 10 Jan 17 ? 20640 10 Jan 17 ? 0:05 ora_pmon_ora8i Они соответствуют процессам, представленным на схеме, за исключением процесса SNPn (о нем будет рассказано позже, т.к. он не является фоновым процессом, выпол Архитектура няющим "конкретную" задачу). Обратите внимание на соглашение по именованию этих процессов. Имя процесса начинается с префикса ога_. Затем следуют четыре символа, представляющие фактическое имя процесса, а затем суффикс — _ora8i. Дело в том, что у меня параметр инициализации ORACLE_SID (идентификатор сайта) имеет значение ora8i. В ОС UNIX это существенно упрощает идентификацию фоновых процессов Oracle и их привязку к определенному экземпляру (в Windows простого способа для этого нет, поскольку фоновые процессы реализованы как потоки одного большого процесса). Но что самое интересное, хотя это и не очевидно по представленным результатам, — все эти процессы реализуются одним и тем же двоичным файлом. Вы не найдете на диске двоичный выполняемый файл arc0, точно так же, как не найдете файлов LGWR и DBW0. Все эти процессы реализуются файлом oracle (именно этот выполняемый двоичный файл запускается). Просто при запуске процессы получают такие псевдонимы, чтобы проще было идентифицировать их назначение. Это позволяет совместно использовать большую часть объектного кода на платформе UNIX. В среде Windows это вообще не имеет значения, поскольку процессы Oracle — всего лишь потоки в одном физическом процессе, поэтому все они — один большой двоичный файл. Давайте теперь рассмотрим функции каждого процесса.

PMON - монитор процессов Этот процесс отвечает за очистку после нештатного прекращения подключений. Например, если выделенный сервер "падает" или, получив сигнал, прекращает работу, именно процесс PMON освобождает ресурсы. Процесс PMON откатит незафиксированные изменения, снимет блокировки и освободит ресурсы в области SGA, выделенные прекратившему работу процессу. Помимо очистки после прерванных подключений, процесс PMON контролирует другие фоновые процессы сервера Oracle и перезапускает их при необходимости (если это возможно). Если разделяемый сервер или диспетчер сбоит (прекращает работу), процесс PMON запускает новый процесс (после очистки структур сбойного процесса). Процесс PMON следит за всеми процессами Oracle и либо перезапускает их, либо прекращает работу экземпляра, в зависимости от ситуации. Например, в случае сбоя процесса записи журнала повторного выполнения (LGWR) экземпляр надо перезапускать. Это серьезная ошибка и самое безопасное — немедленно прекратить работу экземпляра, предоставив исправление данных штатному процессу восстановления. Это происходит очень редко, и о случившемся надо немедленно сообщить службе поддержки Oracle. Еще одна функция процесса PMON в экземпляре (версия Oracle 8i) — регистрировать экземпляр в процессе прослушивания протокола Net8. При запуске экземпляра процесс PMON опрашивает известный порт (если явно не указан другой), чтобы убедиться, запущен и работает ли процесс прослушивания. Известный/стандартный порт, используемый сервером Oracle, — порт 1521. А что произойдет, если процесс прослушивания запущен на другом порте? В этом случае используется тот же механизм, но адрес процесса прослушивания необходимо указать явно с помощью параметра инициализации LOCAL_LISTENER. Если процесс прослушивания запущен, процесс PMON связывается с ним и передает соответствующие параметры, например имя службы.

Глава SMON - монитор системы SMON — это процесс, занимающийся всем тем, от чего "отказываются" остальные процессы. Это своего рода "сборщик мусора" для базы данных. Вот некоторые из решаемых им задач. • Очистка временного пространства. С появлением по-настоящему временных табличных пространств эта задача упростилась, но она не снята с повестки дня полностью. Например, при построении индекса выделяемые ему в ходе создания экстенты помечаются как временные (TEMPORARY). Если выполнение оператора CREATE INDEX прекращено досрочно по какой-либо причине, процесс SMON должен эти экстенты освободить. Есть и другие операции, создающие временные экстенты, за очистку которых также отвечает процесс SMON. • Восстановление после сбоев. Процесс SMON после сбоя восстанавливает экземпляр при перезапуске. • Дефрагментация свободного пространства. При использовании табличных пространств, управляемых по словарю, процесс SMON заменяет расположенные подряд свободные экстенты одним "большим" свободным экстентом. Это происходит только в табличном пространстве, управляемом по словарю и имеющем стандартную конструкцию хранения с ненулевым значением параметра PCTINCREASE. • Восстановление транзакций, затрагивающих недоступные файлы. Эта задача аналогична той, которая возникает при запуске базы данных. Процесс SMON восстанавливает сбойные транзакции, пропущенные при восстановлении экземпляра после сбоя по причине недоступности файлов для восстановления. Например, файл мог быть на недоступном или на не смонтированном диске. Когда файл будет доступен, процесс SMON восстановит его. • Восстановление сбойного экземпляра в OPS. В конфигурации Oracle Parallel Server, если одна из машин кластера останавливается (на ней происходит сбой), другая машина в экземпляре откроет файлы журнала повторного выполнения этой сбойной машины и восстановит все данные этой машины. • Очистка таблицы OBJ$. OBJ$ — низкоуровневая таблица словаря данных, содержащая записи практически для каждого объекта (таблицы, индекса, триггера, представления и т.д.) базы данных. Часто там встречаются записи, представляющие удаленные или "отсутствующие" объекты, используемые механизмом поддержки зависимостей Oracle. Процесс SMON удаляет эти ненужные строки. • Сжатие сегментов отката. Процесс SMON автоматически сжимает сегмент отката до заданного размера. • "Отключение" сегментов отката. Администратор базы данных может "отключить" или сделать недоступным сегмент отката с активными транзакциями. Активные транзакции могут продолжать использование такого отключенного сегмента отката. В этом случае сегмент отката фактически не отключается: он помечается для Архитектура "отложенного отключения". Процесс SMON периодически пытается "действительно" отключить его, пока это не получится. Этот список дает представление о том, что делает процесс SMON. Как видно из представленной выше информации о процессах, полученной с помощью команды ps, процесс SMON может со временем потребовать существенных вычислительных ресурсов (команда ps выполнялась на машине, где экземпляр проработал около месяца). Процесс SMON периодически "пробуждается" (или его "будят" другие фоновые процессы) для выполнения задач сопровождения.

RECO - восстановление распределенной базы данных Процесс RECO имеет очень конкретную задачу: он восстанавливает транзакции, оставшиеся в готовом состоянии из-за сбоя или потери связи в ходе двухэтапной фиксации (2PC). 2PC — это распределенный протокол, позволяющий неделимо фиксировать изменения в нескольких удаленных базах данных. Он пытается максимально снизить вероятность распределенного сбоя перед фиксацией. При использовании протокола 2PC между N базами данных одна из баз данных обычно (но не всегда) та, к которой первоначально подключился клиент, становится координатором. Соответствующий сервер опрашивает остальные N -1 серверов, готовы ли они фиксировать транзакцию. Фактически, этот сервер связывается с остальными N -1 серверами и просит их подготовиться к фиксации. Каждый из N -1 серверов сообщает о своем состоянии готовности как да (YES) или нет (NO). Если любой из серверов вернул NO, вся транзакция откатывается. Если все серверы вернули YES, координатор рассылает всем N -1 серверам сообщение о постоянной фиксации. Если серверы ответили YES и подготовились к фиксации, но до получения директивы о фактической фиксации от координатора происходит сбой сети или возникает какая-то другая ошибка, транзакция становится сомнительной (in-doubt) распределенной транзакцией. Протокол 2PC старается сократить до минимума время, в течение которого это может произойти, но не может полностью предотвратить сомнительные транзакции. Если сбой произойдет в определенном месте и в определенное время, дальнейшую обработку сомнительной транзакции выполняет процесс RECO. Он пытается связаться с координатором транзакции, чтобы узнать ее исход. До этого транзакция остается незафиксированной. Связавшись с координатором транзакции, процесс RECO восстановит либо откатит ее. Если связаться с координатором долго не удается и имеется ряд сомнительных транзакций, их можно зафиксировать или откатить вручную. Это приходится делать, поскольку сомнительная распределенная транзакция может вызвать блокирование читающих пишущими (единственный случай в СУБД Oracle). Ваш администратор базы данных должен связаться с администратором другой базы данных и попросить его определить состояние сомнительных транзакций. Затем администратор базы данных может зафиксировать или откатить их, предоставив все остальное процессу RECO. CKPT — обработка контрольной точки Процесс обработки контрольной точки вовсе не обрабатывает ее, как можно предположить по названию, — это делает процесс DBWn. Процесс CKPT просто содейству Глава ет обработке контрольной точки, обновляя заголовки файлов данных. Раньше процесс CKPT был необязательным, но, начиная с версии 8.0, он запускается всегда, так что он представлен в результатах выполнения команды ps в ОС UNIX. Ранее заголовки файлов данных обновлялись в соответствии с информацией о контрольной точке процессом записи журнала LGWR (Log Writer). Однако с ростом размеров баз данных и увеличением количества файлов это стало невыполнимой задачей для процесса LGWR. Если процессу LGWR надо обновлять десятки, сотни, а то и тысячи файлов, увеличивается вероятность того, что ожидающие фиксации транзакций сеансы будут ждать слишком долго. Процесс CKPT снимает эту задачу с процесса LGWR.

DBWn - запись блоков базы данных Процесс записи блоков базы данных (Database Block Writer — DBWn) — фоновый процесс, отвечающий за запись измененных блоков на диск. Процесс DBWn записывает измененные блоки из буферного кэша, чтобы освободить пространство в кэше (чтобы освободить буферы для чтения других данных) или в ходе обработки контрольной точки (чтобы перенести вперед позицию в активном файле журнала повторного выполнения, с которой сервер Oracle начнет чтение при восстановлении экземпляра после сбоя). Как было описано ранее, при переключении журнальных файлов сервером Oracle запрашивается обработка контрольной точки. Серверу Oracle нужно перенести отметку контрольной точки, чтобы не было необходимости в только что заполненном активном файле журнала повторного выполнения. Если ему не удастся это сделать до того, как возникнет необходимость в файле журнала повторного выполнения, выдается сообщение, что обработка контрольной точки не завершена (checkpoint not complete), и придется ждать завершения обработки. Как видите, производительность процесса DBWn может иметь принципиальное значение. Если он недостаточно быстро записывает блоки для освобождения буферов, сеансам приходится ждать события FREE_BUFFER_WAITS, и показатель 'Write Complete Waits' начинает расти. Можно сконфигурировать несколько (до десяти) процессов DBWn (DBW0... DBW9). В большинстве систем работает только один процесс записи блоков базы данных, но в больших, многопроцессорных системах имеет смысл использовать несколько. Если сконфигурировано более одного процесса DBWn, не забудьте также увеличить значение параметра инициализации DB_BLOCK_LRU_LATCHES. Он определяет количество защелок списков по давности использования, LRU lists (теперь, в версии 8i, их называют списками количества обращений — touch lists). Каждый процесс DBWn должен иметь собственный список. Если несколько процессов DBWn совместно используют один список блоков для записи на диск, они будут конфликтовать друг с другом при доступе к списку. Обычно процесс DBWn использует асинхронный ввод/вывод для записи блоков на диск. При использовании асинхронного ввода/вывода процесс DBWn собирает пакет блоков для записи и передает его операционной системе. Процесс DBWn не ждет, пока ОС запишет блоки, — он собирает следующий пакет для записи. Завершив асинхронную запись, ОС уведомляет об этом процесс DBWn. Это позволяет процессу DBWn работать намного быстрее, чем при последовательном выполнении действий. В разделе "Подчиненные процессы" будет показано, как с помощью подчиненных процессов вво Архитектура да/вывода можно эмулировать асинхронный ввод/вывод на платформах, где он не поддерживается. И последнее замечание о процессе DBWn. Он, по определению, записывает блоки, разбросанные по всему диску, — процесс DBWn выполняет множество записей вразброс. В случае изменений будут изменяться разбросанные блоки индекса и блоки данных, достаточно случайно распределенные по диску. Процесс LGWR, напротив, выполняет в основном запись последовательных блоков в журнал повторного выполнения. Это — важное отличие и одна из причин, почему сервер Oracle имеет журнал повторного выполнения и отдельный процесс LGWR. Записи вразброс выполняются намного медленнее, чем последовательные записи. Имея грязные блоки в буферном кэше в SGA и процесс LGWR, записывающий большое количество последовательных блоков информации для восстановления измененных буферов, можно повысить производительность. Сочетание работы двух процессов — процесс DBWn медленно работает в фоновом режиме, тогда как процесс LGWR быстро выполняет работу для ожидающего пользователя — позволяет повысить общую производительность. Это верно даже несмотря на то, что сервер Oracle может выполнять больший объем ввода/вывода, чем надо (записывает в журнал и в файл данных), — записи в активный журнал повторного выполнения можно пропустить, если в ходе обработки контрольной точки сервер Oracle уже записал измененные блоки на диск. LGWR — запись журнала Процесс LGWR отвечает за сброс на диск содержимого буфера журнала повторного выполнения, находящегося в области SGA. Он делает это: • раз в три секунды;

• при фиксации транзакции;

• при заполнении буфера журнала повторного выполнения на треть или при записи в него 1 Мбайт данных. Поэтому создание слишком большого буфера журнала повторного выполнения не имеет смысла: сервер Oracle никогда не сможет использовать его целиком. Все журналы записываются последовательно, а не вразброс, как вынужден выполнять ввод/вывод процесс DBWn. Запись большими пакетами, как в этом случае, намного эффективнее, чем запись множества отдельных блоков в разные части файла. Это одна из главных причин выделения процесса LGWR и журнала повторного выполнения. Эффективность последовательной записи измененных байтов перевешивает расход ресурсов на дополнительный ввод/вывод. Сервер Oracle мог бы записывать блоки данных непосредственно на диск при фиксации, но это потребовало бы записи множества разбросанных блоков, а это существенно медленнее, чем последовательная запись изменений процессом LGWR. ARCn — архивирование Задача процесса ARCn — копировать в другое место активный файл журнала повторного выполнения, когда он заполняется процессом LGWR. Эти архивные файлы журнала повторного выполнения затем можно использовать для восстановления носителя.

Глава Тогда как активный журнал повторного выполнения используется для "исправления" файлов данных в случае сбоя питания (когда прекращается работа экземпляра), архивные журналы повторного выполнения используются для восстановления файлов данных в случае сбоя диска. Если будет потерян диск, содержащий файл данных /d0l/ oradata/ora8i/system.dbf, можно взять резервные копии за прошлую неделю, восстановить из них старую копию файла и попросить сервер применить активный журнал повторного выполнения и все архивные журналы, сгенерированные с момента создания этой резервной копии. Это "подтянет" файл по времени к остальным файлам в базе данных, и можно будет продолжить работу без потери данных. Процесс ARCn обычно копирует активный журнал повторного выполнения в несколько мест (избыточность — гарантия сохранности данных!). Это могут быть диски на локальной машине или, что лучше, на другой машине, на случай катастрофического сбоя. Во многих случаях архивные файлы журнала повторного выполнения копируются затем другим процессом на третье устройство хранения, например на ленту. Они также могут отправляться на другую машину для применения к резервной базе данных (это одно из средств защиты от сбоев, предлагаемое Oracle).

BSP — сервер блоков Этот процесс используется исключительно в среде Oracle Parallel Server (OPS). OPS — конфигурация Oracle, при которой несколько экземпляров монтируют и открывают одну и ту же базу данных. Каждый экземпляр Oracle в этом случае работает на своей машине в кластере, и все они имеют доступ для чтения и записи к одному и тому же набору файлов базы данных. При этом буферные кэши в SGA экземпляров должны поддерживаться в согласованном состоянии. Для этого и предназначен процесс BSP. В ранних версиях OPS согласование достигалось с помощью сброса блока ('ping'). Если машине в кластере требовалось согласованное по чтению представление блока данных, заблокированного в исключительном режиме другой машиной, выполнялся обмен данными с помощью сброса на диск. В результате получалась очень дорогостоящая операция чтения данных. Сейчас, при наличии процесса BSP, обмен происходит из кэша в кэш через высокоскоростное соединение машин в кластере.

LMON — контроль блокировок Этот процесс используется исключительно в среде OPS. Процесс LMON контролирует все экземпляры кластера для выявления сбоя экземпляра. Затем он вместе с диспетчером распределенных блокировок (Distributed Lock Manager — DLM), используемым аппаратным обеспечением кластера, восстанавливает глобальные блокировки, которые удерживаются сбойным экземпляром.

LMD - демон диспетчера блокировок Этот процесс используется исключительно в среде OPS. Процесс LMD управляет глобальными блокировками и глобальными ресурсами для буферного кэша в кластерной среде. Другие экземпляры посылают локальному процессу LMD запросы с требованием снять блокировку или определить, кто ее установил. Процесс LMD также выявляет и снимает глобальные взаимные блокировки.

Архитектура LCKn - блокирование Процесс LCKn используется исключительно в среде OPS. Он подобен по функциям описанному выше процессу LMD, но обрабатывает запросы ко всем остальным глобальным ресурсам, кроме буферного кэша.

Служебные фоновые процессы Эти фоновые процессы необязательны — они запускаются в случае необходимости. Они реализуют средства, необязательные для штатного функционирования базы данных. Использование этих средств инициируется явно или косвенно, при использовании возможности, требующей их запуска. Служебных фоновых процессов — два. Один из них запускает посланные на выполнение задания. В СУБД Oracle встроена очередь пакетных заданий, позволяющая выполнять по расписанию однократные или периодические задания. Другой процесс поддерживает и обрабатывает таблицы очереди, используемые средствами расширенной поддержки очередей (Advanced Queuing — AQ). Средства AQ обеспечивают встроенные возможности обмена сообщениями между сеансами базы данных. Эти процессы можно увидеть в среде ОС UNIX, как и любой другой фоновый процесс, с помощью команды ps. В представленных ранее результатах выполнения команды ps можно видеть, что у меня в экземпляре работает один процесс очереди заданий (ora_snp0_ora8i) и ни одного процесса очереди.

SNPn - обработка снимков (очереди заданий) Сейчас можно сказать, что имя для процесса SNPn выбрано неудачно. В версии 7.0 сервера Oracle впервые поддерживалась репликацию. Это делалось с помощью объекта базы данных, известного как моментальный снимок (snapshot). Внутренним механизмом для обновления или приведения к текущему состоянию моментальных снимков был SNPn — процесс обработки снимков (snapshot process). Этот процесс контролировал таблицу заданий, по которой определял, когда необходимо обновлять моментальные снимки в системе. В Oracle 7.1 корпорация Oracle открыла это средство для общего доступа через пакет DBMS_JOB. To, что было связано с моментальными снимками в версии 7.0, стало "очередью заданий" в версии 7.1 и последующих. Со временем имена параметров для управления очередью (как часто ее надо проверять и сколько процессов может быть в очереди) изменились со SNAPSHOT_REFRESH_INTERVAL и SNAPSHOT_REFRESH_PROCESSES на JOB_QUEUE_INTERVAL и JOB_QUEUE_PROCESSES. А вот имя процесса операционной системы не изменилось. Можно иметь до 36 процессов очереди заданий. Они именуются SNP0, SNP1,..., SNP9, SNPA,..., SNPZ. Эти процессы очередей заданий интенсивно используются при репликации в ходе обновления моментального снимка или материализованного представления. Разработчики также часто используют их для запуска отдельных (фоновых) или периодически выполняющихся заданий. Например, далее в книге будет показано, как использовать очереди заданий для существенного ускорения обработки: за счет дополнительной работы в одном месте можно сделать намного приятнее среду для пользователя (аналогично тому, как сделано в самом сервере Oracle при использовании процессов LGWR и DBWn).

Глава Процессы SNPn сочетают в себе особенности как разделяемого, так и выделенного сервера: обрабатывают несколько заданий, но памятью управляют как выделенный сервер (область UGA находится в области PGA процесса). Процесс очереди заданий выполняет в каждый момент времени только одно задание. Вот почему необходимо несколько процессов, если требуется выполнять несколько заданий одновременно. На уровне заданий не поддерживаются потоки или вытеснение. Запущенное задание выполняется, пока не будет выполнено (или не произойдет сбой). В приложении А мы более детально рассмотрим пакет DBMS_JOB и нетрадиционное использование очереди заданий.

QMNn — монитор очередей Процесс QMNn по отношению к таблицам AQ выполняет ту же роль, что и процесс SNPn по отношению к таблице заданий. Этот процесс контролирует очереди и уведомляет ожидающие сообщений процессы о том, что доступно сообщение. Он также отвечает за распространение очередей — возможность переместить сообщение, поставленное в очередь в одной базе данных, в другую базу данных для извлечения из очереди. Монитор очередей — это необязательный фоновый процесс. Параметр инициализации AQ_TM_PROCESS позволяет создать до десяти таких процессов с именами QMN0,..., QMN9. По умолчанию процессы QMNn не запускаются.

EMNn — монитор событий Процессы EMNn — часть подсистемы расширенной поддержки очередей. Они используются для уведомления подписчиков очереди о сообщениях, в которых они могут быть заинтересованы. Это уведомление выполняется асинхронно. Имеются функции Oracle Call Interface (OCI) для регистрации обратного вызова, уведомляющего о сообщении. Обратный вызов — это функция в программе OCI, которая вызывается автоматически при появлении в очереди определенного сообщения. Фоновый процесс EMNn используется для уведомления подписчика. Процесс EMNn запускается автоматически при выдаче первого уведомления в экземпляре. После этого приложение может явно вызвать message_receive(dequeue) для извлечения сообщения из очереди.

Подчиненные процессы Теперь мы готовы рассмотреть последний класс процессов Oracle — подчиненные процессы. В сервере Oracle есть два типа подчиненных процессов — ввода/вывода (I/O slaves) и параллельных запросов (Parallel Query slaves).

Подчиненные процессы ввода/вывода Подчиненные процессы ввода/вывода используются для эмуляции асинхронного ввода/вывода в системах или на устройствах, которые его не поддерживают. Например, ленточные устройства (чрезвычайно медленно работающие) не поддерживают асинхронный ввод/вывод. Используя подчиненные процессы ввода/вывода, можно сымитировать для ленточных устройств такой способ работы, который операционная система обычно обеспечивает для дисков. Как и в случае действительно асинхронного ввода/вывода, Архитектура процесс, записывающий на устройство, накапливает большой объем данных в виде пакета и отправляет их на запись. Об их успешной записи процесс (на этот раз — подчиненный процесс ввода/вывода, а не ОС) сигнализирует исходному вызвавшему процессу, который удаляет этот пакет из списка данных, ожидающих записи. Таким образом, можно существенно повысить производительность, поскольку именно подчиненные процессы ввода/вывода ожидают завершения работы медленно работающего устройства, а вызвавший их процесс продолжает выполнять другие важные действия, собирая данные для следующей операции записи. Подчиненные процессы ввода/вывода используются в нескольких компонентах Oracle 8i — процессы DBWn и LGWR используют их для имитации асинхронного ввода/вывода, а утилита RMAN (Recovery MANager — диспетчер восстановления) использует их при записи на ленту. Использование подчиненных процессов ввода/вывода управляется двумя параметрами инициализации. • BACKUP_TAPE_IO_SLAVES. Этот параметр указывает, используются ли подчиненные процессы вода/вывода утилитой RMAN для резервного копирования или восстановления данных с ленты. Поскольку этот параметр предназначен для ленточных устройств, а к ленточным устройствам в каждый момент времени может обращаться только один процесс, он — булева типа, а не задает количество используемых подчиненных процессов, как можно было ожидать. Утилита RMAN запускает необходимое количество подчиненных процессов, в соответствии с количеством используемых физических устройств. Если параметр BACKUP_TAPE_IO_SLAVES имеет значение TRUE, то для записи или чтения с ленточного устройства используется подчиненный процесс ввода/вывода. Если этот параметр имеет (стандартное) значение FALSE, подчиненные процессы ввода/вывода не используются при резервном копировании. К ленточному устройству тогда обращается фоновый процесс, выполняющий резервное копирование. • DBWn_IO_SLAVES. Задает количество подчиненных процессов ввода/вывода, используемых процессом DBWn. Процесс DBWn и его подчиненные процессы всегда записывают на диск измененные буфера буферного кэша. По умолчанию этот параметр имеет значение 0, и подчиненные процессы ввода/вывода не используются.

Подчиненные процессы параллельных запросов В Oracle 7.1 появились средства распараллеливания запросов к базе данных. Речь идет о возможности создавать для SQL-операторов типа SELECT, CREATE TABLE, CREATE INDEX, UPDATE и т.д. план выполнения, состоящий из нескольких планов, которые можно выполнять одновременно. Результаты выполнения этих планов объединяются. Это позволяет выполнить операцию за меньшее время, чем при последовательном выполнении. Например, если имеется большая таблица, разбросанная по десяти различным файлам данных, 16-процессорный сервер, и необходимо выполнить к этой таблице запрос, имеет смысл разбить план выполнения этого запроса на 16 небольших частей и полностью использовать возможности сервера. Это принципиально отличается от использования одного процесса для последовательного чтения и обработки всех данных.

Глава Резюме Вот и все компоненты СУБД Oracle. Мы рассмотрели файлы, используемые в СУБД Oracle: небольшой, но важный файл параметров инициализации init.ora, файлы данных, файлы журнала повторного выполнения и т.д. Мы изучили структуры памяти, используемые экземпляром Oracle как в серверных процессах, так и в области SGA. Было показано, как различные конфигурации сервера, например подключение в режиме MTS и к выделенному серверу, принципиально влияют на использование памяти в системе. Наконец, мы рассмотрели процессы (или потоки — в зависимости от базовой ОС), обеспечивающие выполнение функций сервера Oracle. Теперь мы готовы к рассмотрению других возможностей сервера Oracle — управления блокированием и одновременным доступом, и поддержки транзакций.

Блокирование и одновременный доступ Одна из основных проблем при разработке многопользовательских приложений баз. данных — обеспечить одновременный доступ максимальному количеству пользователей при согласованном чтении и изменении данных каждым из них. Механизмы блокирования и управления одновременным доступом, позволяющие решить эту проблему, являются ключевыми в любой базе данных, и в СУБД Oracle они весьма эффективны. Однако реализация этих механизмов в Oracle уникальна, и разработчик приложений должен обеспечить их корректное использование в программе при манипулировании данными. В противном случае приложение будет работать не так, как предполагалось, и целостность данных может быть нарушена (как было показано в главе 1). В этой главе мы подробно рассмотрим, как сервер Oracle блокирует данные, а также последствия выбора модели блокирования, которые необходимо учитывать при разработке многопользовательских приложений. Мы изучим уровни блокирования данных в Oracle, реализацию многовариантной согласованности по чтению и ее последствия для разработчиков приложений. Я буду сравнивать модель блокирования Oracle с другими популярными вариантами реализации — в основном для того, чтобы развеять миф, будто "блокирование на уровне строк требует дополнительных затрат ресурсов". Так происходит только в том случае, если эти затраты ресурсов связаны с соответствующей реализацией.

Глава Что такое блокировки?

Блокировка — это механизм, используемый для управления одновременным доступом к общему ресурсу. Обратите внимание: использован термин "общий ресурс", а не "строка таблицы". Сервер Oracle действительно блокирует данные таблицы на уровне строк, но для обеспечения одновременного доступа к различным ресурсам он использует блокировки и на других уровнях. Например, при выполнении хранимой процедуры она блокируется в режиме, который позволяет другим сеансам ее выполнять, запрещая при этом изменять ее. Блокировки используются в базе данных для одновременного доступа к общим ресурсам и обеспечения при этом целостности и согласованности данных. В однопользовательской базе данных блокировки не нужны. У них по определению только один пользователь, изменяющий информацию. Однако если данные или структуры данных читаются и изменяются несколькими пользователями, важно иметь штатный механизм предотвращения одновременных изменений одного и того же фрагмента информации. Именно для этого и используется блокирование. Очень важно понять, что способов блокирования в базе данных столько же, сколько и СУБД. Богатый опыт работы с моделью блокирования конкретной реляционной СУБД еще не означает, что вы знаете о блокировании все. Я, например, прежде чем начать работать с Oracle, использовал другие СУБД, в частности Sybase и Informix. В упомянутых СУБД для управления одновременным доступом применялись механизмы блокирования, но между их реализацией в каждой из СУБД имеются глубокие и фундаментальные различия. Чтобы продемонстрировать это, я кратко опишу свой путь от разработчика приложений Sybase до пользователя Informix и, наконец, разработчика приложений Oracle. Это давняя история, и поклонники Sybase скажут, что теперь в этой СУБД реализовано блокирование на уровне строк, но способ его реализации коренным образом отличается от используемого в Oracle. Сравнивать их — все равно, что сравнивать яблоки и апельсины, — отличия принципиальны. Программируя для Sybase, я обычно не предусматривал возможность одновременной вставки данных в таблицу несколькими пользователями, поскольку в этой СУБД подобное происходило нечасто. В то время Sybase предлагала только блокирование на уровне страниц и, поскольку данные в некластеризованных таблицах обычно вставляются в последнюю страницу, одновременной вставки данных двумя пользователями никогда не происходило. Та же проблема возникает и при попытке одновременного обновления данных (поскольку UPDATE — это фактически DELETE, за которым следует INSERT). Возможно, именно поэтому СУБД Sybase по умолчанию фиксировала или откатывала изменения сразу после выполнения каждого оператора. Кроме того, что в большинстве случаев пользователи не могли одновременно изменять ту же таблицу, при изменении таблицы заблокированными оказывались и многие запросы к таблице. Если при выполнении запроса к таблице требовалась страница, заблокированная в ходе обновления, мне приходилось ждать (иногда очень долго). Механизм блокирования был настолько неудачным, что поддержка транзакций, продолжительней тысячной доли секунды, практически отсутствовала, и при попытке их выполнения казалось, что база данных зависла. Так я приобрел множество плохих привычек.

Блокирование и одновременный доступ Я понял, что транзакции — это "плохо", что фиксировать изменения надо быстро и никогда не удерживать блокировки данных. Одновременность работы достигалась ценой согласованности. Нужно было выбирать: правильный результат или быстро полученный результат. Я пришел к выводу, что достичь одновременно и того, и другого невозможно. Когда я стал работать с СУБД Informix, дела пошли лучше, но не намного. Если я не забывал создавать таблицу с блокированием на уровне строк, мне удавалось обеспечить одновременную вставку данных в эту таблицу двумя пользователями. К сожалению, одновременная работа давалась дорого. Блокировки строк в Informix были дорогостоящими, требуя дополнительного времени и памяти. Требовалось определенное время для установки и снятия блокировок, и каждая блокировка требовала места в оперативной памяти. Кроме того, общее количество доступных системе блокировок необходимо было рассчитать до запуска СУБД. Если этого количества оказывалось достаточно, вам, можно сказать, повезло. В результате таблицы обычно создавались с блокированием на уровне страниц и, как в случае с Sybase, любая блокировка строки или страницы останавливала запросы, которым были необходимы соответствующие данные. Это приучило меня фиксировать изменения как можно быстрее. Плохие привычки, приобретенные в процессе работы с СУБД Sybase, снова пригодились, и, более того, я начал рассматривать блокировки как очень дорогой ресурс, который надо экономить. Я понял, что иногда необходимо повышать уровень блокирования вручную со строчного до табличного, чтобы избежать излишнего количества блокировок и останова системы. Начав использовать СУБД Oracle, я и не пытался читать руководства, чтобы понять, как в ней работает механизм блокирования. Используя СУБД достаточно долго и считаясь своего рода экспертом в этой области (кроме Sybase и Informix, я работал с СУБД Ingress, DB2, Gupta SQLBase и с множеством других баз данных), я стал жертвой собственной самоуверенности. Казалось, что, поскольку я знаю, как все должно работать, именно так оно и будет работать. Я очень сильно ошибался. Насколько сильно я ошибался, стало понятно в ходе тестирования производительности. На раннем этапе развития упомянутых СУБД среди их производителей было принято организовывать "сравнительные тесты" для крупных проектов, чтобы клиент понял, какая СУБД позволит справиться с задачей быстрее, более простым способом и предоставит больше возможностей. Было устроено сравнение СУБД Informix, Sybase и Oracle. СУБД Oracle тестировалась первой. Технические специалисты компании прибыли к нам, прочитали спецификации теста и начали настройку среды. Первое, что я заметил, — это их намерение использовать для записи времени выполнения тестовых задач таблицу базы данных, хотя предполагалось несколько десятков сеансов, в каждом из которых требовалось часто вставлять и изменять данные в этой таблице. Более того, они собирались еще и читать данные из этой таблицы в ходе выполнения теста! Отозвав одного из специалистов в сторонку, я спросил, не сошли ли они все с ума: зачем намеренно вносить еще одну потенциальную точку конфликтов в систему? Не выстроятся ли все процессы, участвующие в тесте, в очередь при работе с этой таблицей? Не исказим ли мы существенно результаты теста, пытаясь читать из этой таблицы в то время как другие процессы интенсивно вносят в нее изменения? Зачем вы хотите добавить все эти дополнительные блокировки, которыми системе придется управлять? У меня были десятки вопросов в стиле "а зачем вообще так делать". До тех пор пока я не подключил Глава ся к Sybase или Informix и не показал, что происходит, когда два сеанса пытаются вставить данные в одну и ту же таблицу или кто-то пытается выполнить запрос к таблице, в которую другие сеансы вставляют строки (запрос возвращал ноль строк в секунду), специалистам Oracle казалось, что я несколько преувеличиваю проблемы. Разница между тем, как это делается в Oracle и в других СУБД, — феноменальна. Не стоит и говорить, что ни Informix, ни Sybase не пытались регистрировать результаты в базе данных в ходе тестирования. Они предпочли записывать результаты в обычные файлы операционной системы. Из этой истории можно сделать два вывода: все СУБД существенно различаются, и при разработке приложения необходимо подходить к каждой СУБД так, будто она для вас — первая. То, что принято делать в одной СУБД, может оказаться ненужным или просто не работать в другой. Работая с Oracle, вы поймете, что: • Транзакции — это хорошо, именно для их поддержки и создавались СУБД. • Откладывать фиксацию транзакции можно настолько, насколько необходимо. Не надо стремиться к коротким транзакциям с целью снижения нагрузки на систему, поскольку длинные или большие по объему изменений транзакции не нагружают систему. Правило следующее: фиксируйте транзакцию тогда, когда это необходимо, и не раньше. Размер транзакций диктуется только бизнес-логикой. • Удерживать блокировки данных можно столько, сколько необходимо. Для вас это — средство, а не проблема, которой надо избегать. Блокировки не являются ограниченным ресурсом. • Блокирование на уровне строк в Oracle не приводит к дополнительным расходам ресурсов. • Никогда не нужно повышать уровень блокировки (например, блокировать таблицу вместо блокирования строк), поскольку "так лучше для системы". В Oracle для системы так лучше не будет: ресурсы при этом не экономятся. • Одновременный доступ к данным и их согласованность не противоречат друг другу. Всегда можно получить результаты быстро, и при том корректные. В последующих разделах главы эти утверждения будут рассмотрены более подробно.

Проблемы блокирования Прежде чем описывать различные типы блокировок, используемые СУБД Oracle, имеет смысл разобраться с рядом проблем блокирования, многие из которых возникают из-за неправильно спроектированных приложений, некорректно использующих (или вообще не использующих) механизмы блокирования базы данных.

Потерянные изменения Потерянное изменение — классическая проблема баз данных. Если коротко, потерянное изменение возникает, когда происходят следующие события (в указанном порядке).

Блокирование и одновременный доступ 1. Пользователь 1 выбирает (запрашивает) строку данных. 2. Пользователь 2 выбирает ту же строку. 3. Пользователь 1 изменяет строку, обновляет базу данных и фиксирует изменение. 4. Пользователь 2 изменяет строку, обновляет базу данных и фиксирует изменение. Это называется потерянным изменением, поскольку все сделанные на шаге 3 изменения будут потеряны. Рассмотрим, например, окно редактирования информации о сотруднике, позволяющее изменить адрес, номер рабочего телефона и т.д. Само приложение — очень простое: небольшое окно поиска со списком сотрудников и возможность получить детальную информацию о каждом сотруднике. Проще некуда. Так что пишем приложение, не выполняющее никакого блокирования, — только простые операторы SELECT и UPDATE. Итак, пользователь (пользователь 1) переходит к окну редактирования, изменяет там адрес, щелкает на кнопке Save и получает подтверждение успешного обновления. Все отлично, кроме того, что, проверяя на следующий день эту запись, чтобы послать сотруднику налоговую декларацию, пользователь 1 увидит в ней старый адрес. Как это могло случиться? К сожалению, очень просто: другой пользователь (пользователь 2) запросил ту же запись за 5 минут до того, как к ней обратился пользователь 1, и у него на экране отображались старые данные. Пользователь 1 запросил данные, изменил их, получил подтверждение изменения и даже выполнил повторный запрос, чтобы увидеть эти изменения. Однако затем пользователь 2 изменил поле номера рабочего телефона и щелкнул на кнопке сохранения, не зная, что переписал старые данные поверх внесенных пользователем 1 изменений адреса! Так может случиться потому, что разработчик приложения предпочел обновлять сразу все столбцы, а не разбираться, какой именно столбец был изменен, и написал программу так, что при изменении одного из полей обновляются все. Обратите внимание, что для потери изменений пользователям 1 и 2 вовсе не обязательно работать с записью одновременно. Нужно, чтобы они работали с ней примерно в одно и то же время. Эта проблема баз данных проявляется постоянно, когда разработчики графических интерфейсов, не имеющие достаточного опыта работы с базами данных, получают задание создать приложение для базы данных. Они получают общее представление об операторах SELECT, INSERT, UPDATE и DELETE и начинают писать программы. Когда получившееся в результате приложение ведет себя, как описано выше, пользователи полностью перестают ему доверять, особенно потому что подобные результаты кажутся случайными и спорадическими и абсолютно невоспроизводимы в управляемой среде тестирования (что приводит разработчика к мысли, что это, возможно, ошибка пользователя). Многие инструментальные средства, например Oracle Forms, автоматически защищают разработчиков от таких ситуаций, проверяя, не изменилась ли запись с момента запроса и заблокирована ли перед началом изменений. Но другие средства разработки (и обычные программы на языке VB или Java) такой защиты не обеспечивают. Как средства разработки, обеспечивающие защиту (за кадром), так и разработчики, использую Глава щие другие средства (явно), должны применять один из двух описанных ниже методов блокирования.

Пессимистическое блокирование Этот метод блокирования должен использоваться непосредственно перед изменением значения на экране, например, когда пользователь выбирает определенную строку с целью изменения (допустим, щелкая на кнопке в окне). Итак, пользователь запрашивает данные без блокирования:

scott@TKYTE816> SELECT EMPNO, ENAME, SAL FROM EMP WHERE DEPTNO = 10;

EMPNO ENAME 7782 CLARK 7839 KING 7934 MILLER SAL 2450 5000 В какой-то момент пользователь выбирает строку для потенциального изменения. Пусть в этом случае он выбрал строку, соответствующую сотруднику MILLER. Наше приложение в этот момент (перед выполнением изменений на экране) выполняет следующую команду:

scott@TKYTE816> SELECT EMPNO, ENAME, SAL 2 FROM EMP 3 WHERE EMPNO = :EMPNO 4 AND ENAME = :ENAME 5 AND SAL = :SAL 6 FOR UPDATE NOWAIT 7 / EMPNO ENAME 7934 MILLER SAL Приложение передает значения для связываемых переменных в соответствии с данными на экране (в нашем случае — 7934, MILLER и 1300) и повторно запрашивает ту же самую строку из базы данных, но в этот раз блокирует ее изменения другими сеансами. Вот почему такой подход называется пессимистическим блокированием. Мы блокируем строку перед попыткой изменения, поскольку сомневается, что она останется неизменной. Поскольку все таблицы имеют первичный ключ (приведенный выше оператор SELECT выберет не более одной строки, поскольку критерий выбора включает первичный ключ EMPNO), а первичные ключи должны быть неизменны, при выполнении этого оператора возможен один из трех результатов. • Если данные не изменились, мы получим ту же строку сотрудника MILLER, на этот раз заблокированную от изменения (но не чтения) другими сеансами. • Если другой сеанс находится в процессе изменения данной строки, мы получим сообщение об ошибке ORA-00054 Resource Busy (ресурс занят). Наш сеанс заб Блокирование и одновременный доступ локирован и мы должны ждать, пока другой сеанс не завершит изменения строки. • Если за период между выборкой данных и попыткой их изменить другой сеанс уже изменил соответствующую строку, мы получим в результате ноль строк. Данные на экране не обновятся. Приложение должно повторно запросить и заблокировать данные, прежде чем разрешить пользователю изменять их, чтобы предотвратить описанную выше ситуацию с потерей изменений. В этом случае, если используется пессимистическое блокирование, когда пользователь 2 пытается изменить поле номера телефона, приложение "поймет", что изменилось поле адреса, и повторно запросит данные. Поэтому пользователь 2 никогда не перезапишет старые данные поверх изменений, внесенных в это поле пользователем 1. После успешного блокирования строки приложение выполняет требуемые изменения и фиксирует их:

scott@TKYTE816> UPDATE EMP 2 SET ENAME = :ENAME, SAL = :SAL 3 WHERE EMPNO =.EMPNO;

1 row updated. scott@TKYTE816> commit;

Commit complete.

Теперь мы абсолютно безопасно изменили соответствующую строку. Мы не могли стереть изменения, сделанные в другом сеансе, поскольку проверили, что данные не изменились с момента первоначального чтения до момента блокирования.

Оптимистическое блокирование Второй метод, который называют оптимистическим блокированием, состоит в том, чтобы сохранять старое и новое значения в приложении и использовать их при изменении следующим образом:

Update Set Where And table column1 = :new_column1, column2 = :new_column2, column1 = :old_column1 column2 = :old_column2....

Здесь мы оптимистически надеемся, что данные не изменились. Если в результате изменена одна строка, значит, нам повезло: данные не изменились с момента считывания. Если изменено ноль строк, мы проиграли — кто-то уже изменил данные и необходимо решить, что делать, чтобы это изменение не потерять. Должны ли мы заставлять пользователя повторно выполнять транзакцию после запроса новых значений для строки (сбивая его с толку, поскольку снова может получиться так, что изменяемая им строка обновлена другим сеансом)? Стоит ли попытаться совместить изменения, разрешая конфликты двух изменений на основе бизнес-правил (что требует написания большого объема кода)? Конечно, для отключившихся пользователей последний вариант — единственно возможный.

Глава Стоит заметить, что и в этом случае тоже можно использовать оператор SELECT FOR UPDATE NOWAIT. Представленный выше оператор UPDATE позволяет избежать потери изменений, но может приводить к блокированию, "зависая" в ожидании завершения изменения строки другим сеансом. Если все приложения используют оптимистическое блокирование, то применение простых операторов UPDATE вполне допустимо, поскольку строки блокируются на очень короткое время выполнения и фиксации изменений. Однако если некоторые приложения используют пессимистическое блокирование, удерживая блокировки строк достаточно долго, имеет смысл выполнять оператор SELECT FOR UPDATE NOWAIT непосредственно перед оператором UPDATE, чтобы избежать блокирования другим сеансом. Итак, какой же метод лучше? По моему опыту, пессимистическое блокирование очень хорошо работает в Oracle (но вряд ли так же хорошо подходит для других СУБД) и имеет много преимуществ по сравнению с оптимистическим. При использовании пессимистического блокирования пользователь может быть уверен, что изменяемые им на экране данные сейчас ему "принадлежат" — он получил запись в свое распоряжение, и никто другой не может ее изменять. Можно возразить, что, блокируя строку до изменения, вы лишаете к ней доступа других пользователей и, тем самым, существенно снижаете масштабируемость приложения. Но обновлять строку в каждый момент времени сможет только один пользователь (если мы не хотим потерять изменения). Если сначала заблокировать строку, а затем изменять ее, пользователю будет удобнее работать. Если же пытаться изменить, не заблокировав заранее, пользователь может напрасно потерять время и силы на изменения, чтобы в конечном итоге получить сообщение: "Извините, данные изменились, попробуйте еще раз". Чтобы ограничить время блокирования строки перед изменением, можно снимать блокировку в приложении, если пользователь занялся чем-то другим и некоторое время не использует строку, или использовать профили ресурсов (Resource Profiles) в базе данных для отключения простаивающих сеансов. Более того, блокирование строки в Oracle не мешает ее читать, как в других СУБД;

блокирование строки не мешает обычной работе с базой данных. Все это исключительно благодаря соответствующей реализации механизмов одновременного доступа и блокирования в Oracle. В других СУБД верно как раз обратное. Если попытаться использовать в них пессимистическое блокирование, ни одно приложение не будет работать. Тот факт, что в этих СУБД блокирование строки не дает возможности выполнять к ней запросы, не позволяет даже рассматривать подобный подход. Поэтому иногда приходится "забывать" правила, выработанные в процессе работе с одной СУБД, чтобы успешно разрабатывать приложения для другой.

Блокирование Блокирование происходит, когда один сеанс удерживает ресурс, запрашиваемый другим сеансом. В результате запрашивающий сеанс будет заблокирован — он "повиснет" до тех пор, пока удерживающий сеанс не завершит работу с ресурсом. Блокирования практически всегда можно избежать. Если оказывается, что интерактивное приложение заблокировано, проблема, скорее всего, связана с ошибкой, подобной описанному выше потерянному изменению (логика работы приложения ошибочна, что и приводит к блокированию).

Блокирование и одновременный доступ Блокирование в базе данных выполняют четыре основных оператора ЯМД: INSERT, UPDATE, DELETE и SELECT FOR UPDATE. Решение проблемы в последнем случае тривиально: добавьте конструкцию NOWAIT, и оператор SELECT FOR UPDATE больше не будет заблокирован. Вместо этого приложение должно сообщать пользователю, что строка уже заблокирована. Интерес представляют остальные три оператора ЯМД. Мы рассмотрим каждый из них и увидим, почему они не должны блокировать друг друга и как это исправить, если блокирование все-таки происходит.

Заблокированныевставки Единственный случай блокирования операторами INSERT друг друга, — когда имеется таблица с первичным ключом или ограничением уникальности и два сеанса одновременно пытаются вставить строку с одним и тем же значением. Один из сеансов будет заблокирован, пока другой не зафиксирует изменение (в этом случае заблокированный сеанс получит сообщение об ошибке, связанной с дублированием значения) или не откатит его (в этом случае операция заблокированного сеанса будет выполнена успешно). Такое обычно происходит с приложениями, позволяющими генерировать пользователю первичные ключи или значения уникальных столбцов. Этой проблемы проще всего избежать за счет использования при генерации первичных ключей последовательностей Oracle — средства генерации уникальных ключей, обеспечивающего максимальный параллелизм в многопользовательской среде. Если нельзя использовать последовательность, можно применить метод, описанный в приложении А при рассмотрении пакета DBMS_LOCK. Там я демонстрирую, как решить эту проблему, прибегнув к явному блокированию вручную.

Заблокированные изменения и удаления В интерактивном приложении, которое запрашивает данные из базы, позволяет пользователю манипулировать ими, а затем возвращает их в базу данных, заблокированные операторы UPDATE или DELETE показывают, что в коде может быть проблема потерянного изменения. Вы пытаетесь изменить с помощью UPDATE строку, которую уже изменяет другой пользователь, другими словами, которая уже кем-то заблокирована. Этого блокирования можно избежать с помощью запроса SELECT FOR UPDATE NOWAIT, позволяющего: • проверить, не изменились ли данные с момента их прочтения (для предотвращения потерянного изменения);

• заблокировать строку (предотвращая ее блокирование другим оператором изменения или удаления). Как уже упоминалось, это можно сделать независимо от принятого подхода — как при пессимистическом, так и при оптимистическом блокировании можно использовать оператор SELECT FOR UPDATE NOWAIT для проверки того, что строка не изменилась. При пессимистическом блокировании этот оператор выполняется в тот момент, когда пользователь выражает намерение изменять данные. При оптимистическом блокировании этот оператор выполняется непосредственно перед изменением данных в базе. Это не только решает проблемы блокирования в приложении, но и обеспечивает целостность данных.

Глава Взаимные блокировки Взаимные блокировки возникают, когда два сеанса удерживают ресурсы, необходимые другому сеансу. Взаимную блокировку легко продемонстрировать на примере базы данных с двумя таблицами, А и В, в каждой из которых по одной строке. Для этого необходимо начать два сеанса (скажем, два сеанса SQL*Plus) и в сеансе А изменить таблицу А. В сеансе Б надо изменить таблицу В. Теперь, если попытаться изменить таблицу А в сеансе Б, он окажется заблокированным, поскольку соответствующая строка уже заблокирована сеансом А. Это еще не взаимная блокировка — сеанс просто заблокирован. Взаимная блокировка еще не возникла, поскольку есть шанс, что сеанс А зафиксирует или откатит транзакцию и после этого сеанс Б продолжит работу. Если мы вернемся в сеанс А и попытаемся изменить таблицу В, то вызовем взаимную блокировку. Один из сеансов будет выбран сервером в качестве "жертвы", и в нем произойдет откат оператора. Например, может быть отменена попытка сеанса Б изменить таблицу А с выдачей сообщения об ошибке следующего вида:

update a set x = х+1 * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource Попытка сеанса А изменить таблицу В по-прежнему блокируется — сервер Oracle не откатывает всю транзакцию. Откатывается только один из операторов, ставших причиной возникновения взаимной блокировки. Сеанс Б по-прежнему удерживает блокировку строки в таблице В, а сеанс А терпеливо ждет, пока эта строка станет доступной. Получив сообщение о взаимной блокировке, сеанс Б должен решить, фиксировать ли уже выполненные изменения в таблице В, откатить ли их или продолжить работу и зафиксировать транзакцию позднее. Как только этот сеанс зафиксирует или откатит транзакцию, другие заблокированные сеансы смогут продолжить работу. Для сервера Oracle взаимная блокировка — настолько редкий, необычный случай, что при каждом ее возникновении создается трассировочный файл. Содержимое трассировочного файла примерно таково:

*** 2001-02-23 14:03:35.041 *** SESSION ID:(8.82) 2001-02-23 14:03:35.001 DEADLOCK DETECTED Current SQL statement for this session: update a set x = x+1 The following deadlock is not an ORACLE error. It is a deadlock due to user error in the design of an application or from issuing incorrect ad-hoc SQL. The following...

Очевидно, сервер Oracle воспринимает взаимные блокировки как ошибки приложения, и в большинстве случаев это справедливо. В отличие от многих других реляционных СУБД, взаимные блокировки настолько редко происходят в Oracle, что вполне можно игнорировать их существование. Обычно взаимную блокировку необходимо создавать искусственно.

Блокирование и одновременный доступ Как свидетельствует опыт, основной причиной возникновения взаимных блокировок в базах данных Oracle являются неиндексированные внешние ключи. При изменении главной таблицы сервер Oracle полностью блокирует подчиненную таблицу в двух случаях: • при изменении первичного ключа в главной таблице (что бывает крайне редко, если следовать принятому в реляционных базах данных правилу неизменности первичных ключей) подчиненная таблица блокируется при отсутствии индекса по внешнему ключу;

• при удалении строки в главной таблице подчиненная таблица также полностью блокируется (при отсутствии индекса по внешнему ключу). Чтобы продемонстрировать первый случай, создадим пару таблиц следующим образом:

tkyte@TKYTE816> create table p (x int primary key) ;

Table created. tkyte@TKYTE816> create table с (у references p) ;

Table created. tkyte@TKYTE816> insert into p values (1) ;

tkyte@TKYTE816> insert into p values (2) ;

tkyte@TKYTE816> commit;

А затем выполним:

tkyte@TKYTE816> update p set x = 3 where x = 1;

1 row updated.

В результате сеанс заблокировал таблицу С, и никакой другой сеанс не может удалять, вставлять или изменять в ней строки. Повторю еще раз: изменение первичного ключа не приветствуется при работе с реляционными базами данных, так что обычно подобная проблема не возникает. Зато проблема изменения первичного ключа становится актуальной в случае использования средств автоматической генерации SQL-операторов, которые обновляют значения всех столбцов, независимо от того, изменил ли значение пользователь. Например, при создании в Oracle Forms стандартной формы для просмотра и редактирования таблицы по умолчанию генерируется оператор UPDATE, изменяющий все просматриваемые столбцы таблицы. Если создать стандартную форму для таблицы DEPT и включить в нее все три поля, Oracle Forms будет выполнять следующую команду при изменении любого из столбцов таблицы DEPT:

update dept set deptno=:1, dname=:2,loc=:3 where rowid=: В этом случае, если таблица EMP имеет внешний ключ, ссылающийся на DEPT, и по столбцу DEPTNO в таблице EMP нет индекса, вся таблица EMP будет заблокирована после изменения таблицы DEPT. За этим надо внимательно следить при использовании любого средства, автоматически генерирующего SQL-операторы. Хотя значение первичного ключа не изменилось, подчиненная таблица EMP после выполнения приведенного выше SQL-оператора будет заблокирована. В случае Oracle Forms необ Глава ходимо установить значение Yes для свойства таблицы update changed columns only (обновлять только измененные столбцы). В результате Oracle Forms будет генерировать оператор UPDATE, включающий только изменившиеся столбцы (но не первичный ключ). Проблемы, связанные с удалением строки в главной таблице, возникают намного чаще. Если удаляется строка в таблице Р, то вся подчиненная таблица С оказывается заблокированной, что не позволяет выполнять другие изменения таблицы С в течение всей транзакции (предполагается, конечно, что ни один другой сеанс не изменял таблицу С в момент удаления, иначе оператору удаления пришлось бы ожидать). Именно так возникают проблемы блокирования, в том числе взаимного. Блокирование таблицы С ограничивает возможность одновременной работы с базой данных, — любые изменения в ней становятся невозможными. Кроме того, увеличивается вероятность взаимного блокирования, поскольку сеанс в течение транзакции "владеет" слишком большим объемом данных. Вероятность того, что другой сеанс окажется заблокированным при попытке изменения таблицы С, теперь намного больше. Вследствие этого блокируется множество сеансов, удерживающих определенные ресурсы. Если какой-либо из заблокированных сеансов удерживает ресурс, необходимый исходному, удалившему строку сеансу, возникает взаимная блокировка. Причина взаимной блокировки в данном случае — блокирование исходным сеансом намного большего количества строк, чем реально необходимо. Если кто-то жалуется на взаимные блокировки в базе данных, я предлагаю выполнить сценарий, который находит неиндексированные внешние ключи, и в девяноста девяти процентах случаев мы обнаруживаем таблицу, вызвавшую проблемы. После индексирования соответствующего внешнего ключа взаимные блокировки и множество других конфликтов при доступе исчезают навсегда. Вот пример того, как автоматически находить неиндексированные внешние ключи:

tkyte@TKYTE816> column columns format a30 word_wrapped tkyte@TKYTE816> column tablename format a15 word_wrapped tkyte@TKYTE816> column constraint_name format a15 word_wrapped tkyte@TKYTE816> select table_name, constraint_name, 2 enamel || nvl2(cname2,','||cname2,null) || 3 nvl2(cnameЗ,','||cname,null) || nvl2(cname4,','||cname4,null) || 4 nvl2(cname5,','||cname5,null) || nvl2(cname6,','||cname6,null) || 5 nvl2(cname7,','||cname7,null) || nvl2(cname8,','||cname8,null) 6 columns 7 from (select b.table_name, 8 b.constraint_name, 9 max(decode(position, 1, colunm_name, null)) enamel, 10 max(decode(position, 2, column_name, null)) cname2, 11 max(decode(position, 3, column_name, null)) cnameЗ, 12 max(decode(position, 4, column_name, null)) cname4, 13 max(decode(position, 5, colunm_name, null)) cname5, 14 max(decode(position, 6, column_name, null)) cname6, 15 max(decode(position, 7, column_name, null)) cname7, 16 max(decode(position, 8, column_name, null)) cname8, 17 count(*) col_cnt 18 from (select substr(table_name,1,30) table_name, 19 substr(constraint_name,1,30) constraint_name, Блокирование и одновременный доступ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 substr(column_naine,1,30) column_name, position from user_cons_columns) a, user_constraints b where a.constraint_name = b.constraint_name and b.constraint_type = 'R' group by b.table_name, b.constraint_name ) cons where col_cnt > ALL (select count(*) from user_ind_columns i where i.table_name = cons.table_name and i.column_name in (cname1, cname2, cname3, cname4, cname5, cname6, cname7, cname8) and i.column_position <= cons.col_cnt group by i.index_name ) / CONSTRAINT_NAME SYS_C004710 COLUMNS Y TABLE_NAME С Этот сценарий работает с ограничениями внешнего ключа, включающими до 8 столбцов (если у вас используется больше столбцов, имеет смысл пересмотреть структуру). Вначале создается подставляемое представление (inline view), названное в данном запросе CONS. Это представление транспонирует имена столбцов, входящих в ограничение, представляя их в виде столбцов. В результате для каждого ограничения получается одна строка, включающая до 8 имен столбцов в ограничении. Кроме того, в строке имеется столбец COL_CNT, содержащий количество столбцов в самом ограничении внешнего ключа. Для каждой строки, выбранной из представления CONS, мы выполняем коррелированный подзапрос, проверяющий все индексы по обрабатываемой таблице. Он считает столбцы в этом индексе, соответствующие столбцам в ограничении внешнего ключа, а затем группирует результаты по имени индекса. Таким образом, он генерирует набор чисел, каждое из которых представляет собой количество совпавших столбцов в одном из индексов таблицы. Если исходное значение в столбце COL_CNT больше, чем все эти числа, значит, нет ни одного индекса таблицы, обеспечивающего выполнение ограничения внешнего ключа. Если значение в столбце COL_CNT меньше некоторых чисел, значит, имеется хотя бы один индекс, обеспечивающий выполнение ограничения. Обратите внимание на использование функции NVL2 (впервые появилась в Oracle 8.15), которая формирует из имен столбцов список (через запятую). Эта функция принимает три аргумента: А, В и С. Если аргумент А — не пустой, она возвращает аргумент В, в противном случае — аргумент С. В запросе предполагается, что владелец ограничения является также владельцем таблицы и индекса. Если таблица проиндексирована другим пользователем или принадлежит другому пользователю, сценарий будет работать некорректно (но подобные случаи встречаются редко). Итак, представленный выше сценарий показывает, что таблица С имеет внешний ключ по столбцу Y, но по этому столбцу нет индекса. Проиндексировав столбец Y, мы • Глава можем полностью устранить проблему блокирования. Помимо блокирования таблицы неиндексированный внешний ключ может вызывать проблемы в следующих случаях: Если установлено ограничение ON DELETE CASCADE и подчиненная таблица не проиндексирована. Например, таблица EMP является подчиненной по отношению к DEPT. Оператор DELETE DEPTNO = 10 должен вызвать каскадное удаление (CASCADE) соответствующих строк в таблице EMP. Если столбец DEPTNO в таблице ЕМР не проиндексирован, придется выполнять полный просмотр (full table scan) таблицы ЕМР. Полный просмотр обычно нежелателен, поскольку при удалении большого количества строк из главной таблицы приводит к большим расходам времени и ресурсов.

• При выполнении запроса из главной таблицы в подчиненную. Вернемся опять к примеру с таблицами ЕМР и DEPT. Очень часто таблица ЕМР запрашивается с учетом DEPTNO. Если постоянно для генерации отчета выполняется, например, следующий запрос, то отсутствие индекса замедлит работу:

s e l e c t * from dept, emp where emp.deptno = dept.daptno and dept.deptno = :X;

Итак, когда индекс по внешнему ключу не нужен? В общем случае, когда выполнены следующие условия: • не удаляются строки из главной таблицы;

• не изменяется значение уникального/первичного ключа главной таблицы (следите за непреднамеренным обновлением первичного ключа используемыми инструментальными средствами!);

• не выполняется соединение главной и подчиненной таблиц (как в случае с DEPT и ЕМР). Если соблюдены все три условия, индекс можно не создавать: он не нужен. Если выполняется одно из перечисленных действий, помните о последствиях. Это один из немногих случаев, когда сервер Oracle "избыточно" блокирует данные.

Эскалация блокирования Когда происходит эскалация блокирования, система увеличивает размер блокируемых объектов. Примером может служить блокирование системой всей таблицы вместо 100 отдельных ее строк. В результате одной блокировкой удерживается намного больше данных, чем перед эскалацией. Эскалация блокирования часто используется в СУБД, когда требуется избежать лишнего расходования ресурсов. В СУБД Oracle никогда не применяется эскалация блокирования, однако выполняется преобразование блокировок (lock conversion) или распространение блокировок (lock promotion). Эти термины часто путают с эскалацией блокирования. Термины "преобразование блокировок" и "распространение блокировок" — синонимы. В контексте Oracle обычно говорят о преобразовании.

Блокирование и одновременный доступ Берется блокировка самого низкого из возможных уровней (наименее ограничивающая блокировка) и преобразуется к более высокому (ограничивающему) уровню. Например, при выборе строки из таблицы с конструкцией FOR UPDATE будет создано две блокировки. Одна из них устанавливается на выбранную строку (или строки);

это — исключительная блокировка: ни один сеанс уже не сможет заблокировать соответствующие строки в исключительном режиме. Другая блокировка, ROW SHARE TABLE (совместное блокирование строк таблицы), устанавливается на соответствующую таблицу. Это предотвратит исключительную блокировку таблицы другими сеансами и, следовательно, возможность изменения, например, структуры таблицы. Все остальные операторы смогут работать с таблицей. Другой сеанс может даже сделать таблицу доступной только для чтения с помощью оператора LOCK TABLE X IN SHARE MODE, предотвратив тем самым ее изменение. Однако этот другой сеанс не должен иметь права предотвращать изменения, которые уже происходят. Поэтому, как только будет выполнена команда фактического изменения строки, сервер Oracle преобразует блокировку ROW SHARE TABLE в более ограничивающую блокировку ROW EXCLUSIVE TABLE, и изменение будет выполнено. Такое преобразование блокировок происходит само собой независимо от приложений. Эскалация блокировок — не преимущество базы данных. Это — нежелательное свойство. Тот факт, что СУБД поддерживает эскалацию блокировок, означает, что ее механизм блокирования расходует слишком много ресурсов, что особенно ощутимо при управлении сотнями блокировок. В СУБД Oracle расходы ресурсов в случае одной или миллиона блокировок одинаковы, — ресурсы просто не расходуются.

Типы блокировок Ниже перечислены пять основных классов блокировок в Oracle. Первые три — общие (используются во всех базах данных Oracle), а две остальные — только в OPS (Oracle Parallel Server — параллельный сервер). Специфические блокировки OPS мы рассмотрим лишь вкратце, зато общие блокировки — очень подробно. • Блокировки ЯМД (DML locks). ЯМД означает язык манипулирования данными (Data Manipulation Language), т.е. операторы SELECT, INSERT, UPDATE и DELETE. К блокировкам ЯМД относятся, например, блокировки строки данных или блокировка на уровне таблицы, затрагивающая все строки таблицы. • Блокировки ЯОД (DDL locks). ЯОД означает язык определения данных (Data Definition Language), т.е. операторы CREATE, ALTER и так далее. Блокировки ЯОД защищают определения структур объектов. • Внутренние блокировки (internal locks) и защелки (latches). Это блокировки, используемые сервером Oracle для защиты своих внутренних структур данных. Например, разбирая запрос и генерируя оптимизированный план его выполнения, сервер Oracle блокирует с помощью защелки библиотечный кэш, чтобы поместить в него этот план для использования другими сеансами. Защелка — это простое низкоуровневое средство обеспечения последовательности обращений, используемое сервером Oracle, и по функциям аналогичное блокировке.

Глава • Распределенные блокировки (distributed locks). Эти блокировки используются сервером OPS для согласования ресурсов машин, входящих в кластер. Распределенные блокировки устанавливаются экземплярами баз данных, а не отдельными транзакциями. • Блокировки параллельного управления кэшем (PCM — Parallel Cache Management Locks). Такие блокировки защищают блоки данных в кэше при использовании их несколькими экземплярами. Теперь мы подробно рассмотрим отдельные типы блокировок в каждом из этих общих классов, в том числе последствия их использования. Существуют и другие типы блокировок, однако они выходят за рамки тем, рассматриваемых в этой книге. Мы рассмотрим блокировки наиболее часто используемые и долго удерживаемые. Блокировки других типов обычно устанавливаются лишь на очень непродолжительное время.

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

ТХ - блокировки транзакций Блокировка ТХ устанавливается, когда транзакция инициирует первое изменение, и удерживается до тех пор, пока транзакция не выполнит оператор COMMIT или ROLLBACK. Она используется как механизм организации очереди для сеансов, ожидающих завершения транзакции. Каждая измененная или выбранная с помощью SELECT FOR UPDATE строка будет "указывать" на соответствующую блокировку ТХ. Казалось бы, это должно повлечь большие расходы ресурсов, но на самом деле этого не происходит. Чтобы понять, почему, необходимо разобраться, где "живут" блокировки и как сервер ими управляет. В Oracle блокировки хранятся как атрибут данных (см. описание формата блока Oracle в главе 2). У сервера Oracle нет традиционного диспетчера блокировок, поддерживающего длинный список со всеми строками, заблокированными в системе. Другие СУБД делают именно так, поскольку для них блокировки — дорогостоящий ресурс, за использованием которого надо следить. Чем больше блокировок, тем сложнее ими управлять, отсюда и заботы о том, "не слишком ли много" блокировок используется в системе. Если бы сервер Oracle имел традиционный диспетчер блокировок, при блокировании строки нужно было бы выполнить примерно такую последовательность действий. 1. Найти адрес строки, которую необходимо заблокировать. 2. Подключиться к диспетчеру блокировок (необходимо выполнять по очереди, поскольку используются общие структуры в памяти.) 3. Заблокировать список. 4. Просмотреть список, чтобы проверить, не заблокирована ли эта строка другим сеансом.

Блокирование и одновременный доступ 5. Создать в списке новую запись, фиксирующую факт блокирования строки. 6. Разблокировать список. Теперь, когда строка заблокирована, ее можно изменять. При последующей фиксации изменений необходимо: 7. Снова подключиться к диспетчеру. 8. Заблокировать список блокировок. 9. Найти в списке и снять все установленные блокировки. 10. Разблокировать список. Как видите, чем больше установлено блокировок, тем больше времени потребуется для изменения данных и фиксации этих изменений. Поэтому сервер Oracle поступает примерно так: 1. Находит адрес строки, которую необходимо заблокировать. 2. Переходит на эту строку. 3. Блокирует ее (ожидая снятия блокировки, если она уже заблокирована и при этом не используется опция NOWAIT). Вот и все. Поскольку блокировка хранится как атрибут данных, серверу Oracle не нужен традиционный диспетчер блокировок. Транзакция просто переходит к соответствующим данным и блокирует их (если они еще не заблокированы). Иногда при обращении данные кажутся заблокированными, хотя фактически они уже не заблокированы. При блокировании строки данных в Oracle с блоком данных связывается идентификатор транзакции, причем остается там после снятия блокировки. Этот идентификатор уникален для нашей транзакции и задает номер сегмента отката, слот и номер изменения (sequence number). Оставляя его в блоке, содержащем измененную строку, мы как бы говорим другим сеансам, что "эти данные принадлежат нам" (не все данные в блоке, только одна строка, которую мы меняем). Когда другой сеанс обращается к блоку, он "видит" идентификатор блокировки и, "зная", что он представляет транзакцию, определяет, активна ли еще транзакция, установившая блокировку. Если транзакция уже закончена, сеанс может получить данные "в собственность". Если же транзакция активна, сеанс "попросит" систему уведомить его о завершении транзакции. Таким образом, имеется механизм организации очереди: сеанс, нуждающийся в блокировке, будет помещен в очередь в ожидании завершения транзакции, после чего получит возможность работать с данными. Вот небольшой пример, демонстрирующий, как это происходит. Чтобы увидеть, как работает сервер, используют три таблицы* динамической производительности V$: • V$TRANSACTION, содержащую запись о каждой активной транзакции;

• V$SESSION, которая показывает действующие сеансы;

• V$LOCK, содержащую записи обо всех блокировках, с указанием сеансов, ожидающих их снятия.

*На самом деле это представления, но мы вслед за автором будем называть их таблицами. Прим. иаучн. ред.

Глава Во-первых, начнем транзакцию:

tkyte@TKYTE816> update dept s e t deptno - deptno +10;

4 rows updated.

Теперь рассмотрим состояние системы в этот момент:

tkyte@TKYTE816> select username, 2 v$lock.sid, 3 trunc(id1/power(2,16)) rbs, 4 bitand(id1,power(2,16)-l)+0 slot, 5 id2 seq, 6 lmode, 7 request 8 from v$lock, v$session 9 where v$lock.type = 'TX' 10 and v$lock.sid = v$session.sid 11 and v$session. username = USER 12 / USERNAME TKYTE SID 8 RBS 2 SLOT 46 SEQ 160 LMODE 6 REQUEST tkyte@TKYTE816> select XIDUSM, XIDSLOT, XIDSQN 2 from v$transaction 3/ XIDUSN 2 XIDSLOT 46 XIDSQN Обратите внимание на следующее: • Значение столбца LMODE в таблице V$LOCK равно 6, а значение столбца REQUEST — 0. Обратившись к описанию таблицы V$LOCK в руководстве Oracle Server Reference Manual, можно узнать, что LMODE=6 означает исключительную блокировку. Значение 0 в столбце REQUEST означает, что мы не запрашиваем блокировку, а удерживаем ее. • В таблице только одна строка. Таблица V$LOCK содержит не сами блокировки, а очередь на них. Можно ожидать найти в таблице V$LOCK четыре строки, поскольку мы заблокировали именно такое количество строк. Надо, однако, помнить, что сервер Oracle не хранит список заблокированных строк. Чтобы узнать, не заблокирована ли строка, надо ее прочитать. • Возьмем значения столбцов ID1 и ID2 и выполним с ними определенную манипуляцию. Серверу Oracle необходимо хранить три 16-битовых числа, но для этого имеется только два столбца. Поэтому первый столбец, ID1, хранит два из этих чисел. Разделив значение столбца на 2^16 с использованием конструкции trunc(id1/power(2,16)) rbs и замаскировав старшие биты с помощью bitand(id1,power(2,16)-1)+0 slot, получаем два числа, хранящиеся в виде одного значения.

Блокирование и одновременный доступ • Значения RBS, SLOT и SEQ сопоставляются с информацией в таблице V$TRANSACTION. Это — идентификатор нашей транзакции. Теперь откроем другой сеанс от имени того же пользователя, поменяем ряд строк в таблице ЕМР, а затем попытаемся изменить таблицу DEPT:

tkyte@TKYTE816> update emp set ename = upper(ename);

14 rows updated. tkyte@TKYTE816> update dept set deptno = deptno-10;

Сеанс окажется заблокированным. Выполнив запросы к таблицам V$ снова, можно увидеть следующее:

tkyte@TKYTE816> select username, 2 v$lock.sid, 3 trunc(idl/power(2,16)) 4 bitand(id1,power(2,16)-1)+0 5 id2 seq, 6 lmode, 7 request 8 from v$lock, v$session 9 where v$lock.type = 'TX' 10 and v$lock.sid = v$session.aid 11 and vSsession.username = USER 12 / USERNAME TKYTE TKYTE TKYTE SID 8 9 9 RBS 2 2 3 SLOT 46 46 rba, slot, SEQ 160 160 LMODE 6 0 REQUEST 0 6 tkyte@TKYTE816> select XIDUSN, XIDSLOT, XIDSQN 2 from v$transaction 3/ XIDUSN 3 2 XIDSLOT 82 46 XIDSQN 163 Итак, понятно, что начата новая транзакция с идентификатором (3,82,163). Наш сеанс, SID=9, теперь имеет две строки в таблице V$LOCK. Одна строка представляет удерживаемые им блокировки (та, где LMODE=6). Там же есть строка, содержащая значение 6 в столбце REQUEST. Это запрос исключительной блокировки. Интересно отметить, что значения RBS/SLOT/SEQ в этой строке запроса представляют идентификатор транзакции сеанса, удерживающего блокировку. Транзакция сеанса со значением SID=8 блокирует транзакцию сеанса со значением SID=9. Это можно явно увидеть, выполнив соединение таблицы V$LOCK с самой собой:

Глава tkyte@TKYTE816> select (select username from v$session where sid=a.sid) blocker, 2 a.sid, 3 ' is blocking ', 4 (select username from v$session where sid=b.sid) blоскее, 5 b.sid 6 from v$lock a, v$lock b 7 where a.block = 1 8 and b.request > 0 9 and a.idl = b.idl 10 and a.id2 = b.id2 11 / BLOCKER TKYTE SID 8 'ISBLOCKING' is blocking BLOCKEE TKYTE SID Теперь, если зафиксировать исходную транзакцию в сеансе SID=8 и выполнить запрос повторно, окажется, что строка запроса блокировки исчезла:

tkyte@TKYTE816> select username, 2 v$lock.sid, 3 trunc(id1/power(2,16)) 4 bitand(id1,power(2,16)-1)+0 5 id2 seq, 6 lmode, 7 request, block 8 from v$lock, v$session 9 where v$lock.type = 'TX' 10 and v$lock.aid = v$session.sid 11 and v$session. username = USER 12 / USERNAME SID RBS SLOT rbs, slot, SEQ LMODE REQUEST TKYTE 9 3 82 163 tkyte@TKYTE816> select XIDUSN, XIDSLOT, XIDSQN 2 from v$transaction 3/ XIDUSN 3 XIDSLOT 82 XIDSQN Строка запроса блокировки, использовавшаяся для организации очереди, исчезает одновременно со снятием блокировки другим сеансом. Сервер может "разбудить" заблокированные сеансы в момент завершения транзакции. Можно бесконечно "улучшать" представление данных с помощью различных графических средств, но важнее знать, в какие таблицы смотреть. Напоследок необходимо разобраться еще в одном: как хранится информация о блокировках и транзакциях в самих данных. Она является частью служебной информации блока. В главе 2 было описано, что базовый формат предусматривает в начале блока Блокирование и одновременный доступ "системное" пространство для хранения таблицы транзакций для этого блока. Такая таблица транзакций включает записи для каждой "реальной" транзакции, заблокировавшей те или иные данные в этом блоке. Размер этой структуры управляется двумя атрибутами хранения, задаваемыми в операторе CREATE при создании объекта: • INITRANS — первоначальный, заранее заданный размер этой структуры. Стандартное значение: 2 — для индексов и 1 — для таблиц. • MAXTRANS — максимальный размер, до которого может разрастаться эта структура. Стандартное значение: 255. Итак, каждый блок начинает стандартно свое существование с одним или двумя слотами для транзакций. Количество активных транзакций, которые могут одновременно работать с блоком, ограничивается значением MAXTRANS и доступностью пространства в блоке. Невозможно достичь 255 одновременных транзакций в блоке, если там нет места для роста данной структуры. Можно искусственно продемонстрировать, как это происходит, создав таблицу с ограниченным значением MAXTRANS. Например:

tkyte@TKYTE816> create table t (x int) maxtrans 1;

Table created. tkyte@TKYTE816> insert into t values (1) ;

1 row created. tkyte@TKYTE816> insert into t values (2) ;

1 row created. tkyte@TKYTE816> commit;

Commit complete.

Теперь в одном сеансе выполняем:

tkyte@TKYTE816> update t set x - 3 where x = 1;

1 row updated.

а в другом:

tkyte@TKYTE816> update t set x = 4 where x = 2;

Поскольку обе строки находятся в одном и том же блоке и параметру MAXTRANS (максимальная степень одновременности доступа для данного блока) установлено значение 1, второй сеанс будет заблокирован. Это демонстрирует, что происходит, когда транзакции в количестве, превышающем значение параметра MAXTRANS, пытаются одновременно обратиться к одному и тому же блоку. Точно так же происходит блокирование, если параметр INITRANS имеет небольшое значение и в блоке нет места для динамического добавления транзакции. В большинстве случаев стандартные значения 1 и 2 для параметра INITRANS приемлемы, поскольку таблица транзакций будет динамически расти (в пределах имеющегося свободного пространства), но в некоторых средах для повышения параллелизма и уменьшения количества ожидающих сеансов это значение придется увеличить. В качестве примера можно привести таблицу или, ско Глава рее, индекс (поскольку в блоках индекса может храниться намного больше строк, чем в обычном блоке таблицы), который часто изменяется. Может понадобиться увеличить значение INITRANS, чтобы заранее зарезервировать достаточно места в блоке для предполагаемого количества одновременных транзакций. Это особенно необходимо, если предполагается заполнение блоков практически целиком и может не остаться места для динамического расширения таблицы транзакций в блоке.

ТМ — блокировки очередности ЯМД Такие блокировки позволяют быть уверенным, что структура таблицы не изменится при изменении ее содержимого. Рассмотрим такой пример. При изменении таблицы на нее устанавливается блокировка ТМ;

это предотвращает применение к ней операторов DROP или ALTER другим сеансом. Если сеанс пытается применить оператор ЯОД к таблице, на которую другой сеанс установил блокировку, он получит следующее сообщение об ошибке:

drop t a b l e dept * E R R at l i n e 1: RO ORA-00054: resource busy and acquire with NOWAIT specified Это сообщение поначалу сбивает с толку, поскольку нет никакого способа задать конструкции NOWAIT или WAIT в операторе DROP TABLE. Это просто сообщение общего вида, которое сеанс получает, пытаясь выполнить операцию, которая будет заблокирована, хотя не допускает блокирования. Как мы уже видели, такое же сообщение отображается при попытке применить оператор SELECT FOR UPDATE NOWAIT к заблокированной строке. Ниже показано, как эти блокировки будут отражены в таблице V$LOCK:

tkyte@TKYTE816> create table tl (x int) ;

Table created. tkyte@TKYTE816> create table t2 (x int) ;

Table created. tkyte@TKYTE816> insert into tl values (1);

1 row created. tkyte@TKYTE816> insert into t2 values (1);

1 row created. tkyte@TKYTE816> select username, 2 v$lock.sid, 3 id1, id2, 4 lmode, 5 request, block, v$lock.type 6 from v$lock, v$session 7 where v$lock.sid = v$session.sid 8 and v$session.username = USER 9/ Блокирование и одновременный доступ USERNAME TKYTE TKYTE TKYTE SID 8 8 8 ID1 24055 24054 327697 ID2 0 0 165 LMODE 3 3 6 REQUEST 0 0 0 BLOCK TY 0 TM 0 TM 0 TX tkyte@TKYTE816> select object_name, object_id from user_objects;

OBJECT_NAME Tl T2 OBJECT_ID 24054 Хотя для каждой транзакции может быть только одна блокировка ТХ, можно устанавливать столько блокировок ТМ, сколько объектов изменяется. В этом случае значение столбца ID1 для блокировки ТМ представляет собой идентификатор объекта, заблокированного оператором ЯМД, так что найти блокируемый объект легко. Интересная особенность блокировки ТМ: общее количество блокировок ТМ, поддерживаемых в системе, конфигурируется администратором (подробнее см. описание параметра DML_LOCKS файла init.ora в руководстве Oracle8i Server Reference). Его можно даже установить равным нулю. Это не означает, что база данных становится доступной только для чтения (так как блокировки не поддерживаются), — в ней не разрешены операторы ЯОД. Это используется, например, в сервере OPS для упрощения координации экземпляров. Можно также лишить возможности получать блокировки ТМ для отдельных объектов с помощью оператора ALTER TABLE имя_таблицы DISABLE TABLE LOCK.

Блокировки ЯОД Блокировки ЯОД автоматически устанавливаются на объекты в ходе выполнения операторов ЯОД для защиты их от изменения другими сеансами. Например, при выполнении оператора ЯОД ALTER TABLE Т на таблицу Т будет установлена исключительная блокировка ЯОД, что предотвращает установку блокировок ЯОД и ТМ на эту таблицу другими сеансами. Блокировки ЯОД удерживаются на период выполнения оператора ЯОД и снимаются сразу по его завершении. Это делается путем помещения операторов ЯОД в неявные пары операторов фиксации (или фиксации и отката). Вот почему операторы ЯОД в Oracle всегда фиксируются. Операторы CREATE, ALTER и т.д. фактически выполняются, как показано в следующем псевдокоде:

Begin Commit;

Оператор ЯОД Commit;

Exception When others then rollback;

End;

Поэтому операторы ЯОД всегда фиксируют транзакцию, даже если завершаются неудачно. Выполнение оператора ЯОД начинается с фиксации. Помните об этом. Сна Глава чала выполняется фиксация, чтобы в случае отката не пришлось откатывать предыдущую часть транзакции. При выполнении оператора ЯОД фиксируются все выполненные ранее изменения, даже если сам оператор ЯОД выполнен неудачно. Если должен быть выполнен оператор ЯОД, но не требуется, чтобы он зафиксировал существующую транзакцию, можно использовать автономную транзакцию (подробнее см. в главе 15). Имеется три типа блокировок ЯОД: • Исключительные блокировки ЯОД. Они предотвращают установку блокировок ЯОД или ТМ (ЯМД) другими сеансами. Это означает, что можно запрашивать таблицу в ходе выполнения оператора ЯОД, но нельзя ее изменять. • Разделяемые блокировки ЯОД. Они защищают структуру соответствующего объекта от изменения другими сеансами, но разрешают изменять данные. • Нарушаемые блокировки разбора (breakable parse locks). Они позволяют объекту, например плану запроса, хранящемуся в кэше разделяемого пула, зарегистрировать свою зависимость от другого объекта. При выполнении оператора ЯОД, затрагивающего заблокированный таким образом объект, сервер Oracle получает список объектов, зарегистрировавших свою зависимость, и помечает их как недействительные. Вот почему эти блокировки — "нарушаемые": они не предотвращают выполнение операторов ЯОД. Большинство операторов ЯОД устанавливает исключительную блокировку ЯОД. При выполнении оператора, подобного Alter table t add new_column date;

таблица Т будет недоступна для изменения, пока оператор выполняется. К таблице в этот период можно обращаться с помощью оператора SELECT, но другие действия, в том числе операторы ЯОД, блокируются. В Oracle 8i некоторые операторы ЯОД теперь могут выполняться без установки блокировок ЯОД. Например, можно выполнить:

create index t_idx on t ( x ) ONLINE;

Ключевое слово ONLINE изменяет метод построения индекса. Вместо установки исключительной блокировки ЯОД, предотвращающей изменения данных, Oracle попытается установить на таблицу низкоуровневую (режим 2) блокировку ТМ. Это предотвращает изменения структуры с помощью операторов ЯОД, но позволяет нормально выполнять операторы ЯМД. Сервер Oracle достигает этого путем записи в таблице изменений, сделанных в ходе выполнения оператора ЯОД, и учитывает эти изменения в новом индексе, когда завершается его создание. Это существенно увеличивает доступность данных. Другие типы операторов ЯОД устанавливают разделяемые блокировки ЯОД. Они устанавливаются на объекты, от которых зависят скомпилированные хранимые объекты, типы процедур и представлений. Например, если выполняется оператор:

Create as select from where view MyView * emp, dept emp. deptno = dept.deptno;

Pages:     | 1 | 2 || 4 | 5 |   ...   | 24 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.