КАЗАНСКИЙ (ПРИВОЛЖСКИЙ) ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ Факультет вычислительной математики и кибернетики Кафедра теоретической кибернетики МЕТОДИЧЕСКОЕ ПОСОБИЕ «Технологии баз данных (СУБД Oracle)» Старший преподаватель кафедры теоретической кибернетики Гусенков А.М. Администратор СУБД Oracle лаборатории технологий баз данных НИИММ им. Н.Г.Чеботарёва Макарова Н.Г. Казань-2010 Технологии баз данных (СУБД Oracle) Гусенков А.М. Макарова Н.Г. Методическое пособие представляет собой основную часть специального курса «СУБД Oracle» и предназначено для использования в качестве учебного и справочного материала студентами IV курса кафедры теоретической кибернетики. В методическом пособии исследованы следующие вопросы: Структура и архитектура реляционной системы управления базами данных Oracle; Средства языка SQL; Способы создания и работы с объектами базы данных (таблицами, индексами, представлениями, ограничениями, триггерами и т.д.); Утилиты Oracle для импорта, экспорта, загрузки данных; Проведение настройки приложений; Применение основных стандартных пакетов; Казань - 2010 2 Содержание Введение .............................................................................................................................................. 6 Архитектура сервера Oracle .............................................................................................................. 7 Файлы базы данных........................................................................................................................ 8 Файлы параметров ...................................................................................................................... 9 Файлы данных........................................................................................................................... 10 Файлы журнала повторного выполнения ............................................................................... 12 Активный журнал повторного выполнения........................................................................... 13 Архивный журнал повторного выполнения .......................................................................... 15 Управляющие файлы................................................................................................................ 15 Временные файлы .................................................................................................................... 16 Память............................................................................................................................................ 16 Области PGA и UGA ................................................................................................................ 16 Область SGA ............................................................................................................................. 17 Буферный кэш ........................................................................................................................... 17 Разделяемый пул ....................................................................................................................... 18 Большой пул .............................................................................................................................. 19 Буфер журнала повторного выполнения ................................................................................ 19 Java-пул ...................................................................................................................................... 20 Процессы ....................................................................................................................................... 21 Серверные процессы ................................................................................................................ 21 Фоновые процессы ................................................................................................................... 23 Подчиненные процессы ........................................................................................................... 26 Управление безопасностью БД ...................................................................................................... 27 Механизм аутентификации ......................................................................................................... 28 Квоты табличных пространств .................................................................................................... 28 Табличное пространство по умолчанию .................................................................................... 29 Временное табличное пространство ........................................................................................... 29 Блокирование входа пользователя в систему ............................................................................ 29 Ресурсные ограничения ............................................................................................................... 29 Привилегии ................................................................................................................................... 29 Системные привилегии (system privileges) ............................................................................ 29 Объектные привилегии (object privileges) .............................................................................. 30 Роли ................................................................................................................................................ 30 Профиль ......................................................................................................................................... 30 Аудит ............................................................................................................................................. 31 Стандартный аудит ................................................................................................................... 31 Аудит по значениям ................................................................................................................. 33 Дифференцированный аудит ................................................................................................... 33 Создание объектов БД...................................................................................................................... 34 Стандартные таблицы и поддержка целостности базы данных .............................................. 34 Ограничение целостности Foreign Key .................................................................................. 36 Состояния ограничений целостности ..................................................................................... 36 Проверка ограничений ............................................................................................................. 37 Изменение режима выполнения откладываемых ограничений ........................................... 38 Создание ограничений ............................................................................................................. 38 3 Получение информации о таблицах и ограничениях целостности ..................................... 38 Временные таблицы ..................................................................................................................... 39 Внешние таблицы ......................................................................................................................... 39 Индексы на основе В*-дерева ..................................................................................................... 40 Индексы с обращенным ключом................................................................................................. 40 Индексы на основе битовых карт ............................................................................................... 41 Индексы по функции .................................................................................................................... 41 Таблицы, организованные по индексу ....................................................................................... 42 Представления .............................................................................................................................. 43 Триггера ......................................................................................................................................... 44 Табличные триггера ................................................................................................................. 44 Триггер INSTEAD OF .............................................................................................................. 47 Системные триггера ................................................................................................................. 48 Синонимы ...................................................................................................................................... 49 Последовательности ..................................................................................................................... 50 Импорт, экспорт и загрузка данных ........................................................................................... 51 Утилиты IMP и EXP ................................................................................................................ 51 Утилиты Data Pump .................................................................................................................. 53 SQL*Loader ............................................................................................................................... 54 Описание метаданных .................................................................................................................. 58 SQL-запросы и подзапросы ............................................................................................................. 60 Базовый оператор SELECT ...................................................................................................... 60 Работа с NULL .......................................................................................................................... 62 Соединение таблиц ................................................................................................................... 63 Подзапросы ............................................................................................................................... 66 Составные запросы (операторы SET) ..................................................................................... 69 Операторы изменения данных в БД ............................................................................................... 70 INSERT ...................................................................................................................................... 70 UPDATE .................................................................................................................................... 72 DELETE ..................................................................................................................................... 73 MERGE ...................................................................................................................................... 73 COMMIT, SAVEPOINT, ROLLBACK .................................................................................... 74 Подзапросы в командах DML ................................................................................................ 75 Использование однострочных и групповых функций .................................................................. 76 Однострочные функции ........................................................................................................... 76 Многострочные функции......................................................................................................... 80 Аналитические функции .................................................................................................................. 85 Конструкция фрагментации ........................................................................................................ 87 Конструкция упорядочения ......................................................................................................... 87 Конструкция окна ......................................................................................................................... 88 Окна диапазона ............................................................................................................................. 89 Окна строк ..................................................................................................................................... 90 Задание окон ................................................................................................................................. 91 Создание иерархических запросов ................................................................................................. 92 Использование объектно-реляционных средств ........................................................................... 94 Применение основных стандартных пакетов .............................................................................. 102 DBMS_APPLICATION_INFO ............................................................................................... 103 DBMS_JOB .............................................................................................................................. 107 UTL_FILE ................................................................................................................................ 111 4 DBMS_LOB ............................................................................................................................. 112 DBMS_ALERT ........................................................................................................................ 113 DBMS_PIPE............................................................................................................................. 117 DBMS_UTILITY ..................................................................................................................... 118 Стратегии и средства настройки приложений ............................................................................. 120 EXPLAIN PLAN ...................................................................................................................... 121 SQL_TRACE, TIMED_STATISTICS и TKPROF ................................................................. 122 Связываемые переменные ..................................................................................................... 129 Стабилизация плана оптимизатора ............................................................................................... 135 Список литературы ......................................................................................................................... 144 5 Введение СУБД Oracle - реляционная база данных, состоящая из множества таблиц, которые могут быть использованы независимо или взаимосвязано друг с другом. На Рис.1 представлена диаграмма отношений сущностей (erd – entity relationship diagram) для приложения “Трудовые ресурсы”(HR – Human Resources). В ней отражены таблицы и отношения между ними, которые используются в практических примерах данного пособия. Рис.1 6 Архитектура сервера Oracle Oracle проектировалась как максимально переносимая СУБД, - она доступна на всех распространенных платформах. Хотя физические средства реализации СУБД Oracle на разных платформах могут отличаться, архитектура системы - достаточно общая, чтобы можно было понять, как СУБД Oracle работает на всех платформах. Сервер Oracle включает процессы, структуры памяти и файлы. Сервер Oracle состоит из экземпляра Oracle и базы данных Oracle. Экземпляр Oracle это совокупность фоновых процессов и структур памяти. Экземпляр должен быть запущен для обеспечения доступа к информации базы данных. Каждый раз, когда запускается экземпляр, выделяется память для системной глобальной области (SGA) и стартуют фоновые процессы. Фоновые процессы экземпляра выполняют стандартные функции, необходимые для обслуживания запросов нескольких пользователей одновременно. Фоновые процессы выполняют операции ввода-вывода и контролируют другие процессы Oracle, обеспечивая параллельную обработку, повышение производительности и надежности. База данных состоит из файлов операционной системы (файлов базы данных), в которых на физическом уровне хранится информация в базе данных. Использование файлов базы данных гарантирует целостность хранимой информации и восстановление в случае сбоя экземпляра Oracle. 7 Рис.2 компонента архитектуры сервера Oracle: На Рис.2 представлены три основных Файлы - образуют базу данных и поддерживают экземпляр. Это файлы параметров, управляющие файлы, данных, временных данных и журналов повторного выполнения. Структуры памяти - системная глобальная область (SGA - System Global Area ), глобальная область процесса (PGA - Process Global Area), глобальная область пользователя (UGA - User Global Area). Физические процессы или потоки - серверные процессы, фоновые процессы и подчиненные процессы. Файлы базы данных. Базу данных образуют следующие файлы. Файлы параметров. По этим файлам экземпляр при запуске определяет свои характеристики, например, размер структур в памяти и местонахождение управляющих файлов. Файлы данных. Собственно данные (в этих файлах хранятся таблицы, индексы и все остальные сегменты). 8 Файлы журнала повторного выполнения. Журналы транзакций. Управляющие файлы. Определяют местонахождение файлов данных и содержат другую необходимую информацию о состоянии базы данных. Временные файлы. Используются при сортировке больших объемов данных и для хранения временных объектов. Файлы паролей. Используются для аутентификации пользователей, выполняющих администрирование удаленно, по сети. Файлы параметров С базой данных Oracle связано много файлов параметров: от файла TNSNAMES.ORA на клиентской рабочей станции (используемого для поиска сервера) и файла LISTENER.ORA на сервере (для запуска процесса прослушивания) до файлов SQLNET.ORA, PROTOCOL.ORA, NAMES.ORA, CMAN.ORA и LDAP.ORA. Наиболее важным является файл параметров инициализации экземпляра, потому что без него не удастся запустить экземпляр. Файл параметров инициализации экземпляра обычно называют файлом init или файлом init.ora. Это название происходит от стандартного имени этого файла, init<ORACLE_SID>.ora. Например, экземпляр со значением SID, равным kurs, обычно имеет файл инициализации initkurs.ora. Без файла параметров инициализации нельзя запустить экземпляр Oracle. Поэтому файл этот достаточно важен. Однако, поскольку это обычный текстовый файл, который можно создать в любом текстовом редакторе, сохранять его ценой собственной жизни не стоит. ORACLE_SID (или SID) - это идентификатор экземпляра. Если значение ORACLE_SID задано неправильно, выдается сообщение об ошибке ORACLE NOT AVAILABLE. В одном и том же базовом каталоге ORACLE_HOME может быть несколько баз данных, так что необходимо иметь возможность уникально идентифицировать их и соответствующие конфигурационные файлы. В Oracle файл init.ora имеет очень простую конструкцию. Он представляет собой набор пар имя параметра/значение. Файл init.ora может иметь такой вид: db_name = "kurs" db_block_size = 8192 control_files = ("C:\oradata\control01.ctl", "C:\oradata\control02.ctl") Фактически это почти минимальный файл init.ora, с которым уже можно работать. В нем указан размер блока, стандартный для платформы OC. Файл параметров инициализации используется для получения имени базы данных и местонахождения управляющих файлов. Управляющие файлы содержат информацию о местонахождении всех остальных файлов, так что они нужны в процессе начальной загрузки при запуске экземпляра. Начиная с версии Oracle 9i, существуют два типа файлов параметров инициализации: файл статических параметров, PFILE или init<ORACLE_SID>.ora; файл постоянных параметров, SPFILE или :sp<ORACLE_SID>.ora; В файле параметров содержатся: перечень параметров экземпляра, имя базы данных, которую обслуживает экземпляр, распределение памяти для структур системной глобальной области (SGA), имена и расположение управляющих файлов, информация о сегментах отката, режим журналирования (ARCHJVELOG или NOARCHIVELOG). 9 PFILE – это текстовый файл, который может быть отредактирован с помощью стандартного редактора ОС. Экземпляр читает файл параметров только при запуске. Если файл был модифицирован, то для того, чтобы изменения вступили в силу, необходимо перезапустить экземпляр. Некоторые из параметров могут быть динамически изменены во время работы экземпляра. Изменения динамических параметров не отражаются на содержимом файла PFILE. SPFILE – это двоичный файл. Этот файл не предназначен для внесения изменений вручную и должен всегда располагаться на сервере. После создания этот файл сопровождается сервером Oracle. SPFILE позволяет вносить изменения в параметры, которые становятся постоянными и действуют после перезапуска базы данных. Команда ALTER SYSTEM применяется для изменения значений параметров. Ключевой параметр SCOPE в этой команде определяет границы внесения изменений. SCOPE=MEMORY – изменения действуют только до остановки запущенного в настоящее время экземпляра. SCOPE=SPFILE - значение параметра изменяется только в файлу SPFILE. SCOPE=BOTH - изменяется значение параметра для выполняемого экземпляра и в файле SPFOLE. Пример: ALTER SYSTEM SET shared_pool_size = 512000000 SCOPE=SPFILE Файл SPFILE может быть создан на основе файла init<ORACLE_SID>.ora с помощью команды CREATE SPFILE, которая может быть выполнена до или после открытия базы данных. CREATE SPFILE [=’полный путь SPFILE’] FROM PFILE [=’полный путь PFILE’ ] На основе SPFILE можно получить PFILE. CREATE PFILE [=’полный путь PFILE’] FROM SPFILE [=’полный путь SPFILE’ ] Выгрузка SPFILE в PFILE полезна для сохранения содержимого файла инициализации на случай его потери. Чтобы посмотреть информацию из SPFILE V$SPARAMETER можно использовать представление При запуске экземпляра можно явно указать файл параметров. STARTUP PFILE = ’полный путь PFILE’ В файл PFILE можно вставить параметр, указывающий на то, что нужно пользоваться SPFILE. Это единственный способ, чтобы указать путь к файлу SPFILE, если он не назван и не находится в директории по умолчанию (опции STARTUP SPFILE нет). Файлы данных Файлы данных вместе с файлами журнала повторного выполнения являются наиболее важными в базе данных. Именно в них хранятся все данные. Рассмотрим организацию файлов данных в Oracle и способы хранения данных в этих файлах. Но прежде надо разобраться, что такое табличное пространство, сегмент, экстент и блок. Все это - единицы выделения пространства под объекты в базе данных Oracle. Сегменты - это области на диске, выделяемые под объекты - таблицы, индексы, сегменты отката и т.д. При создании таблицы создается сегмент таблицы. При создании фрагментированной таблицы создается по сегменту для каждого фрагмента. При создании индекса создается сегмент индекса и т.д. Каждый объект, занимающий место на диске, 10 хранится в одном сегменте. Есть сегменты отката, временные сегменты, сегменты индексов и т.д. Сегменты, в свою очередь, состоят из одного или нескольких экстентов. Экстент - это непрерывный фрагмент пространства в файле. Каждый сегмент первоначально состоит хотя бы из одного экстента, причем для некоторых объектов требуется минимум два экстента (в качестве примера можно назвать сегменты отката). Чтобы объект мог вырасти за пределы исходного экстента, ему необходимо выделить следующий экстент. Этот экстент не обязательно должен выделяться рядом с первым; он может находиться достаточно далеко от первого, но в пределах экстента в файле пространство всегда непрерывно. Экстенты состоят из блоков. Блок - наименьшая единица выделения пространства в Oracle. В блоках и хранятся строки данных, индексов или промежуточные результаты сортировок. Именно блоками сервер Oracle обычно выполняет чтение и запись на диск. Блоки в Oracle бывают размером 2 Кбайт, 4 Кбайт или 8 Кбайт (хотя допустимы также блоки размером 16 Кбайт и 32 Кбайт). Размер блока в базе данных с момента ее создания величина постоянная, поэтому все блоки в базе данных одного размера. Формат блока: - - Заголовок блока содержит информацию о типе блока (блок таблицы, блок индекса и т.д.), информацию о текущих и прежних транзакциях, затронувших блок, а также адрес (местонахождение) блока на диске. Каталог таблиц содержит информацию о таблицах, строки которых хранятся в этом блоке (в блоке могут храниться данные нескольких таблиц). Каталог строк содержит описание хранящихся в блоке строк. Это массив указателей на строки, хранящиеся в области данных блока. Вместе эти три части блока называются служебным пространством блока. Это пространство недоступно для данных и используется сервером Oracle для управления блоком. Остальные две части блока вполне понятны: в блоке имеется: Занятое пространство, в котором хранятся данные. Свободное пространство. Таким образом, сегменты состоят из экстентов, которые, в свою очередь, состоят из блоков. - Следующее понятие: Табличное пространство (tablespace) - это контейнер с сегментами. Каждый сегмент принадлежит к одному табличному пространству. В табличном пространстве может быть много сегментов. Все экстенты сегмента находятся в табличном пространстве, где создан сегмент. Сегменты никогда не переходят границ табличного пространства. С табличным пространством, в свою очередь, связан один или несколько файлов данных. Экстент любого сегмента табличного пространства целиком помещается в одном файле данных. Однако экстенты сегмента могут находиться в нескольких различных файлах данных. Табличные пространства в Oracle - это логические структуры хранения данных. Разработчики создают сегменты в табличных пространствах. Они никогда не переходят на уровень файлов. Объекты создаются в табличных пространствах, а об остальном заботится сервер Oracle. Если в дальнейшем администратор базы данных решит перенести файлы данных на другой диск для более равномерного распределения операций 11 ввода/вывода по дискам, никаких проблем для приложения это не создаст. На работе приложения это никак не отразится. Итак, иерархия объектов, обеспечивающих хранение данных в Oracle, выглядит так. 1. База данных, состоящая из одного или нескольких табличных пространств. 2. Табличное пространство, состоящее из одного или нескольких файлов данных. Табличное пространство содержит сегменты. 3. Сегмент (TABLE, INDEX и т.д.), состоящий из одного и более экстентов. Сегмент привязан к табличному пространству, но его данные могут находиться в разных файлах данных, образующих это табличное пространство. 4. Экстент - набор расположенных рядом на диске блоков. Экстент целиком находится в одном табличном пространстве и, более того, в одном файле данных этого табличного пространства. 5. Блок - наименьшая единица управления пространством в базе данных. Блок наименьшая единица ввода/вывода, используемая сервером. Файлы журнала повторного выполнения Файлы журнала повторного выполнения(redo log) принципиально важны для базы данных Oracle. Это журналы транзакций базы данных. Они используются только для восстановления при сбое экземпляра или носителя или при поддержке резервной базы данных на случай сбоев. Если на сервере, где работает СУБД, отключится питание и вследствие этого произойдет сбой экземпляра, для восстановления системы в состояние, непосредственно предшествующее отключению питания, сервер Oracle при повторном запуске будет использовать активные журналы повторного выполнения. Если диск, содержащий файлы данных, полностью выйдет из строя, для восстановления резервной копии этого диска на соответствующий момент времени сервер Oracle, помимо активных журналов повторного выполнения, будет использовать также архивные. Кроме того, при случайном удалении таблицы или какой-то принципиально важной информации, если эта операция зафиксирована, с помощью активных и заархивированных журналов повторного выполнения можно восстановить данные из резервной копии на момент времени, непосредственно предшествующий удалению. Практически каждое действие, выполняемое в СУБД Oracle, генерирует определенные данные повторного выполнения, которые надо записать в активные файлы журнала повторного выполнения. При вставке строки в таблицу конечный результат этой операции записывается в журналы повторного выполнения. При удалении строки записывается факт удаления. При удалении таблицы в журнал повторного выполнения записываются последствия этого удаления. Данные из удаленной таблицы не записываются, но рекурсивные SQL-операторы, выполняемые сервером Oracle при удалении таблицы, генерируют определенные данные повторного выполнения. Например, при этом сервер Oracle удалит строку из таблицы SYS.OBJ$, и это удаление будет отражено в журнале. Некоторые операции могут выполняться в режиме с минимальной генерацией информации повторного выполнения. Например, можно создать индекс с атрибутом NOLOGGING. Это означает, что первоначальное создание этого индекса не будет записываться в журнал, но любые инициированные при этом рекурсивные SQL-операторы, выполняемые сервером Oracle, - будут. Например, вставка в таблицу SYS.OBJ$ строки, соответствующей индексу, в журнал записываться не будет. Однако последующие изменения индекса при выполнении SQL-операторов INSERT, UPDATE и DELETE, будут записываться в журнал. 12 Есть два типа файлов журнала повторного выполнения: активные и архивные. Активный журнал повторного выполнения В каждой базе данных Oracle есть как минимум два активных файла журнала повторного выполнения. Эти активные файлы журнала повторного выполнения имеют фиксированный размер и используются циклически. Сервер Oracle выполняет запись в файл журнала 1, а когда доходит до конца этого файла, - переключается на файл журнала 2 и переписывает его содержимое от начала до конца. Когда заполнен файл журнала 2, сервер переключается снова на файл журнала 1 (если имеется всего два файла журнала повторного выполнения; если их три, сервер, разумеется, переключится на третий файл): Переход с одного файла журнала на другой называется переключением журнала. Важно отметить, что переключение журнала может вызвать временное "зависание" плохо настроенной базы данных. Поскольку журналы повторного выполнения используются для восстановления транзакций в случае сбоя, перед повторным использованием файла журнала необходимо убедиться, что его содержимое не понадобится в случае сбоя. Если сервер Oracle "не уверен", что содержимое файла журнала не понадобится, он приостанавливает на время изменения в базе данных и убеждается, что данные, "защищаемые" этой информацией повторного выполнения, записаны на диск. После этого обработка возобновляется, и файл журнала переписывается. Ключевое понятие баз данных - обработка контрольной точки. Чтобы понять, как используются активные журналы повторного выполнения, надо разобраться с обработкой контрольной точки, использованием буферного кэша базы данных и рассмотреть функции процесса записи блоков базы данных (Database Block Writer - DBWn). В буферном кэше базы данных временно хранятся блоки базы данных. Это структура в области SGA разделяемой памяти экземпляра Oracle. При чтении блоки запоминаются в этом кэше (предполагается, что в дальнейшем их не придется читать с диска). Буферный кэш - первое и основное средство настройки производительности сервера. Он существует исключительно для ускорения очень медленного процесса ввода/вывода. При изменении блока путем обновления одной из его строк изменения выполняются в памяти, в блоках буферного кэша. Информация, достаточная для повторного выполнения этого изменения, записывается в буфер журнала повторного выполнения - еще одну структуру данных в области SGA. При фиксации изменений с помощью оператора COMMIT сервер Oracle не записывает на диск все измененные блоки в области SGA. Он только записывает в активные журналы повторного выполнения содержимое буфера журнала повторного выполнения. Пока измененный блок находится в кэше, а не на диске, содержимое соответствующего активного журнала может быть использовано в случае сбоя экземпляра. Если сразу после фиксации изменения отключится питание, содержимое буферного кэша пропадет. Если это произойдет, единственная запись о выполненном изменении останется в файле журнала повторного выполнения. После перезапуска экземпляра сервер Oracle будет по сути повторно выполнять транзакцию, изменяя блок точно так же, как он это делал ранее, и фиксируя это изменение автоматически. Итак, если измененный блок находится в кэше и не записан на диск, мы не можем повторно записывать соответствующий файл журнала повторного выполнения. Тут и вступает в игру процесс DBWn. Это фоновый процесс сервера Oracle, отвечающий за освобождение буферного кэша при заполнении и обработку контрольных точек. Обработка контрольной точки состоит в сбросе грязных (измененных) блоков из буферного кэша на диск. Сервер Oracle делает это автоматически, в фоновом 13 режиме. Обработка контрольной точки может быть вызвана многими событиями, но чаще всего - переключением журнала повторного выполнения. При заполнении файла журнала 1, перед переходом на файл журнала 2, сервер Oracle инициирует обработку контрольной точки. В этот момент процесс DBWn начинает сбрасывать на диск все грязные блоки, защищенные файлом журнала I. Пока процесс DBWn не сбросит все блоки, защищаемые этим файлом, сервер Oracle не сможет его повторно использовать. Если попытаться использовать его прежде, чем процесс DBWn завершит обработку контрольной точки, в журнал сообщений (alert log) будет выдано следующее сообщение: Thread 1 cannot allocate new log, sequence 66 Checkpoint not complete Current log# 2 seq# 65 mem# 0: C:\ORACLE\ORADATA\TKYTE816\REDO02.LOG Журнал сообщений - это файл на сервере, содержащий информационные сообщения сервера, например, о запуске и останове, а также уведомления об исключительных ситуациях, вроде незавершенной обработки контрольной точки. Итак, в момент выдачи этого сообщения обработка изменений была приостановлена до завершения процессом DBWn обработки контрольной точки. Для ускорения обработки сервер Oracle отдал все вычислительные мощности процессу DBWn. При соответствующей настройке сервера это сообщение в журнале появляться не должно. Если оно все же есть, значит, имеют место искусственные, ненужные ожидания, которых можно избежать. Цель (в большей степени администратора базы данных, чем разработчика) - иметь достаточно активных файлов журнала повторного выполнения. Это предотвратит попытки сервера использовать файл журнала, прежде чем будет закончена обработка контрольной точки. Если это сообщение выдается часто, значит, администратор базы данных не выделил для приложения достаточного количества активных журналов повторного выполнения или процесс DBWn не настроен, как следует. Разные приложения генерируют различные объемы информации повторного выполнения. Системы класса СППР (системы поддержки принятия решений, выполняющие только запросы), естественно, будут генерировать намного меньше информации повторного выполнения, чем системы ООТ (системы оперативной обработки транзакций). Система, манипулирующая изображениями в больших двоичных объектах базы данных, может генерировать во много раз больше данных повторного выполнения, чем простая система ввода заказов. В системе ввода заказов со 100 пользователями генерируется в десять раз меньше данных повторного выполнения, чем в системе с 1000 пользователей. "Правильного" размера для журналов повторного выполнения нет, - он просто должен быть достаточным. При определении размера и количества активных журналов повторного выполнения необходимо учитывать много факторов. Некоторые из них: • Резервная база данных. Когда заполненные журналы повторного выполнения посылаются на другую машину и там применяются к копии текущей базы данных, необходимо много небольших файлов журнала. Это поможет уменьшить рассинхронизацию резервной базы данных с основной. • Множество пользователей, изменяющих одни и те же блоки. Здесь могут понадобиться большие файлы журнала повторного выполнения. Поскольку все изменяют одни и те же блоки, желательно, чтобы до того как блоки будут сброшены на диск, было выполнено как можно больше изменений. Каждое переключение журнала инициирует обработку контрольной точки, так что желательно переключать журналы как можно реже. Это, однако, может замедлить восстановление. 14 • Среднее время восстановления. Если необходимо обеспечить максимально быстрое восстановление, придется использовать файлы журнала меньшего размера, даже если одни и те же блоки изменяются множеством пользователей. Один или два небольших файла журнала повторного выполнения будут обработаны при восстановлении намного быстрее, чем один гигантский. Система в целом будет работать медленнее, чем могла бы (из-за слишком частой обработки контрольных точек), но восстановление будет выполняться быстрее. Архивный журнал повторного выполнения База данных Oracle может работать в двух режимах - NOARCHIVELOG и ARCHIVELOG. Если база данных не работает в режиме ARCHIVELOG, данные рано или поздно будут потеряны. Работать в режиме NOARCHIVELOG можно только в среде разработки или тестирования. Эти режимы отличаются тем, что происходит с файлом журнала повторного выполнения до того как сервер Oracle его перепишет. Сохранять ли копию данных повторного выполнения или разрешить серверу Oracle переписать ее, потеряв при этом навсегда? - очень важный вопрос. Если не сохранить этот файл, мы не сможем восстановить данные с резервной копии до текущего момента. Предположим, резервное копирование выполняется раз в неделю, по субботам. В пятницу вечером, после того как за неделю было сгенерировано несколько сотен журналов повторного выполнения, происходит сбой диска. Если база данных не работала в режиме ARCHIVELOG, остается только два варианта дальнейших действий. • Удалить табличное пространство/пространства, связанные со сбойным диском. Любое табличное пространство, имеющее файлы данных на этом диске, должно быть удалено, включая его содержимое. Если затронуто табличное пространство SYSTEM (словарь данных Oracle), этого сделать нельзя. • Восстановить данные за субботу и потерять все изменения за неделю. Оба варианта непривлекательны, поскольку приводят к потере данных. Работая же в режиме ARCHIVELOG, достаточно найти другой диск и восстановить на него соответствующие файлы с субботней резервной копии. Затем применить к ним архивные журналы повторного выполнения и, наконец, - активные журналы повторного выполнения (то есть повторить все накопленные за неделю транзакции в режиме быстрого наката). При этом ничего не теряется. Данные восстанавливаются на момент сбоя. Управляющие файлы Управляющий файл (control) - это сравнительно небольшой файл (в редких случаях он может увеличиваться до 64 Мбайт), содержащий информацию обо всех файлах, необходимых серверу Oracle. Из файла параметров инициализации (init.ora) экземпляр может узнать, где находятся управляющие файлы, а в управляющем файле описано местонахождение файлов данных и файлов журнала повторного выполнения. В управляющих файлах хранятся и другие необходимые серверу Oracle сведения, в частности время обработки контрольных точек, имя базы данных (которое должно совпадать со значением параметра инициализации db_name), дата и время создания базы данных, хронология архивирования журналов повторного выполнения (именно она приводит к увеличению размера управляющего файла в некоторых случаях) и т.д. Управляющие файлы надо мультиплексировать. Необходимо поддерживать несколько копий этих файлов, желательно на разных дисках, чтобы предотвратить потерю управляющих файлов в случае сбоя диска. Потеря управляющих файлов - не фатальное событие, она только существенно усложнит восстановление. 15 Временные файлы Временные файлы данных в Oracle (temporary) - это специальный тип файлов данных. Сервер Oracle использует временные файлы для хранения промежуточных результатов сортировки большого объема данных или результирующих множеств, если для них не хватает оперативной памяти. Постоянные объекты данных, такие как таблицы или индексы, во временных файлах никогда не хранятся, в отличие от содержимого временных таблиц и построенных по ним индексов. Так что создать таблицы приложения во временном файле данных нельзя, а вот хранить в нем данные можно, если использовать временную таблицу. Сервер Oracle обрабатывает временные файлы специальным образом. Обычно все изменения объектов записываются в журналы повторного выполнения. Эти журналы транзакций в дальнейшем можно использовать для повторного выполнения транзакций Это делается, например, при восстановлении после сбоя. Временные файлы в этом процессе не участвуют. Для них не генерируются данные повторного выполнения, хотя и генерируются данные отмены (UNDO) при работе с глобальными временными таблицами, чтобы можно было откатить изменения, сделанные в ходе сеанса. Создавать резервные копии временных файлов данных нет необходимости, а если кто-то это делает, то напрасно теряет время, поскольку данные во временном файле данных восстановить все равно нельзя. Память Основные структуры памяти сервера Oracle. Их три: • SGA, System Global Area - глобальная область системы. Это большой совместно используемый сегмент памяти, к которому обращаются все процессы Oracle. • PGA, Process Global Area - глобальная область процесса. Это приватная область памяти процесса или потока, недоступная другим процессам/потокам. • UGA, User Global Area - глобальная область пользователя. Это область памяти, связанная с сеансом. Глобальная область памяти может находиться в SGA либо в PGA. Если сервер работает в режиме разделяемого сервера, она располагается в области SGA, если в режиме выделенного сервера, - в области PGA. Области PGA и UGA PGA - область памяти процесса. Эта область памяти используется одним процессом или одним потоком. Она недоступна ни одному из остальных процессов/потоков в системе. Область PGA никогда не входит в состав области SGA - она всегда локально выделяется процессом или потоком. Область памяти UGA хранит состояние сеанса, поэтому всегда должна быть ему доступна. Местонахождение области UGA зависит исключительно от конфигурации сервера Oracle. Если сконфигурирован режим MTS, область UGA должна находиться в структуре памяти, доступной всем процессам, следовательно, в SGA. В этом случае сеанс сможет использовать любой разделяемый сервер, так как каждый из них сможет прочитать и записать данные сеанса. При подключении к выделенному серверу это требование общего доступа к информации о состоянии сеанса снимается, и область UGA становится почти синонимом PGA, - именно там информация о состоянии сеанса и будет располагаться. Просматривая статистическую информацию о системе, можно обнаружить, что при работе в режиме выделенного сервера область UGA входит в PGA (размер области PGA будет больше или равен размеру используемой памяти UGA - размер UGA будет учитываться при определении размера области PGA). 16 Размер области PGA/UGA определяют параметры уровня сеанса в файле init.ora: SORT_AREA_SIZE и SORT_AREA_RETAINED_SIZE. Эти два параметра управляют объемом пространства, используемым сервером Oracle для сортировки данных перед сбросом на диск, и определяют объем сегмента памяти, который не будет освобожден по завершении сортировки. SORT_AREA_SIZE обычно SORT_AREA_RETAINED_SIZE - в UGA. выделяется в области PGA, a Область SGA Каждый экземпляр Oracle имеет одну большую область памяти, которая называется SGA, System Global Area - глобальная область системы. Это большая разделяемая структура, к которой обращаются все процессы Oracle. Область SGA разбита на несколько пулов. Java-пул представляет собой фиксированный пул памяти, выделенный виртуальной машине JVM, которая работает в составе сервера. Большой пул (large pool) используется сервером в режиме разделяемого сервера для размещения памяти сеанса. Разделяемый пул (shared pool) содержит разделяемые курсоры, хранимые процедуры, объекты состояния, кэш словаря данных и десятки других компонентов данных. Буферный пул - память, выделенная под буферы блоков (кэш блоков базы данных, буферный кэш), буфер журнала повторного выполнения. На общий размер SGA наиболее существенно влияют следующие параметры init.ora. JAVA_POOL_SIZE. - управляет размером Java-пула. SHARED_POOL_SIZE - управляет (до некоторой степени) размером разделяемого пула. LARGE_POOL_SIZE.- управляет размером большого пула. DB_CACHE _SIZE.- управляет размером буферного кэша. LOG_BUFFER.- управляет размером буфера журнала повторного выполнения. Буферный кэш В буферном кэше сервер Oracle хранит блоки базы данных перед их записью на диск, а также после считывания с диска. Это принципиально важный компонент SGA. Если сделать его слишком маленьким, запросы будут выполняться годами. Если же он будет чрезмерно большим, пострадают другие процессы (например, выделенному серверу не хватит пространства для создания области PGA, и он просто не запустится). Блоки в буферном кэше контролируются двумя списками. Это список "грязных" блоков, которые должны быть записаны процессом записи блоков базы данных (это DBWn). Есть еще список "чистых" блоков, где вместо физического упорядочения списка блоков, сервер Oracle с помощью счетчика, связанного с блоком, подсчитывает количество обращений ("touch count) при каждом обращении (hit) к этому блоку в буферном КЭШе. Таблица X$BH содержит информацию о блоках в буферном кэше. В ней можно увидеть, как "счетчик обращений" увеличивается при каждом обращении к блоку. Использованный буфер не переносится в начало списка. Он остается на месте, а его "счетчик обращений" увеличивается. Блоки со временем перемешаются по списку естественным путем, поскольку измененные блоки переносятся в список "грязных" (для записи на диск процессом DBWn). 17 Кроме того, если, несмотря на повторное использование блоков, буферный кэш заполнился, и блок с небольшим значением "счетчика обращений" удаляется из списка, он возвращается с новыми данными примерно в середину списка.. В буферном кэше выделено два буферных пула: KEEP RECYCLE Если блок используется часто, он остается в пуле KEEP. Если к блоку некоторое время не обращались и в буферном пуле не осталось места, этот блок выбрасывается из пула как устаревший. В пуле RECYCLE блоки выбрасываются иначе. Пул KEEP предназначен для продолжительного кэширования "горячих" блоков. Из пула RECYCLE блок выбрасывается сразу после использования. Это эффективно в случае "больших"таблиц, которые читаются случайным образом. (Понятие "большая таблица" очень относительно; нет эталона для определения того, что считать "большим".) Если в течение разумного времени вероятность повторного считывания блока мала, нет смысла долго держать такой блок в кэше. Поэтому в пуле RECYCLE блоки регулярно перечитываются. Разделяемый пул Разделяемый пул - один из наиболее важных фрагментов памяти в области SGA, особенно для обеспечения производительности и масштабируемости. Слишком маленький разделяемый пул может снизить производительность настолько, что система будет казаться зависшей. Слишком большой разделяемый пул может привести к такому же результату. Неправильное использование разделяемого пула грозит катастрофой. В разделяемом пуле сервер Oracle кэширует различные "программные" данные. Здесь кэшируются результаты разбора запроса. Перед повторным разбором запроса сервер Oracle просматривает разделяемый пул в поисках готового результата. Выполняемый сеансом PL/SQL-код тоже кэшируется здесь, так что при следующем выполнении не придется снова читать его с диска. PL/SQL-код в разделяемом пуле не просто кэшируется, - появляется возможность его совместного использования сеансами. Если 1000 сеансов выполняют один и тот же код, загружается и совместно используется всеми сеансами лишь одна копия этого кода. Сервер Oracle хранит в разделяемом пуле параметры системы. Здесь же хранится кэш словаря данных, содержащий информацию об объектах базы данных. Разделяемый пул состоит из множества маленьких (около 4 Кбайт) фрагментов памяти. Память в разделяемом пуле управляется по принципу давности использования (LRU). В этом отношении она похожа на буферный кэш: если фрагмент не используется, он теряется. Стандартный пакет DBMS_SHARED_POOL позволяет изменить это и принудительно закрепить объекты в разделяемом пуле. Это позволяет загрузить часто используемые процедуры и пакеты при запуске сервера и сделать так, чтобы они не выбрасывались из пула как устаревшие. Обычно, если в течение определенного периода времени фрагмент памяти в разделяемом пуле не использовался, он выбрасывается как устаревший. Даже PL/SQL-код, который может иметь весьма большой размер, управляется механизмом постраничного устаревания, так что при выполнении кода очень большого пакета необходимый код загружается в разделяемый пул небольшими фрагментами. Если в течение продолжительного времени он не используется, то в случае переполнения выбрасывается из разделяемого пула, а пространство выделяется для других объектов. Самый простой способ поломать механизм разделяемого пула Oracle - не 18 использовать связываемые переменные. Отказавшись от использования связываемых переменных, можно "поставить на колени" любую систему, поскольку: • система будет тратить много процессорного времени на разбор запросов; • система будет тратить очень много ресурсов на управление объектами в разделяемом пуле, т.к. не предусмотрено повторное использование планов выполнения запросов. Если каждый переданный серверу Oracle запрос специфичен, с жестко заданными константами, это вступает в противоречие с назначением разделяемого пула. Разделяемый пул создавался для того, чтобы хранящиеся в нем планы выполнения запросов использовались многократно. Если каждый запрос - абсолютно новый и никогда ранее не встречался, в результате кэширования только расходуются дополнительные ресурсы. Разделяемый пул начинает снижать производительность. Обычно эту проблему пытаются решить, увеличивая разделяемый пул, но в результате становится еще хуже. Разделяемый пул снова неизбежно заполняется, и его поддержка требует больших ресурсов, чем поддержка маленького разделяемого пула, поскольку при управлении большим заполненным пулом приходится выполнять больше действий, чем при управлении маленьким заполненным пулом. Единственным решением этой проблемы является применение разделяемых операторов SQL, которые используются повторно. Существует параметр инициализации CURSOR_SHARING, который можно использовать для частичного решения подобных проблем, но наиболее эффективное решение - применять повторно используемые SQLоператоры. Даже самые большие из крупных систем требуют от 10000 до 20000 уникальных SQL-операторов. В большинстве систем используется лишь несколько сотен уникальных запросов. Большой пул Большой пул назван так не потому, что это "большая" структура (хотя его размер вполне может быть большим), а потому, что используется для выделения больших фрагментов памяти - больших, чем те, для управления которыми создавался разделяемый пул. Большой пул - это область памяти, управляемая по принципу пула RECYCLE. После освобождения фрагмента памяти он может использоваться другими процессами. Большой пул, в частности, используется: • сервером в режиме MTS для размещения области UGA в SGA; • при распараллеливании выполнения операторов - для буферов сообщений, которыми обмениваются процессы для координации работы серверов; • в ходе резервного копирования для буферизации дискового ввода/вывода утилиты RMAN. Буфер журнала повторного выполнения Буфер журнала повторного выполнения используется для временного кэширования данных активного журнала повторного выполнения перед записью на диск. Поскольку перенос данных из памяти в память намного быстрее, чем из памяти - на диск, использование буфера журнала повторного выполнения позволяет существенно ускорить работу сервера. Данные не задерживаются в буфере журнала повторного выполнения надолго. Содержимое этого буфера сбрасывается на диск: 19 раз в три секунды; при фиксации транзакции; при заполнении буфера на треть или когда в нем оказывается 1 Мбайт данных журнала повторного выполнения. Поэтому создание буфера журнала повторного выполнения размером в десятки Мбайт - напрасное расходование памяти. Чтобы использовать буферный кэш журнала повторного выполнения размером 6 Мбайт, например, надо выполнять продолжительные транзакции, генерирующие по 2 Мбайт информации повторного выполнения не более чем за три секунды. Если кто-либо в системе зафиксирует транзакцию в течение этих трех секунд, в буфере не будет использовано и 2 Мбайт, - содержимое буфера будет регулярно сбрасываться на диск. Лишь очень немногие приложения выиграют от использования буфера журнала повторного выполнения размером в несколько мегабайт. Стандартный размер буфера журнала повторного выполнения, задаваемый параметром LOG_BUFFER в файле init.ora, определяется как максимальное из значений 512ти (128 * количество процессоров) Кбайт. Минимальный размер этой области равен максимальному размеру блока базы данных для соответствующей платформы, умноженному на четыре. Java-пул Java-пул - создан для поддержки работы Java-машины в базе данных. Если поместить хранимую процедуру на языке Java или компонент EJB (Enterprise JavaBean) в базу данных, сервер Oracle будет использовать этот фрагмент памяти при обработке соответствующего кода. Java-пул виден в представлении V$SGASTAT, а также в результатах выполнения команды SHOW SGA. Параметр инициализации JAVA_POOL_SIZE используется для определения фиксированного объема памяти, отводящегося для Java-кода и данных сеансов. Java-пул используется по-разному, в зависимости от режима работы сервера Oracle. В режиме выделенного сервера Java-пул включает разделяемую часть каждого Java-класса, использованного хоть в одном сеансе. Эти части только читаются (векторы выполнения, методы и т.д.) и имеют для типичных классов размер от 4 до 8 Кбайт. Таким образом, в режиме выделенного сервера (который, как правило, и используется, если в приложениях применяются хранимые процедуры на языке Java) объем общей памяти для Java-пула весьма невелик; его можно определить исходя из количества используемых Java-классов. Информация о состоянии сеансов при работе в режиме разделяемого сервера в области SGA не сохраняется, поскольку эти данные находятся в области UGA, а она, в режиме разделяемого сервера является частью области PGA. При работе в режиме MTS Java-пул включает: • разделяемую часть каждого Java-класса и • часть области UGA для каждого сеанса, используемую для хранения информации о состоянии сеансов. Оставшаяся часть области UGA выделяется как обычно - из разделяемого пула или из большого пула, если он выделен. Поскольку общий размер Java-пула фиксирован, разработчикам приложений необходимо оценить общий объем памяти для приложения и умножить на предполагаемое количество одновременно поддерживаемых сеансов. Полученное значение будет определять общий размер Java-пула. Каждая Java-часть области UGA будет увеличиваться и 20 уменьшаться при необходимости, но помните, что размер пула должен быть таким, чтобы части всех областей UGA могли поместиться в нем одновременно. В режиме MTS может потребоваться очень большой Java-пул. Его размер будет зависеть не от количества используемых классов, а от количества одновременно работающих пользователей. Как и большой пул, размеры которого становятся очень большими в режиме MTS, Java-пул тоже может разрастаться до огромных размеров. Процессы В экземпляре Oracle есть три класса процессов. • Серверные процессы. Они выполняют запросы клиентов. • Фоновые процессы. Это процессы, которые начинают выполняться при запуске экземпляра и решают различные задачи поддержки базы данных, такие как запись блоков на диск, поддержка активного журнала повторного выполнения, удаление прекративших работу процессов и т.д. • Подчиненные процессы. Они подобны фоновым процессам, но выполняют, корме того, действия от имени фонового или серверного процесса. Серверные процессы Серверные процессы решают задачу: обрабатывают передаваемые им SQLоператоры. При получении запроса именно серверный процесс Oracle будет разбирать его и помещать в разделяемый пул (или находить соответствующий запрос в разделяемом пуле). Именно этот процесс создает план выполнения запроса. Этот процесс реализует план запроса, находя необходимые данные в буферном кэше или считывая данные в буферный кэш с диска. Такие серверные процессы можно назвать "рабочими лошадками" СУБД. Часто именно они потребляют основную часть процессорного времени в системе, поскольку выполняют сортировку, суммирование, соединения - в общем, почти все. В режиме выделенного сервера (dedicated) имеется однозначное соответствие между клиентскими сеансами и серверными процессами (или потоками). Если имеется 100 сеансов на UNIX-машине, будет 100 процессов, работающих от их имени. Другой тип серверных процессов - разделяемый серверный процесс (shared) или режим многопотокового сервера (MTS - multi-threaded server). Для подключения к серверному процессу этого типа обязательно используется протокол Net, даже если клиент и сервер работают на одной машине. Клиентское приложение подключается к процессу прослушивания Net и перенаправляется на процесс-диспетчер. Диспетчер играет роль канала передачи информации между клиентским приложением и разделяемым серверным процессом. Клиентские приложения со скомпонованными в них библиотеками Oracle физически подключаются к диспетчеру MTS. Диспетчеров MTS для любого экземпляра можно сгенерировать несколько, но часто для сотен и даже тысяч пользователей используется один диспетчер. Диспетчер отвечает за получение входящих запросов от клиентских приложений и их размещение в очереди запросов в области SGA. Первый свободный разделяемый серверный процесс, по сути, ничем не отличающийся от выделенного серверного процесса, выберет запрос из очереди и подключится к области UGA соответствующего сеанса. Разделяемый сервер обработает запрос и поместит полученный при его выполнении результат в очередь ответов. Диспетчер постоянно следит за появлением результатов в очереди и передает их клиентскому приложению. С точки зрения клиента нет никакой разницы между подключением к выделенному серверу и подключением в режиме MTS, - они работают одинаково. Различие возникает только на уровне экземпляра. 21 Режим выделенного сервера проще настроить и он обеспечивает самый простой способ подключения. При этом требуется минимальное конфигурирование. Настройка и конфигурирование режима MTS, хотя и несложный, но дополнительный шаг. Основное различие между этими режимами, однако, не в настройке. Оно связано с особенностями работы. При использовании выделенного сервера имеется соответствие один к одному между клиентским сеансом и серверным процессом. В режиме MTS соответствие - многие к одному (много клиентов и один разделяемый сервер). Как следует из названия, разделяемый сервер общий ресурс, а выделенный - нет. При использовании общего ресурса необходимо стараться не монополизировать его надолго. Правило номер один для режима MTS: убедитесь, что транзакции выполняются быстро. Они могут выполняться часто, но должны быть короткими . В противном случае будут наблюдаться все признаки замедления работы системы из-за монополизации общих ресурсов несколькими процессами. В экстремальных случаях, если все разделяемые серверы заняты, система "зависает". Режим MTS не подходит, однако, для хранилища данных. В такой системе выполняются запросы продолжительностью одна, две, пять и более минут. Для режима MTS это "смертельно". В системе, где 90 процентов задач относятся к классу ООТ (системы оперативной обработки транзакций).а 10 процентов - "не совсем ООТ", можно поддерживать одновременно выделенные и разделяемые серверы в одном экземпляре. В этом случае существенно сокращается количество процессов для пользователей ООТ, а "не совсем ООТ"-задачи не монополизируют надолго разделяемые серверы. Итак, какие же преимущества дает режим MTS, если учитывать, для какого типа транзакций он предназначен? Режим MTS позволяет добиться следующего. - - Сократить количество процессов/потоков операционной системы. В системе с тысячами пользователей ОС может быстро оказаться перегруженной при попытке управлять тысячами процессов. В обычной системе одновременно активна лишь небольшая часть этих тысяч пользователей. Искусственно ограничить степень параллелизма. . 22 Рис.3 На рис.3 представлена диаграмма, показывающая зависимость количества транзакций от количества одновременно работающих пользователей: Сначала при добавлении одновременно работающих пользователей количество транзакций растет. С какого-то момента, однако, добавление новых пользователей не увеличивает количества выполняемых в секунду транзакций: оно стабилизируется. Пропускная способность достигла максимума, и время ожидания ответа начинает расти (каждую секунду выполняется то же количество транзакций, но пользователи получают результаты со все возрастающей задержкой. При дальнейшем добавлении пользователей пропускная способность начинает падать. Количество одновременно работающих пользователей перед началом этого падения и является максимально допустимой степенью параллелизма в системе. Дальше система переполняется запросами, и образуются очереди. С этого момента система не справляется с нагрузкой. Не только существенно увеличивается время ответа, но и начинает падать пропускная способность системы. Если ограничить количество одновременно работающих пользователей до числа, непосредственно предшествующего падению, можно обеспечить максимальную пропускную способность и приемлемое время ответа для большинства пользователей. Режим MTS позволяет ограничить максимальную степень параллелизма в системе до этого количества одновременно работающих пользователей. - Сократить объем памяти, необходимый системе. Это одна из наиболее часто упоминаемых причин использования режима MTS: сокращается объем памяти, необходимой для поддержки определенного количества пользователей. При использовании режима MTS область UGA помещается в SGA. Это означает, что при переходе на режим MTS необходимо точно оценить суммарный объем областей UGA и выделить место в области SGA с помощью параметра инициализации LARGE_POOL Поэтому размер области SGA при использовании режима MTS обычно очень большой. Эта память выделяется заранее и поэтому может использоваться только СУБД. Сравните это с режимом разделяемого сервера, когда процессы могут использовать любую область памяти, не выделенную под SGA. Итак, если область SGA становится намного больше вследствие размещения в ней областей UGA, каким же образом экономится память? Экономия связана с уменьшением количества выделяемых областей PGA. Каждый выделенный/разделяемый сервер имеет область PGA. В ней хранится информация процесса. В ней располагаются области сортировки, области хешей и другие структуры процесса. Именно этой памяти для системы надо меньше, если используется режим MTS. При переходе с 5000 выделенных серверов на 100 разделяемых освобождается 4900 областей PGA - именно такой объем памяти и экономится в режиме MTS. Фоновые процессы Монитор процессов (PMON).Этот процесс отвечает за очистку после нештатного прекращения подключений. Например, если выделенный сервер "падает" или, получив сигнал, прекращает работу, именно процесс PMON освобождает ресурсы. Процесс PMON откатит незафиксированные изменения, снимет блокировки и освободит ресурсы в области 23 SGA, выделенные прекратившему работу процессу. Помимо очистки после прерванных подключений, процесс PMON контролирует другие фоновые процессы сервера Oracle и перезапускает их при необходимости (если это возможно). Если разделяемый сервер или диспетчер сбоит (прекращает работу), процесс PMON запускает новый процесс (после очистки структур сбойного процесса). Процесс PMON следит за всеми процессами Oracle и либо перезапускает их, либо прекращает работу экземпляра, в зависимости от ситуации. Например, в случае сбоя процесса записи журнала повторного выполнения (LGWR) экземпляр надо перезапускать. Это серьезная ошибка и самое безопасное - немедленно прекратить работу экземпляра, предоставив исправление данных штатному процессу восстановления. Монитор системы (SMON). Это процесс, занимающийся всем тем, от чего "отказываются" остальные процессы. Это своего рода "сборщик мусора" для базы данных. Вот некоторые из решаемых им задач. - Очистка временного пространства. Например, при построении индекса выделяемые ему в ходе создания экстенты помечаются как временные (TEMPORARY). Если выполнение оператора CREATE INDEX прекращено досрочно по какой-либо причине, процесс SMON должен эти экстенты освободить. Есть и другие операции, создающие временные экстенты, за очистку которых также отвечает процесс SMON. - Восстановление после сбоев. Процесс SMON после сбоя восстанавливает экземпляр при перезапуске. - Восстановление транзакций, затрагивающих недоступные файлы. Эта задача аналогична той, которая возникает при запуске базы данных. Процесс SMON восстанавливает сбойные транзакции, пропущенные при восстановлении экземпляра после сбоя по причине недоступности файлов для восстановления. Например, файл мог быть на недоступном или на не смонтированном диске. Когда файл будет доступен, процесс SMON восстановит его. - Очистка таблицы OBJ$. OBJ$ - низкоуровневая таблица словаря данных, содержащая записи практически для каждого объекта (таблицы, индекса, триггера, представления и т.д.) базы данных. Часто там встречаются записи, представляющие удаленные или отсутствующие" объекты, используемые механизмом поддержки зависимостей Oracle. Процесс SMON удаляет эти ненужные строки. - Сжатие сегментов отката. Процесс SMON автоматически сжимает сегмент отката до заданного размера. - "Отключение"сегментов отката. Администратор базы данных может "отключить" или сделать недоступным сегмент отката с активными транзакциями. Активные транзакции могут продолжать использование такого отключенного сегмента отката. В этом случае сегмент отката фактически не отключается: он помечается для "отложенного отключения". Процесс SMON периодически пытается "действительно" отключить его, пока это не получится. - Процесс контрольной точки (CKPT). Процесс CKPT передает сигнал процессу DRWn о контрольной точке, обновляя информацию о контрольной точке в заголовках файлов данных и управляющих файлах. Он запускается всегда. - Запись блоков базы данных (DBWn). Процесс записи блоков базы данных (Database Block Writer - DBWn) - фоновый процесс, отвечающий за запись измененных блоков на диск. В кэше буферов серверный процесс собирает изменения, внесенные в блоки отмены и в блоки данных. Процесс записи в базу данных (DBWn) записывает модифицированные буфера из кэша буферов базы данных в файлы данных. Он обеспечивает наличие в кэше 24 буферов базы данных достаточного числа свободных буферов – буферов, которые могут быть переписаны, когда серверный процесс должен считать блоки из файлов данных. Это улучшает производительность базы данных, так как серверные процессы вносят изменения только в кэш буферов, а DBWn откладывает запись в файлы базы данных до возникновения одного из следующих событий: контрольная точка; число модифицированных буферов достигает порогового значения; какой либо процесс не находит свободных буферов; истекло время ожидания; перевод постоянного или временного табличного пространства в автономный режим; перевод табличного пространства в режим “только чтение”; удаление или усечение таблицы; начало горячего резервирования табличного пространства по команде ALTER TABLESPACE … BEGIN BACKUP. В большинстве систем работает только один процесс записи блоков базы данных, но в больших, многопроцессорных системах имеет смысл использовать несколько. Если сконфигурировано более одного процесса DBWn, не забудьте также увеличить значение параметра инициализации DB_BLOCK_LRU_LATCHES. Он определяет количество защелок списков по давности использования, touch lists. Каждый процесс DBWn должен иметь собственный список. Если несколько процессов DBWn совместно используют один список блоков для записи на диск, они будут конфликтовать друг с другом при доступе к списку. Обычно процесс DBWn использует асинхронный ввод/вывод для записи блоков на диск. При использовании асинхронного ввода/вывода процесс DBWn собирает пакет блоков для записи и передает его операционной системе. Процесс DBWn не ждет, пока ОС запишет блоки, - он собирает следующий пакет для записи. Завершив асинхронную запись, ОС уведомляет об этом процесс DBWn. Это позволяет процессу DBWn работать намного быстрее, чем при последовательном выполнении действий - Запись журнала (LGWR). Процесс LGWR отвечает за сброс на диск содержимого буфера журнала повторного выполнения, находящегося в области SGA. Он делает это: • раз в три секунды; • при фиксации транзакции; • при заполнении буфера журнала повторного выполнения на треть или при записи в него 1 Мбайт данных. Поэтому создание слишком большого буфера журнала повторного выполнения не имеет смысла: сервер Oracle никогда не сможет использовать его целиком. Все журналы записываются последовательно, а не вразброс, как вынужден выполнять ввод/вывод процесс DBWn. Запись большими пакетами, как в этом случае, намного эффективнее, чем запись множества отдельных блоков в разные части файла. Это одна из главных причин выделения процесса LGWR и журнала повторного выполнения. Эффективность последовательной записи измененных байтов перевешивает расход ресурсов на дополнительный ввод/вывод. Сервер Oracle мог бы записывать блоки данных 25 непосредственно на диск при фиксации, но это потребовало бы записи множества разбросанных блоков, а это существенно медленнее, чем последовательная запись изменений процессом LGWR. - Архивирование (ARCn). Это не обязательный фоновый процесс, запускается, если база данных сконфигурирована в режиме ARCHIVELOG. Задача процесса ARCn копировать в другое место активный файл журнала повторного выполнения, когда он заполняется процессом LGWR. Эти архивные файлы журнала повторного выполнения затем можно использовать для восстановления носителя. Тогда как активный журнал повторного выполнения используется для "исправления" файлов данных в случае сбоя питания (когда прекращается работа экземпляра), архивные журналы повторного выполнения используются для восстановления файлов данных в случае сбоя диска. Если будет потерян диск, содержащий файл данных, можно взять резервные копии за прошлую неделю, восстановить из них старую копию файла и попросить сервер применить активный журнал повторного выполнения и все архивные журналы, сгенерированные с момента создания этой резервной копии. Это "подтянет" файл по времени к остальным файлам в базе данных, и можно будет продолжить работу без потери данных. Процесс ARCn обычно копирует активный журнал повторного выполнения в несколько мест (избыточность - гарантия сохранности данных!). Это могут быть диски на локальной машине или, что лучше, на другой машине, на случай катастрофического сбоя. Они также могут отправляться на другую машину для применения к резервной базе данных (это одно из средств защиты от сбоев, предлагаемое Oracle). Подчиненные процессы В сервере Oracle есть два типа подчиненных процессов - ввода/вывода (I/O slaves) и параллельных запросов (Parallel Query slaves). Подчиненные процессы ввода/вывода Подчиненные процессы ввода/вывода используются для эмуляции асинхронного ввода/вывода в системах или на устройствах, которые его не поддерживают. Например, ленточные устройства (чрезвычайно медленно работающие) не поддерживают асинхронный ввод/вывод. Используя подчиненные процессы ввода/вывода, можно сымитировать для ленточных устройств такой способ работы, который операционная система обычно обеспечивает для дисков. Как и в случае действительно асинхронного ввода/вывода, процесс, записывающий на устройство, накапливает большой объем данных в виде пакета и отправляет их на запись. Об их успешной записи процесс (на этот раз - подчиненный процесс ввода/вывода, а не ОС) сигнализирует исходному вызвавшему процессу, который удаляет этот пакет из списка данных, ожидающих записи. Таким образом, можно существенно повысить производительность, поскольку именно подчиненные процессы ввода/вывода ожидают завершения работы медленно работающего устройства, а вызвавший их процесс продолжает выполнять другие важные действия, собирая данные для следующей операции записи. Подчиненные процессы ввода/вывода используются в нескольких компонентах сервера - процессы DBWn и LGWR используют их для имитации асинхронного ввода/вывода, а утилита RMAN (Recovery MANager - диспетчер восстановления) использует их при записи на ленту. Использование подчиненных процессов ввода/вывода управляется двумя параметрами инициализации. 26 BACKUP_TAPE_IO_SLAVES. Этот параметр указывает, используются ли подчиненные процессы вода/вывода утилитой RMAN для резервного копирования или восстановления данных с ленты. Поскольку этот параметр предназначен для ленточных устройств, а к ленточным устройствам в каждый момент времени может обращаться только один процесс, он - булева типа, а не задает количество используемых подчиненных процессов, как можно было ожидать. Утилита RMAN запускает необходимое количество подчиненных процессов, в соответствии с количеством используемых физических устройств. Если параметр BACKUP_TAPE_IO_SLAVES имеет значение TRUE, то для записи или чтения с ленточного устройства используется подчиненный процесс ввода/вывода. Если этот параметр имеет (стандартное) значение FALSE, подчиненные процессы ввода/вывода не используются при резервном копировании. К ленточному устройству тогда обращается фоновый процесс, выполняющий резервное копирование. DBWn_IO_SLAVES. Задает количество подчиненных процессов ввода/вывода, используемых процессом DBWn. Процесс DBWn и его подчиненные процессы всегда записывают на диск измененные буфера буферного кэша. По умолчанию этот параметр имеет значение 0, и подчиненные процессы ввода/вывода не используются. Подчиненные процессы параллельных запросов В Oracle есть средства распараллеливания запросов к базе данных. Речь идет о возможности создавать для SQL-операторов типа SELECT, CREATE TABLE, CREATE INDEX, UPDATE и т.д. план выполнения, состоящий из нескольких планов, которые можно выполнять одновременно. Результаты выполнения этих планов объединяются. Это позволяет выполнить операцию за меньшее время, чем при последовательном выполнении. Например, если имеется большая таблица, разбросанная по десяти различным файлам данных, 16процессорный сервер, и необходимо выполнить к этой таблице запрос, имеет смысл разбить план выполнения этого запроса на 16 небольших частей и полностью использовать возможности сервера. Это принципиально отличается от использования одного процесса для последовательного чтения и обработки всех данных. Управление безопасностью БД Чтобы получить доступ к базе данных, необходимо указать правильное имя пользователя и успешно пройти процедуру аутентификации. Администратор базы данных определяет имена пользователей, которым разрешен доступ к базе данных. В некоторых системах для каждого пользователя имеется своя собственная учетная запись, в других системах многие пользователи совместно используют общую учетную запись. На Рис.4 приведены способы управления безопасностью базы данных. 27 Рис.4 Механизм аутентификации Аутентификация означает проверку кого-то или чего-то (пользователя, устройства или других объектов), кто хочет использовать данные, ресурсы или приложения. Она производится для установления доверительного отношения перед дальнейшим взаимодействием. Кроме того аутентификация предоставляет возможность вести учет доступа к ресурсам и объектам базы данных. После аутентификации процесс авторизации может предоставить или ограничить привилегии на доступ или на действия, разрешенные данному пользователю. Наиболее общий метод аутентификации – это использование пароля, когда пользователь создается вместе с паролем. Этот пароль должен указываться пользователем при попытке установить соединение. Когда администратор назначает пароль, он должен сразу же установить для него истечение срока годности (expire). Это заставит пользователя изменить пароль при первом соединении. Квоты табличных пространств Квоты табличных пространств управляют объемом физической памяти, выделенной пользователю в табличных пространствах базы данных. Квота должна быть предоставлена перед тем, как пользователь сможет создавать объекты в табличном пространстве. Квоты могут быть: 28 неограниченные (unlimited) – разрешение пользователю использовать столько пространства, сколько будет доступно в табличном пространстве. Конкретные, заданные в мегабайтах или килобайтах – размер пространства, которое может быть занято пользователем. Это не гарантирует, что пространство специально зарезервировано для пользователя. Значение может быть больше ил меньше, чем текущий размер памяти, доступный в табличном пространстве. Системная привилегия UNLIMITED TABLESPACE имеет приоритет над всеми отдельными квотами и предоставляет пользователю неограниченную квоту на все табличные пространства. Табличное пространство по умолчанию В нем пользователи создают свои объекты, если при создании пользователя не указано другое табличное пространство. Однако, наличие такого табличного пространства не подразумевает, что имеет пользователь имеет привилегию создания объектов в этом табличном пространстве и что у него есть квота для использования этого табличного пространства при создании объектов. Обе эти возможности предоставляются отдельно. Временное табличное пространство Это пространство, где будут создаваться временные объекты: - для сортировки, - для временных таблиц. Блокирование входа пользователя в систему Для запрета соединения с базой данных, можно заблокировать вход пользователя в систему. Блокирование и разблокирование входа пользователя в систему может выполняться как автоматически, так и вручную администратором базы данных. Ресурсные ограничения Можно наложить ограничения на использование ресурсов системы. Например, на время использования центрального процессора, логический ввод/вывод или количество сеансов, открытых пользователем. Привилегии Привилегии используются для управления правами пользователя на выполнение каких-либо действий в базе данных. Привилегия - это право на выполнение конкретной команды SQL или право доступа к объектам других пользователей. Oracle предоставляет возможность дифференцированного контроля разрешенных и запрещенных операций в базе данных. Привилегии делятся на две категории: системные и объектные. Системные привилегии (system privileges) Каждая системная привилегия, представленная пользователю, позволяет ему выполнять в базе данных конкретные операции или классы операций. Например, создание табличных пространств – это системная привилегия. Системные привилегии могут быть предоставлены администратором или кем-то, кому явно предоставлены права по сопровождению привилегий. Имеется более 100 системных привилегий. 29 Объектные привилегии (object privileges) Каждая объектная привилегия, выданная пользователю, позволяет ему выполнять конкретные действия над определенным объектом (например, таблицей, представлением, последовательностью, процедурой, функцией или пакетом). Без специального разрешения пользователи имеют доступ только к своим собственным объектам. Объектные привилегии могут быть предоставлены владельцем объекта или кем-то, кому явно предоставлено право выдавать привилегии на объект. Роли Роль - это набор привилегий, который можно предоставить пользователям и другим ролям. Пользователю можно предоставить привилегии неявно, через роли. Привилегии могут быть добавлены к роли, а роль затем выдана пользователю. Пользователь может включить роль и воспользоваться привилегиями, предоставленными ролью. Роль содержит все привилегии, включенные в эту роль, а также привилегии других ролей, предоставленных этой роли. В большинстве систем выделение по отдельности необходимых пользователю привилегий может занять много времени и вызвать с высокой вероятностью появление ошибок. Oracle предоставляет возможность простого и контролируемого сопровождения привилегий с помощью ролей. Роли – это именованные группы привилегий, которые предоставляются пользователям или другим ролям. Они разработаны для облегчения сопровождения привилегий в базе данных. Роли по умолчанию обычно включены. Это означает, что когда роль предоставлена пользователю, он может воспользоваться привилегиями этой роли. Возможно и следующее: Сделать роль не по умолчанию. При выделении роли пользователю убрать отметку в поле DEFAULT. Теперь пользователь должен явно включать роль перед тем, как станут доступны привилегии роли. Потребовать дополнительную аутентификацию роли. По умолчанию дополнительная аутентификация отключена (NONE), однако возможно задание дополнительной аутентификации перед динамическим включением роли. Создание ”роли приложения”, включение которой возможно только в определенной хранимой процедуре. В этой процедуре можно, например, проверить сетевой адрес пользователя, имя программы, выполняемой пользователем, время дня или что-то другое для обеспечения должной безопасности при выделении группы полномочий. Профиль Профиль – именованный набор накладываемых на пароли и системные ресурсы ограничений: cрок действия паролей и его истечение, хронология паролей, проверка сложности паролей, блокирование входа в систему, процессорное время, операции ввода/вывода, время простоя, 30 время соединения, число параллельных сеансов, объем памяти. После создания профиля администратор базы данных может назначить его каждому пользователю. Если включены ресурсные ограничения , то сервер Oracle ограничивает использование базы данных и ресурсов согласно профилю, назначенному пользователю. После создания базы данных сервер Oracle автоматически создает профиль DEFAULT. Все ресурсные ограничения пользователей, которым явно не назначены никакие профили, устанавливаются профилем по умолчанию (DEFAULT). Первоначально все ресурсные ограничения профиля DEFAULT устанавливаются как UNLIMITED. Однако администратор базы данных может изменить эти ресурсные ограничения, и они будут применены ко всем пользователям, имеющим профиль DEFAULT. Аудит Аудит – наблюдение за выбранными действиями пользователя базы данных. Используется для получения сведений о подозрительных операциях с базой данных и для сбора информации об отдельных операциях в базе данных. База данных Oracle предоставляет три вида аудита. Администратор может выполнить аудит всех действий внутри базы данных. Однако перехват и сохранение информации о том, что происходит, увеличивает затраты системных ресурсов. Аудит следует сфокусировать только на отдельных событиях, представляющих интерес. Такой аудит оказывает минимальное влияние на производительность. Неточно установленный аудит может повлиять на производительность. Стандартный аудит базы данных собирает следующую информацию: какое событие произошло, когда, какой пользователь вызвал появление события, на какой клиентской машине он работал и когда. Аудит по значениям регистрирует изменение данных (вставки, изменения и удаления). Такой аудит расширяет стандартный аудит БД, фиксируя не только произошедшее событие, но и фактические значения, которые были вставлены, изменены или удалены. Он реализуется с помощью триггеров базы данных. Дифференцированный аудит (fine-grained auditing – FGA) применяется для наблюдения за командами SQL. FGA расширяет стандартный аудит БД, регистрируя выполняемые команды SQL, а не только происходящие события. Стандартный аудит Прежде всего, необходимо установить статический параметр инициализации AUDIT_TRAIL. Его значение показывает, куда направляются записи аудита. Обычное значение этого параметра DB приводит к записи данных аудита в представление DBA_AUDIT_TRAIL. Аудит базы данных может фиксировать информацию о событиях соединения с БД, об использовании системных и объектных привилегий. Аудит может быть сфокусирован на действиях конкретных пользователей , на успешных и неудачных событиях . Варианты задания аудита: Аудит команд SQL. Например, команда AUDIT TABLE; 31 вызовет аудит любой команды DDL, связанной с таблицей: CREATE TABLE, DROP TABLE, TRUNCATE TABLE и т.д. Такой вид аудита можно сфокусировать на конкретном пользователе и удачном (SUCCESSFUL) или неудачном (NOT SUCCESSFUL) завершении операции. AUDIT TABLE BY hr WHENEVER NOT SUCCESSFUL; Аудит системных привилегий используется для сбора данных об использовании какой-либо системной привилегии (например, DROP ANY TABLE). Он может быть сфокусирован на конкретном пользователе и удачном или неудачном завершении операции. По умолчанию каждое использование системной привилегии генерирует запись аудита. Записи можно группировать таким образом, чтобы одна запись генерировалась для сеанса (так при изменении пользователем 100000 строк в таблице другого пользователя получается только одна запись). Фразу BY SESSION (аудит на уровне сеанса) необходимо задавать, так как по умолчанию действует BY ACCESS (аудит на уровне доступа). AUDIT SELECT ANY TABLE, CREATE ANY TRIGGER; AUDIT SELECT ANY TABLE BY hr BY SESSION; Аудит объектных привилегий применяется для наблюдения за операциями с таблицами, представлениями, последовательностями, объектами directory и пользовательскими типами данных. Такой аудит может быть сфокусирован на удачных и неудачных операциях и фиксироваться на уровне сеанса или каждого обращения. В отличие от аудита системных привилегий по умолчанию действует аудит на уровне сеанса . Если необходимо получить в журнале записи по каждому использованию объекта, следует указывать фразу BY ACCESS. AUDIT ALL BY hr.employees; AUDIT UPDATE, DELETE ON hr.employees BY ACCESS; Аудит сеансов. AUDIT SESSION WHENEVER NOT SUCCESSFUL: Такой аудит может быть сфокусирован на конкретном пользователе и удачном или неудачном завершении операции. Для каждого установленного с экземпляром соединения создается только одна запись аудита, которая заносится в журнал аудита во время соединения и изменяется при отсоединении. В этой записи аудита собирается суммарная информация о сеансе: время соединения, время отсоединения, выполненные логические и физические операции ввода/вывода и т.д. Во многих базах данных принято использовать команду AUDIT SESSION (несфокусированный аудит). Почти во всех базах данных для регистрации попыток проникновения в систему следует использовать AUDIT SESSION WHENEVER NOT SUCCESSFUL; Доступ к записям аудита должен тщательно контролироваться , поскольку в них может содержаться секретная информация. Обычно сопровождение журнала производится администратором , но, если эта задача возлагается на кого-то другого, ему необходимо предоставить роль DELETE_CATALOG_ROLE, чтобы он мог удалять записи из журнала. 32 Аудит по значениям Аудит базы данных не может регистрировать значения столбцов для операций вставки, изменения и удаления. Если необходимо отслеживать изменения столбцов и сохранять значения столбцов для каждого изменения, используйте аудит по значениям. Такой аудит может быть выполнен с помощью триггеров базы данных. При вставке, изменении и удалении данных из таблицы в фоновом режиме прорабатывает соответствующий триггер данной таблицы. Он записывает перехватываемые, изменяемые данные в таблицу, созданную для хранения результатов аудита. Аудит по значениям может вызвать большее снижение производительности, так как триггер должен прорабатывать при каждой вставке, изменении или удалении. Степень снижения производительности зависит от эффективности кода триггера. Аудит по значениям должен использоваться только в ситуациях, когда стандартный аудит базы данных предоставляет недостаточные данные. Дифференцированный аудит Аудит базы данных фиксирует только операцию, но не сохраняет информацию о команде. Дифференцированный аудит (FGA) позволяет регистрировать команды запросов и манипулирования данных языка SQL. FGA – это наиболее точно сфокусированный вид аудита по сравнению с аудитом по значениям и стандартным аудитом. Параметры FGA позволяют сфокусироваться на отдельных столбцах таблицы или представления, задать условия (определяемые администратором спецификации) выполнения аудита. FGA не требует использование триггеров, его влияние на производительность такое же, как и у стандартного аудита БД. Аудит использует пакет PL/SQL DBMS_FGA для создания политики аудита для таблицы или представления. Когда строки блока запроса содержат наблюдаемые столбцы и удовлетворяют условиям аудита, срабатывает событие аудита, в результате которого создается и сохраняется в журнале строка аудита. Дополнительно событие аудита может вызвать выполнение процедуры. FGA автоматически фокусируется на уровне команды, поэтому одна команда SELECT, возвращающая тысячи строк генерирует только одну запись аудита. Пакет DBMS_FGA – это средство администратора для выполнения функций дифференцированного аудита. Чтобы сопровождать политики аудита, необходима привилегия выполнения (EXECUTE) пакета DBMS_FGA. Пакет DBMS_FGA содержит следующие подпрограммы: ADD_POLICY – создание политики аудита с указанием предиката, определяющего условия аудита. DROP_POLICY – удаление политики аудита. ENABLE_POLICY– включение политики аудита. DISABLE_POLICY - выключение политики аудита Записи дифференцированного аудита и аудита объектов или привилегий заносятся в разные таблицы. Представление DBA_FGA_AUDIT_TRAIL обеспечивает доступ к записям дифференцированного аудита. Для определения политик аудита используются следующие представления: ALL_AUDIT_POLICIES – все политики FGA для объектов, к которым текущий пользователь имеет доступ. DBA_AUDIT_POLICIES - все политики FGA в базе данных. 33 USER_AUDIT_POLICIES пользовательской схеме. - все политики FGA для объектов в текущей Создание объектов БД Основной задачей разработчиков баз данных является создание объектов. Совокупность объектов базы данных, принадлежащих конкретному пользователю, образуют схему. Схема имеет то же самое имя, что и пользователь, владеющий этой схемой. Объекты схемы - это логические структуры, которые прямо ссылаются на данные БД. Объектами схем могут быть, например, таблицы, представления и индексы. Не существует связи между табличным пространством и схемой. Объекты одной и той же схемы могут располагаться в различных табличных пространствах, а табличное пространство может хранить объекты различных схем. При именовании имен следует учитывать, что база данных Oracle использует пространства имен для разрешения зависимостей объектов схемы. Когда команда SQL ссылается на объект, Oracle рассматривает содержимое этой команды и размещает объект в соответствующем пространстве имен. Затем Oracle выполнит над объектом операцию, определяемую командой. Если объект не найден в соответствующем пространстве, Oracle возвратит ошибку. В одном пространстве имен находятся следующие объекты: таблицы, представления, последовательности, частные синонимы, автономные процедуры, автономные хранимые функции, пакеты, материализованные представления, определенные пользователем типы. У следующих объектов свое собственное пространство имен: индексы, ограничения, кластеры, триггеры БД, частные связи БД. Поскольку таблицы и представления в одном пространстве имен, они не могут иметь совпадающие имена в одной и той же схеме. А вот таблицы и индексы в разных пространствах имен и поэтому их имена могут совпадать в одной и той же схеме. У каждой схемы базы данных свои пространства собственные имен для объектов, находящихся в этой схеме. Это означает, например, что две таблицы в разных схемах находятся в разных пространствах имен и могут иметь совпадающие имена. Стандартные таблицы и поддержка целостности базы данных Таблицы – основные формы хранения информации в базе данных Oracle. В них хранятся данные, доступные пользователю. Каждая таблица состоит из столбцов и строк. Строки могут храниться в любом порядке, в зависимости от операций, выполняемых с 34 таблицей. Для создания таблицы используется команда CREATE TABLE. Чтобы создать таблицу в собственной схеме, необходимо иметь привилегию CREATE TABLE. Для создания таблице в схеме другого пользователя необходимо иметь системную привилегию CREATE ANY TABLE. Пример создания таблицы: CREATE TABLE hr.employees( Employee_id NUMBER(6), first_name VARCHAR2(20), last_name VARCHAR2(25), email VARCHAR2(25), phone_number VARCHAR2(20), hire_date DATE DEFAULT SYSDATE, job_id VARCHAR2(10), salary NUMBER(8,2), commission_pct NUMBER(2,2), manager_id NUMBER(6), department_id NUMBER(4) ); Целостность данных в базе данных поддерживается с помощью ограничений (constraints). Для установки ограничений на значения, вводимые в столбцы, можно использовать: Ограничение NOT NULL. По умолчанию во всех столбцах таблицы разрешены неопределенные значения (null) . Это означает возможность отсутствия значения в столбце. Ограничение NOT NULL требует, чтобы в столбце таблицы были определенные значения. Например, если установлено ограничение NOT NULL для столбца last_name таблицы employees, то в каждой строке хранится фамилия сотрудника. Ограничение UNIQUE. Ограничение целостности “уникальный ключ” означает, что каждое значение в столбце или наборе столбцов должно быть уникально, т.е. никакие две строки таблицы не должны содержать одинаковые значения в этом столбце или наборе столбцов. Например, ограничение UNIQUE, определенное для столбца email таблицы employees запрещает строки с повторяющимися адресами электронной почты. Ограничение PRIMARY KEY. Главный ключ (PRIMARY KEY) у каждой таблицы может быть только один. Это ограничение обеспечивает уникальность значения столбца или комбинации столбцов и поэтому значение первичного ключа уникально идентифицирует каждую строку таблицы. Oracle, реализуя ограничение PRIMARY KEY гарантирует, что: - в таблице нет двух строк с повторяющимися значениями в столбце или столбцах главного ключа; и, что столбцы главного ключа должны всегда содержать определенные значения в каждой строке таблицы. Oracle поддерживает ограничение PRIMARY KEY с помощью индекса. Например, главный ключ таблицы employees, включающий столбец employee_id , поддерживается путем неявного создания: - уникального индекса для этого столбца: ограничения NOT NULL для этого столбца. 35 Ограничение ссылочной целостности. Различные таблицы в базе данных могут связываться на основе общих столбцов. БД поддерживает правила ссылочной целостности, гарантируя связи между таблицами. Ограничения ссылочной целостности требуют, чтобы значение внешнего ключа (foreign key) в каждой строке таблицы соответствовало значению главного ключа. Например, внешний ключ в таблице employees - это столбец department_id. Он гарантирует, что каждое значение в этом столбце соответствует значению главного ключа таблицы departments (столбец department_id.). Поэтому в таблице employees не может быть отделов с неверными номерами в столбце department_id.. Ограничение CHECK задает условие, которое должно быть верно (true) или неопределенно для каждой строки. Когда в результате команды DML нарушается условие в ограничении CHECK (false), команда откатывается. Ограничение целостности Foreign Key При сопровождении таблиц, связанных отношением внешнего ключа, необходимо рассмотреть несколько факторов. Перед удалением главной таблицы должен быть удален внешний ключ. Чтобы выполнить оба действия с помощью одного оператора, используется команда: DROP TABLE table CASCADE CONSTRAINTS Главная таблица не может быть очищена без удаления или отключения внешнего ключа. Перед удалением табличного пространства, содержащего главную таблицу, должен быть удален внешний ключ. Для этого используется следующая команда: DROP TABLESPACE табличное пространство INCLUDING CONTENTS CASCADE CONSTRAINTS Если при удалении строк из главной таблицы не используется параметр DELETE CASCADE, то сервер Oracle проверяет, существуют ли в подчиненной таблице строки с соответствующим внешним ключом. Аналогично, обновление в главной таблице разрешается только тогда, когда отсутствуют подчиненные строки со старым значением ключа. Состояния ограничений целостности Ограничения целостности может быть включенным (ENABLE) или выключенным (DISABLE). Если ограничение включено, данные проверяются пи выполнении операций ввода и изменений в базе данных. Ограничения препятствуют занесению информации, не удовлетворяющей условиям, заданным в ограничении. Если ограничение выключено, данные, не удовлетворяющие условиям ограничения, могут быть занесены в базу данных. Ограничение целостности может находиться в одном из следующих состояний: DISABLE NOVALIDATE – отключенное непроверенное; DISABLE VALIDATE - отключенное проверенное; ENABLE NOVALIDATE - включенное непроверенное; ENABLE VALIDATE- включенное проверенное; Отключенное непроверенное ограничение не проверялось, хотя определение ограничения хранится в базе данных. Данные, как находящиеся в таблице, так и вводимые заново, могут не подчиняться правилам, определяемым ограничением. 36 Если ограничение находится в состоянии “отключенное проверенное”, то запрещены все изменения ограниченных колонок. Кроме того, удален индекс для этого ограничения и ограничение отключено. Если ограничение находится в состоянии “включенное непроверенное“, то невозможно ввести новые данные, нарушающие эти ограничение. Однако в таблице могут содержаться некорректные данные, т.е. данные, нарушающие это ограничение. Включенное проверенное состояние ограничения целостности означает, что все данные в таблице гарантированно подчиняются определяемому этим ограничением правилу. Кроме того, это состояние предотвращает ввод некорректных данных. Однако, если ограничение выключено, строки нарушающие его, могут быть введены в таблицы базы данных. При включении ограничения эти строки приводят к возникновению состояния исключения, и ограничение остается выключенным. Строки, нарушающие ограничение, должны быть удалены или изменены соответствующим образом, чтобы можно было перевести ограничение во включенное состояние. Когда ограничение из отключенного состояния переходит во включенное проверенное, таблица блокируется, и все данные этой таблицы проверяются на соответствие правилу, определяемому ограничением. Это может привести к блокированию операций DML, таких как загрузка данных, поэтому рекомендуется сначала переводить ограничение в состояние “включенное непроверенное“, а затем – во “включенное проверенное“. Переходы между этими состояниями подчиняются следующим правилам: Указание только ENABLE подразумевает VALIDATE, если не указано NOVALIDATE. Указание только DISABLE подразумевает NOVALIDATE, если не указано VALIDATE. Для VALIDATE и NOVALIDATE нет подразумеваемого по умолчанию состояния ENABLE и DISABLE. Когда уникальный или первичный ключ переводится из состояния DISABLE.в ENABLE и не существует подходящего индекса, автоматически создается уникальный индекс. Когда любое ограничение переводится из состояния NOVALIDATE в VALIDATE, все данные проверяются. Проверка ограничений Точка транзакции, в которой проверяется ограничение целостности, задается соответствующим определением этого ограничения. Немедленные (IMMEDIATE) проверки ограничений выполняются в конце каждого оператора DML. Нарушение ограничения вызывает откат соответствующего оператора. Если ограничение приводит к такому действию, как, например, каскадное удаление (delete cascade), то это действие выполняется как часть вызвавшего его оператора. Если задано ограничение с немедленной проверкой, то невозможно отложить эту проверку в конец транзакции. Отложенные (DEFERRED) проверки ограничений выполняются только во время фиксации транзакции. Если во время фиксации обнаруживаются какие-либо нарушения ограничений, то выполняется откат всей транзакции. Такие проверки ограничений особенно удобны, когда одновременно вводятся главные и подчиненные строки внешнего ключа. Ограничение, определенное как откладываемое (DEFERRABLE), может быть задано: 37 - - изначально с немедленной проверкой (INITIALLY IMMEDIATE) – т.е. по умолчанию функционирует как ограничение с немедленной проверкой, пока не будет явно указано иное: изначально с отложенной проверкой (INITIALLY DEFERRED) – т.е. по умолчанию это ограничение проверяется только в конце транзакции. Изменение режима выполнения откладываемых ограничений Команда SET CONSTRAINTS переводит ограничения данной транзакции в состояние DEFERRED (отложенное) или IMMEDIATE (немедленные). Можно использовать эту команду, чтобы установить режим для всех или определенных в списке ограничений. Установленный в команде SET CONSTRAINTS режим действует в течении транзакции или до другой SET CONSTRAINTS, в которой переустанавливается режим. Команду SET CONSTRAINTS нельзя использовать в триггерах. В команде ALTER SESSION также имеется фраза (SET CONSTRAINTS) для задания режима ограничений, проверку которых можно отложить. Фраза применяется для перевода в определенный режим DEFERRED или IMMEDIATE всех ограничений (список имен ограничений указать нельзя). Действие команды ALTER SESSION SET CONSTRAINTS распространяется только на текущую транзакцию. ALTER SESSION SET CONSTRAINT[S] = {IMMEDIATE | DEFERRED | DEFAULT}; SET CONSTRAINTS | CONSTRAINT {список имен ограничений | ALL} {IMMEDIATE | DEFERRED}; Создание ограничений Ограничение целостности можно определять при создании таблицы в команде CREATE TABLE или после создания таблицы в команде ALTER TABLE. Фрагмент примера создания первичного ключа при создании таблицы. CREATE TABLE employees ( employee_id NUMBER(6) CONSTRAINT emp_emp_id_pk PRIMARY KEY, …. Пример создания внешнего ключа: ALTER TABLE EMPLOYEES ADD CONSTRAINT emp_dept_key FOREIGN KEY (department_id) REFERENCES departments (department_id); Получение информации о таблицах и ограничениях целостности Информацию о таблицах можно получить из словаря данных. Следующий запрос предоставит список всех таблиц пользователя HR. SELECT table_name FROM dba_tables WHERE owner=’HR’; Чтобы посмотреть, когда создана таблица employees выполнить следующий запрос: пользователя HR, можно SELECT object_name, created FROM dba_objects WHERE object_name=’EMPLOYEES’ AND owner=’HR’; Информацию об ограничениях dba_cons_columns. можно получит из словарей dba_constraints и 38 Временные таблицы Временные таблицы создаются для обработки частных данных сеанса, время жизни которых ограничено транзакцией или сеансом. По команде CREATE GLOBAL TEMPORARY TABLE создаются временные таблицы двух видов: определенные для транзакции и определенные для сеанса. В таблицах первого вида данные существуют в течении транзакции, в таблицах второго вида – в течении сеанса. Данные принадлежат сеансу и в каждом сеансе можно выбирать и изменять только его собственные данные. Блокировки DML не устанавливаются над данными временных таблиц. Время жизни строк устанавливается использованием фраз: ON COMMIT DELETE – строки доступны в рамках транзакции; ON COMMIT PRESERVE ROWS – строки доступны в течении сеанса. Для временных таблиц можно создавать индексы, представления и триггера, а также использовать утилиты Export и Import для выгрузки и загрузки определений временных таблиц. Однако данные не выгружаются. Определение временной таблицы доступно всем сеансам. Пример создания временной таблицы: CREATE GLOBAL TEMPORARY TABLE hr.emp_temp AS SELECT * FROM hr.employees; Внешние таблицы Данные во внешних таблицах нельзя менять и они находятся вне базы данных – во внешних текстовых файлах. Внешняя таблица, как объект базы данных, создается командой CREATE TABLE … ORGANIZATION EXTERNAL Внешние таблицы похожи на представления - есть правила выборки, а доступ осуществляется реально к другим объектам. Операции DML над внешними таблицами невозможны. Индексы создавать нельзя. Внешние таблицы позволяют получить доступ к внешним данным, которые по каким то причинам не загружены в базу данных, как к простым таблицам. Если необходимо обработать внешние данные, перед окончательным помещением их в таблицы можно загрузить внешние данные и последовательно их преобразовывать внутри базы данных. Внешние таблицы позволяют изменить архитектуру обработки – использовать потоковую (pipelined) загрузку и преобразование данных. Можно определить цепочку обработки данных и использовать ее “на лету” без хранения промежуточных результатов. Данные во внешних таблицах нельзя менять с помощью команд DML. Однако можно менять их вручную – редактируя или подменяя внешние файлы. Внешние таблицы используют возможности утилиты SQL*Loader по загрузке и преобразованию данных. Эта утилита предоставляет наиболее быстрый путь по загрузке форматированного текста в таблицы базы данных Oracle. При использовании внешних таблиц, необходимо помнить, что многократный произвольный доступ к строкам такой таблицы может оказаться менее быстрым, чем доступ к простой таблице из-за того, что индексы на внешнюю таблицу отсутствуют. Поэтому, при обращении к внешней таблице будут просматриваться все строки внешнего файла. Внешние таблицы не подменяют собой таблицы базы данных, они предназначены для потоковой обработки данных. Пример создания внешней таблицы: CREATE TABLE my_external (employee_id, first_name, last_name) 39 ORGANIZATION EXTERNAL (TYPE ORACLE_DATAPUMP DEFAULT DIRECTORY ext_tab_dir LOCATION ('ext1.ext','ext2.ext')) PARALLEL AS SELECT employee_id, first_name, last_name FROM HR.EMPLOYEES; При выполнении запроса инициализируются библиотеки драйвера доступа и информация выбирается прямо из внешних файлов. Данные обрабатываются на лету, кэш буферов не используется для хранения информации из внешних таблиц. Индексы на внешнюю таблицу не существуют, поэтому, желательно, выбирать данные за один проход и запрашивать внешние таблицы только в целях преобразования данных там, где не оптимально переносить данные из внешних файлов в базу данных из-за их объема. Информацию о внешних таблицах можно получить из следующих представлений: DBA_EXTERNAL_TABLES – список атрибутов всех внешних таблиц в базе данных; DBA_EXTERNAL_LOCATIONS – список плоских текстовых файлов и имена директорий, где они находятся. Индексы на основе В*-дерева Индексирование - очень важный аспект проектирования и разработки приложения. Если индексов слишком много, снизится производительность операторов. Если индексов не хватает, снизится производительность запросов (а следовательно, вставок, изменений и удалений). Правильное решение этой проблемы позволит обеспечить высокую производительность приложений. Индексы на основе В*-дерева, или "обычные" индексы, - наиболее широко используемый тип индексной структуры в базе данных. По реализации они подобны двоичному дереву поиска. Цель их создания - минимизировать время поиска данных сервером Oracle. Сервер Oracle поддерживает все индексы при выполнении над таблицей команд DML. Операции вставки приводят к вставке элемента индекса. Удаление строки приводит только к логическому удалению элемента индекса. Обновление ключевых столбцов приводит к логическому удалению и вставке индекса. Индексы можно создавать как в схеме пользователя, являющегося владельцем таблицы, так и в другой схеме, хотя обычно они создаются в той схеме, в которой находится индексируемая таблица. Пример создания индекса для таблицы employees, содержащий колонку job_id CREATE INDEX emp_job_ix ON employees (job_id) Индексы могут быть организованы и по убыванию (DESC), что позволяет отсортировать данные в структуре индекса от "больших" к "меньшим" (по убыванию), а не от меньших к большим (по возрастанию). Информация об индексах может быть получена из представлений словаря данных. DBA_INDEXES – данные об индексах. DBA_IND_COLUMNS – данные о колонках индексов. Индексы с обращенным ключом Это индексы на основе В*-дерева, байты ключа в которых инвертированы (REVERSE). Это используется для более равномерного распределения записей по индексу при вводе возрастающих значений ключей. Предположим, при использовании 40 последовательности для генерации первичного ключа генерируются значения 987500, 987501, 987502 и т.д. Поскольку это последовательные значения, они будут попадать в один и тот же блок индекса, конкурируя за него. В индексе с обращенным ключом сервер Oracle будет индексировать значения 205789, 105789, 005789. Эти значения обычно будут далеко отстоять друг от друга в индексе, и вставки в индекс будут распределены по нескольким блокам. Пример создания индекса с обращенным ключом: CREATE INDEX ind_rev ON employees (commission_pct) REVERSE; Индексы на основе битовых карт Обычно в В*-дереве имеется однозначное соответствие между записью индекса и строкой - запись индекса указывает на строку. В индексе на основе битовых карт (BITMAP) запись использует битовую карту для ссылки на большое количество строк одновременно. Такие индексы подходят для данных с небольшим количеством различных значений, которые обычно только читаются. Столбец, имеющий всего три значения - Y, N и NULL, - в таблице с миллионом строк очень хорошо подходит для создания индекса на основе битовых карт. Индексы на основе битовых карт не нужно использовать в базе данных класса ООТ (оперативной обработки транзакций) из-за возможных проблем с одновременным доступом. Пример создания индекса на основе битовых карт: CREATE BITMAP INDEX ind_bit ON employees (commission_pct) ; Индексы по функции Эти индексы на основе В*-дерева или битовых карт хранят вычисленный результат применения функции к столбцу или столбцам строки, а не сами данные строки. Это можно использовать для ускорения выполнения запросов вида: SELECT * FROM t WHERE ФУНКЦИЯ (столбец) = некоторое значение, поскольку значение ФУНКЦИЯ (столбец) уже вычислено и хранится в индексе. Индексы по функциям позволяют индексировать вычисляемые столбцы и эффективно использовать их в запросах. По сути, они позволяют реализовать не зависящий от регистра символов поиск или сортировку, искать результаты вычисления сложных выражений и эффективно расширять возможности языка SQL, добавляя собственные функции, а затем эффективно осуществляя по ним поиск. Индексы по функциям имеет смысл использовать по многим причинам. Вот только основные из них. • Индексы по функциям легко добавить, и они дают немедленный результат. • Индексы по функциям можно использовать для ускорения работы существующих приложений, не изменяя логику их работы и запросы. Чтобы использовать индексы по функциям, необходима предварительная настройка. В отличие от описанных ранее индексов на основе В*-дерева и битовых карт, перед созданием и использованием индексов по функциям необходимо выполнить определенные действия. Чтобы обеспечить возможность их создания, надо задать ряд параметров в файле init.ora или на уровне сеанса. Кроме того, необходимы соответствующие привилегии. Для использования индексов по функциям необходимо сделать следующее. • Чтобы создать индексы по функциям для таблиц в собственной схеме, необходима системная привилегия QUERY REWRITE. 41 • Чтобы создать индексы по функциям для таблиц в других схемах, необходима системная привилегия GLOBAL QUERY REWRITE. • Использовать оптимизатор, основанный на стоимости. Индексы по функциям доступны только стоимостному оптимизатору, и никогда не будут использоваться оптимизатором на основе правил. • Чтобы оптимизатор использовал индексы по функциям, необходимо установить следующие параметры на уровне сеанса или системы: QUERY_REWRITE_ENABLED=TRUE QUERY_REWRITE_INTEGRITY=TRUSTED Необходимо установить эти параметры либо на уровне сеанса с помощью оператора ALTER SESSION, либо на уровне системы в файле параметров инициализации init.ora. Смысл установки параметра QUERY_REWRITE_ENABLED - разрешить оптимизатору переписывать запрос так, чтобы можно было использовать индекс по функции. Смысл установки параметра QUERY_REWRITE_INTEGRITY - сообщить оптимизатору, что можно "доверять" указанному программистом признаку предопределенности результатов выполнения кода (deterministic). Если результаты выполнения кода не предопределены (другими словами, возвращает разные результаты при одних и тех же входных данных), полученные по индексу строки могут оказаться некорректными. Предопределенность должен обеспечить разработчик. Пример создания индекса в таблице employees по функции UPPER для столбца last_name: CREATE INDEX emp_func ON employees (UPPER (last_name)) Таблицы, организованные по индексу Таблицы, организованные по индексу (index organized tables - IOT), - это таблицы, хранящиеся в структуре индекса. Стандартная таблица организована случайным образом (данные попадают в любое свободное место). В таблице же, организованной по индексу, хранимые данные отсортированы по первичному ключу. С точки зрения приложений, таблицы, организованные по индексу, ничем не отличаются: к ним применяются такие же SQL-операторы, как и для доступа к обычной таблице. Они особенно полезны для информационно-поисковых (information retrieval - IR) систем, хранения пространственных данных и приложений оперативного анализа информации (OLAP). При использовании стандартной таблицы необходимо управлять дисковым пространством, как для таблицы, так и индекса по первичному ключу. При использовании таблицы, организованной по индексу, дополнительное пространство для поддержки индекса по первичному ключу не требуется, поскольку индекс - это данные, а данные - это индекс. Проблема в том, что индекс - сложная структура данных, поддержка и управление которой требуют выполнения множества действий. Пример создания простейшей индекс-организованной таблицы: CREATE TABLE t1 (x INT PRIMARY KEY, у VARCHAR2(25), z DATE ) ORGANIZATION INDEX; 42 Представления Представления (view) – это определяемый вид выбираемых данных из одной или нескольких таблиц и других представлений. Их можно рассматривать как хранимые запросы. Представления в действительности не содержат данные, они выбирают их из таблиц, на которых они основываются. Эти таблицы называют базовыми таблицами представления. Данные из представлений запрашиваются так же, как и из таблиц. С некоторыми ограничениями можно выполнять изменение, вставку и удаление данных из представления. Все операции с представлениями в действительности выполняются с базовыми таблицами. С помощью представлений можно создать дополнительный уровень безопасности, ограничив доступ только к предопределенному набору строк и столбцов таблицы. Представления могут основываться на сложных запросах, упрощая доступ к данным пользователям и из приложений. Представления бывают простые и сложные. Основное различие между этими двумя категориями связано с операциями DMlL (вставкой, обновлением и удалением). Простое представление (simple view) – это представление, которое: - выбирает данные только из одной таблицы; - не содержит функций и групп данных, - позволяет выполнять операции DML через представление. Сложное представление – это представление, которое: - выбирает данные из нескольких таблиц, - содержит функции ли группы данных, - не всегда позволяет выполнять операции DML через представление. Для изменения определения представления не создавая его заново (без удаления представления и повторного и повторного предоставления объектных привилегий) можно использовать параметр OR REPLACE. Пример создания представления: CREATE OR REPLACE VIEW emp_view AS SELECT employee_id, last_name, salary FROM employees WHERE department_id=80; Вывести структуру представления можно с помощью команды SQL*Plus: DESCRIBE emp_view Именами столбцов в представлении можно управлять путем включения псевдонимов (алиасов) столбцов в подзапрос или указывать их псевдонимы в предложении CREATE VIEW после имени представления и перед ключевым словом SELECT подзапроса. Количество указанных псевдонимов должно соответствовать количеству столбцов или выражений в подзапросе, как в примере: CREATE OR REPLACE VIEW emp_view (id_number, name, ann_salary) AS SELECT employee_id, last_name, salary*12 FROM employees department_id=80; WHERE Выборка данных из представления производится также, как и из таблицы. Можно вывести содержимое либо всего представления, либо конкретных строк и столбцов. C момента создания представления его имя и определение хранятся в словаре данных USER_VIEWS. Текст команды SELECT, составляющей представление, хранится в столбце с типом данных LONG. Пример создания сложного представления: 43 CREATE OR REPLACE sum_view (name, minsal, maxsal, avgsal) AS SELECT d.department_name, MIN (e.salary), MAX (e.salary), AVG (e.salary) FROM employees e, departments d WHERE e.department_id = d.department_id GROUP BY d.department_id; Опция READ ONLY запрещает операции DML с представлениями. В следующем примере представление модифицируется так, чтобы все операции DML с ним были запрещены: CREATE OR REPLACE VIEW emp_view AS SELECT employee_id, last_name, salary FROM employees WHERE department_id=80 READ ONLY; Триггера Триггера – это хранимые в базе данных объекты, которые срабатывают автоматически, когда что-то происходит. База данных Oracle позволяет обрабатывать с помощью триггеров многие события. Например, вставку в таблицу, подсоединение пользователя к базе данных, кем-то предпринимаемую попытку удаления таблицы или изменения параметров аудита. Триггера могут вызывать другие процедуры и функции. Администраторы базы данных используют триггера для аудита по значениям данных, для проверки сложных ограничений и для автоматизации многих других задач. Существует много различных событий, которые могут быть использованы для создания триггеров. Большинство триггеров срабатывает перед или после события. Триггера DML могут быть созданы для событий, возникающих один раз на уровне команды(INSERT, UPDATE, DELETE) или же для событий, возникающих при изменении каждой строки таблицы. Табличные триггера Триггеры, определенные для таблиц, называются табличными триггерами (table triggers). Синтаксис создания триггера имеет следующей вид: CREATE OR REPLACE TRIGGER триггерное_событие ON имя_таблицы [WHEN триггерное_ограничение] [FOR EACH ROW] [тело_триггера] END имя_триггера'; имя_триггера момент_срабатывания Момент срабатывания определяет, когда будет срабатывать триггер: до (BEFORE) или после (AFTER) наступления триггерного события (выполнения запускающего оператора). Если указано значение BEFORE, триггер выполняется до каких-либо проверок ограничений на строки, затрагиваемые триггерным событием. Никакие строки не блокируются. Триггер этого типа называется, соответственно, BEFORE-триггером (BEFORE trigger). Если выбрать ключевое слово AFTER, то триггер будет срабатывать после того, как запускающий оператор завершит свою работу и будут выполнены проверки всех ограничений. В этом случае затрагиваемые строки блокируются на время выполнения триггера. Триггер этого типа называется AFTER-триггером (AFTER trigger). Триггерное событие может принимать значения INSERT, UPDATE'или DELETE. 44 Триггерное ограничение - это одно и более дополнительных условий, которые должны быть выполнены для срабатывания триггера. Набор ключевых слов FOR EACH ROW указывает на необходимость выполнить тело триггера для каждой строки, затрагиваемой запускающим оператором. Такие триггеры называются строчными (row triggers). Если опция FOR EACH ROW отсутствует, то при наступлении триггерного события триггер выполняется только один раз. В этом случае он называется операторным триггером (statement trigger), поскольку выполняется только один раз для каждого запускающего оператора. Тело триггера - представляет собой обычный базовый блок PL/SQL. Различные триггерные события можно комбинировать с помощью оператора OR. Например: DELETE OR INSERT остальные_операторы В случае использования UPDATE можно указать список столбцов: UPDATE ОF столбец_1, столбец_2,... При использовании опции FOR EACH ROW для триггерного события UPDATE в операторах PL/SQL обращение к новой и старой строкам выполняется с помощью слов "NEW" и "OLD", предваренных двоеточием. Так, :OLD.имя_столбца даст значение, которое столбец имел до обновления. Однако в триггерном ограничении имена "OLD" и "NEW" используются без двоеточий. В теле триггера можно использовать предикаты INSERTING, UPDATING, DELETING которые возвращают TRUE или FALSE в зависимости от команды DML, которую обрабатывает триггер. IF INSERTING THEN :new.hire_date = SYSDATE; END IF; В теле триггера на UPDATE можно уточнить, какой столбец был изменен в текущем операторе: IF UPDATING ('SALARY') THEN ... Если в таблице несколько разнотипных триггеров, то порядок их выполнения таков: 1. Все операторные BEFORE триггера. 2. Все строчные BEFORE триггера. 3. Все строчные AFTER триггера. 4. Все операторные AFTER триггера. При создании триггеров необходимо учитывать следующее: • Триггер, в котором выполняются операторы DML, может вызывать срабатывание других триггеров. В результате количество сработавших триггеров может оказаться довольно большим. • В триггере, запускаем оператором INSERT, имеет смысл обращаться только к новым значениям столбцов. Поскольку INSERT создает строку, старым значением всегда будет NULL. • В триггере, запускаемом оператором UPDATE, можно обращаться к старым и новым значениям столбцов. Это касается как BEFORE, так и AFTER триггеров. • В триггере, запускаемом оператором DELETE, имеет смысл обращаться только к старым значениям столбцов. Поскольку удаленная строка перестает существовать, новым 45 значением всегда будет NULL. Однако значения :NEW нельзя модифицировать. Если вы попытаетесь это сделать, будет выдано сообщение об ошибке ORA-4084. • В теле триггера не рекомендуется использовать операторы ROLLBACK, COMMIT и SAVEPOINT. • При возникновении необрабатываемых исключений производится откат всех изменений, включая те, которые были выполнены запускающим оператором. • Если для одного триггерного события определено более одного триггера, то порядок их срабатывания не определен, т.е. вы не можете заранее сказать, в какой последовательности они будут срабатывать. • Когда триггер пытается прочитать таблицу, а затем записать в нее данные, возбуждается исключение "мутирующей таблицы". Пример операторного триггера для фиксации удалений из таблицы employees Создание таблицы для записи логов. CREATE TABLE emp_log ( date_changed DATE, changed_by VARCHAR2(30), comment VARCHAR2(30) ); CREATE OR REPLACE TRIGGER stat_emp AFTER DELETE ON employees BEGIN INSERT INTO emp_log VALUES (SYSDATE, USER, ‘Удаление из таблицы employees’); END; Пример строчного триггера для фиксации изменений поля salary таблицы employees: Создание таблицы для записи логов. CREATE TABLE salary_log ( id NUMBER, old_salary NUMBER(9,2), new_salary NUMBER(9,2), date_changed DATE, changed_by VARCHAR2(30) ); CREATE OR REPLACE TRIGGER row_emp AFTER UPDATE OF salary ON employees FOR EACH ROW BEGIN INSERT INTO pay_change_log VALUES (:old.employee_id, :old.salary, :new.salary, SYSDATE, USER ); END; Пример строчного триггера для фиксации изменений поля salary таблицы employees с использованием триггерного ограничения: CREATE OR REPLACE TRIGGER row_emp 46 AFTER UPDATE OF salary ON employees FOR EACH ROW WHEN (new.department_id = 50) BEGIN INSERT INTO pay_change_log VALUES ( :old.employee_id, :old.salary, :new.salary, SYSDATE, USER ); END; Данный триггер будет срабатывать только для department_id = 50. Триггер INSTEAD OF Триггер INSTEAD OF (вместо) используется для представлений (view). Пример использования триггера INSTEAD OF: - Создаем представление по таблице employees: CREATE VIEW name_view (id, name) AS SELECT id, last_name || ',' || first_name FROM employees ORDER BY 1 - При попытке вставить запись в представление выдается ошибка: INSERT INTO name_view VALUES (emp_seq.nextval, 'Иванов,Петр') - Определяем на представление триггер типа INSTEAD: CREATE OR REPLACE TRIGGER name_trig INSTEAD OF INSERT ON name_view FOR EACH ROW DECLARE last VARCHAR2(25); first VARCHAR2(15); comma_pos NUMBER; BEGIN comma_pos := INSTR(:new.name, ','); last := SUBSTR(:new.name, 1, comma_pos - 1); first := SUBSTR(:new.name, comma_pos + 1); INSERT INTO employees (employee_id, last_name, first_name) VALUES (:new.id, last, first); END; - Вновь пытаемся вставить запись: INSERT INTO name_view VALUES (person_id_seq.nextval, 'Иванов,Петр'); Команда INSERT выполнилась успешно. Запись вставилась в таблицу employees. Ограничения триггеров INSTEAD OF: Используются только для представлений (не таблиц) Переменные :OLD и :NEW можно использовать только для чтения (read-only). Нельзя использовать опцию UPDATE [OF columns] 47 Системные триггера Системные триггера на базу данных или схему определяются для следующих событий: - команды DDL, - старт базы данных (STARTUP) и останов базы данных (SHUTDOWN), - ошибки сервера (SERVERERROR), начала сеанса пользователя (LOGON) и окончание сеанса пользователя (LOGOFF). Синтаксис для определения системного триггера: CREATE OR REPLACE TRIGGER triggername {BEFORE | AFTER} {event(s)} ON {SCHEMA | DATABASE} … Для создания системных триггеров необходима системная привилегия ADMINISTER DATABASE TRIGGER . Пример системного триггера на команды DDL CREATE и ALTER: CREATE OR REPLACE TRIGGER ddl_trig BEFORE CREATE OR ALTER ON SCHEMA BEGIN INSERT INTO ddl_log VALUES (ORA_DICT_OBJ_OWNER, ORA_DICT_OBJ_NAME, ORA_DICT_OBJ_TYPE, ORA_LOGIN_USER, SYSDATE); END; В системных триггерах существует возможность использовать дополнительные функции Oracle. - При обработке события CREATE: ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_owner ora_dict_obj_type ora_is_creating_nested_table - При обработке события ALTER: ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner ora_des_encrypted_password ora_is_alter_column ora_is_drop_column 48 - При обработке события DROP: ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner - При обработке события GRANT: ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_grantee ora_with_grant_option ora_privileges - При обработке любой команды DDL: ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner - При обработке события RENAME:. ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_owner ora_dict_obj_type - При обработке событий AFTER LOGON и BEFORE LOGOFF: ora_login_user ora_instance_num ora_database_name Синонимы Синоним (synonym) позволяет ссылаться на объект Oracle по имени, которое отличается от его настоящего имени. Синонимы можно определять для таблиц, 49 представлений, последовательностей, функций, процедур и пакетов. Если вы часто ссылаетесь на таблицу с длинным именем, то по достоинству оцените возможность использования короткого имени без переименования таблицы и изменения кода, который на нее ссылается. Удобство синонимов проявляется и в том, что они могут облегчить доступ к вашим данным для других людей. Таблицы организуются по идентификатору пользователя Oracle, который их создает, поэтому если другой пользователь захочет обращаться к таблице, созданной вами, то в общем случае ему придется помещать перед именем таблицы ваше имя пользователя, как показано ниже: SELECT * FROM ваше_имя_пользователя.имя_вашей_таблицы; Это может оказаться утомительным занятием, а если вы передадите свою таблицу кому-то другому, то вдобавок потребуется менять весь код, который на нее ссылается. Синонимы позволяют сделать таблицу "видимой" для всех, даже если не указано имя ее владельца. Благодаря этому можно писать SQL-операторы, которые будут продолжать работать даже при передаче таблицы другому пользователю. Команда создания синонима имеет следующий синтаксис: CREATE [PUPLIC] SYNONYM имя_синонима FOR имя объекта PUBLIC создает синоним, доступный всем пользователям базы данных. Пример создания синонима: CREATE PUPLIC SYNONYM emp FOR hr.employees; Теперь все пользователи базы данных могут обращаться к таблице employees из схемы hr по имени emp. Частный синоним должен отличаться от имен других объектов, принадлежащих этому же пользователю. Последовательности Последовательности (sequence) – это объект базы данных, который может быть использован многими пользователями для генерации уникальных целых чисел. Обычно последовательности используются для генерации значений главного ключа. Генерация последовательности с приращением (или уменьшением) осуществляется внутренней программой Oracle. Этот объект базы данных может сэкономить время, так как замещает код, который потребовался бы для обеспечения уникальности чисел в прикладной программе. Числа последовательности хранятся и генерируются независимо от таблиц. Следовательно, одна и та же последовательность может одновременно использоваться для нескольких таблиц. Пример создания последовательности emp_seq для использования в качестве главного ключа таблицы employees: CREATE OR REPLACE emp_seq INCREMENT BY 10 START WITH 120 MAXVALUE 9999999999999999999 MINVALUE 10 NOCACHE NOCYCLE; Последовательность начинается с 120, шаг 10, кэширование и циклическая генерация значений запрещены. Максимальное значение данной последовательности.9999999999999999999 (может содержать до 28 цифр для возрастающей последовательности). 50 Установка Unlimited в качестве MINVALUE для убывающей последовательности соответствует -10 в 26 степени. Обратиться к значениям последовательности можно с помощью псевдостолбцов NEXTVAL и CURRVAL. Псевдостолбец NEXTVAL используется для выборки следующего свободного числа последовательности. В результате выполнения emp_seq.NEXTVAL генерируется новое число, а текущее число помещается в CURRVAL. Псевдостолбец CURRVAL используется для ссылки на числов последовательности, только что сгенерированное текущим пользователем. Прежде, чем ссылаться на CURRVAL, необходимо использовать NEXTVAL для генерации числа в текущем сеансе пользователя. Псевдостолбцы NEXTVAL и CURRVAL могут использоваться в следующих командах и предложениях: - Список SELECT команды SELECT, не являющейся частью подзапроса. - Список SELECT подзапроса в команде INSERT. - Предложение VALUES команды INSERT. - Предложение SET команды UPDATE. Псевдостолбцы NEXTVAL и CURRVAL не могут использоваться в следующих командах и предложениях: - Список SELECT представления. - Команда SELECT с ключевым словом DISTINCT. - Команда SELECT с предложениями GROUP BY, HAVING или ORDER BY. - Подзапрос в команде SELECT, DELETE или UPDATE. - Выражение DEFAULT в команде CREATE TABLE или ALTER TABLE. Примеры использования последовательности: INSERT INTO employees (employee_id, last_name, department_id) VALUES (emp_seq.NEXTVAL, ‘Иванов’, 50); SELECT emp_seq.NEXTVAL FROM dual; Кэширование последовательности ускоряет доступ к их значениям. Кэш заполняется при первом обращении к последовательности. В ответ на каждый последующий запрос выдается одно из чисел, находящихся в памяти. Если последнее из этих чисел использовано, запрос следующего значения приводит к записи в память очередной партии значений. Хотя генераторы чисел генерируют числа без пропусков, пропуски происходят независимо от фиксации и отката транзакций. Следовательно, в случае отказа команды, содержащей ссылку на последовательность, данное число теряется. С момента создания последовательности она документируется в словаре данных USER_SEQUENCES. Там же можно увидеть следующее число последовательности без его увеличения, если последовательность создана с опцией NOCACHE. Импорт, экспорт и загрузка данных Утилиты IMP и EXP Утилиты командной строки IMP (для импорта) и EXP (для экспорта) используются для извлечения таблиц, схем или всей базы данных из одного экземпляра Oracle для дальнейшего импортирования в другой экземпляр или схему. Утилита ЕХР создает двоичный файл специфического формата, который также называют файлом дампа (и часто сокращенно ссылаются на него как на DMP). На выполнение утилиты запускаются из командной строки. Вызов справки по утилитам EXP и IMP из командной строки: EXP HELP =Y 51 IMP HELP=Y Пример простейшего экспорта одной таблицы из схемы scott в файл c:/EXP/emp.dmp с записью файла журналирования = c:/EXP/emp.log: EXP USERID=scott/tiger@kurs TABLES=emp FILE=c:/EXP/emp.dmp LOG= c:/EXP/emp.log Некоторые параметры утилиты Exp: BUFFER (по умолчанию – зависит от ОС) - Этот параметр задает размер буфера извлечения, используемого утилитой ЕХР. Если поделить значение параметра BUFFER на максимальный размер строки в этой таблице, можно определить, сколько строк за раз будет извлекать из таблицы утилита ЕХР. Чем больше размер буфера, тем выше производительность. Некоторые таблицы, в частности, содержащие столбцы типа LONG или большие двоичные объекты, считываются по одной строке, независимо от размера буфера. Нужно только проверить, достаточен ли размер буфера для размещения самого большого столбца. COMPRESS (по умолчанию = Y) - Этот параметр не задает сжатие экспортированных данных. Он управляет генерацией конструкции STORAGE для экспортируемых объектов. Если оставить значение Y, конструкция хранения будет задавать для объектов начальный экстент, размер которого равен суммарному размеру их текущих экстентов. Т.е. утилита ЕХР будет генерировать оператор CREATE и с его помощью попытаться поместить весь объект в одном экстенте. ROWS - Указывает утилите ЕХР, следует экспортировать ли строки данных таблиц или только структуру. FILESIZE - Если имеет положительное значение, файл DMP, создаваемый утилитой экспорта, устанавливается в максимальный размер. Используется при экспорте более двух гигабайт данных. QUERY - Позволяет связывать конструкцию WHERE с экспортируемыми таблицами. Конструкция WHERE будет применяться к строкам в ходе экспорта на уровне таблиц, при этом будут экспортироваться только строки, удовлетворяющие конструкции WHERE. Это позволяет экспортировать "срез" таблицы. FULL (по умолчанию = N) - Если имеет значение Y, экспортируется вся база данных. При этом выбираются все пользователи, определения табличных пространств, системные привилегии и остальное содержимое базы данных. OWNER - Позволяет задать список схем для экспорта. TABLES - Позволяет задать список экспортируемых таблиц. PARFILE - Задает имя файла параметров, содержащего пары parameter_name = values. Может использоваться как альтернативный вариант заданию всех параметров в командной строке. Чаще всего используется для задания длинных списков экспортируемых таблиц или параметра QUERY. CONSISTENT (по умолчанию =N) - Указывает, должно ли экспортирование выполняться в транзакции только для чтения. Это гарантирует согласованность различных таблиц. Транзакция только для чтения (или с уровнем изолированности SERIALIZABLE) распространяет согласованность по чтению до уровня транзакции. Если экспортируются таблицы, связанные декларативным требованием целостности ссылок (Rl - Referential Integrity) или вложенные таблицы и в дальнейшем планируется импортировать их вместе, рекомендуется использовать параметр consistent = Y. Это особенно важно, если велика вероятность изменения таблиц при экспортировании. 52 TRANSPORT_ TABLESPACE (по умолчанию = N) - Указывает, будет ли утилита ЕХР использоваться для экспортирования метаданных набора переносимых табличных пространств. TABLESPACES (по умолчанию = N) - Используется совместно с параметром TRANSPORT_TABLESPACE, чтобы задать список табличных пространств для переноса. Пример простейшего импорта данных из файла c:/EXP/emp.dmp с записью файла журналирования = c:/EXP/emp_i.log в схему scott: EXP USERID=scott/tiger@kurs FILE=c:/EXP/emp.dmp LOG= c:/EXP/emp.log Некоторые параметры утилиты Imp: SHOW (по умолчанию = N) - Если установлено значение Y, утилита импорта покажет свои потенциальные действия, не выполняя импортирование реально. Если задан параметр SHOW = Y, объекты не создаются и данные не добавляются. IGNORE (по умолчанию = N) - Если установлено значение Y, IMP будет игнорировать большинство ошибок создания объектов. Пригодится, если объекты уже созданы в базе данных и IMP используется только для наполнения таблиц данными. INDEXFILE - Если этот параметр задан, IMP будет сбрасывать все операторы CREATE INDEX и множество других операторов в указанный файл индексов (с комментариями в начальных строках, начинающихся с REM). Другие объекты из файла DMP не обрабатываются, создается только файл индексов. FROMUSER - С помощью этого параметра задают список пользователей, объекты которых надо импортировать из файла DMP. Можно использовать для восстановления одной схемы из файла экспорта всей базы данных. TOUSER - Если этот параметр указан, объекты пользователя, задаваемого параметром FROMUSER, импортируются в пользовательскую схему, имя которой является значением параметра TOUSER. Это позволяет "клонировать" пользовательскую схему. COMMIT (по умолчанию = N) - Указывает, должна ли утилита IMP фиксировать изменения после каждой множественной вставки. Количество вставляемых строк определяется параметром BUFFER. Обычно утилита IMP выполняет COMMIT после полной загрузки таблицы. Поскольку операторы вставки генерируют минимальный объем данных отката, при частом фиксировании замедляется вставка и увеличивается объем информации, записываемой в журналы повторного выполнения. Кроме того, продолжить работу IMP с места сбоя нельзя, поэтому я рекомендуется оставлять для параметра значение N. TTS_OWNERS При использовании вместе с параметром TRANSPORTABLE_TABLESPACES задает список владельцев объектов в переносимом табличном пространстве. Утилиты Data Pump Утилита Data Pump выполняется на стороне сервера, а не на клиенте. Поэтому доступ к dump-файлам, файлам журналов операций и SQL файлам осуществляется через пути к директориям на стороне сервера. Утилита Data Pump требует, чтобы пути к директориям задавались с помощью объектов DIRECTORY. Такие объекты хранят путь к каталогу в файловой системе. Для создания объекта DIRECTORY используется команда CREATE DIRECTORY. Объект DIRECTORY определяет псевдоним (алиас) каталога файловой системы сервера. Чтобы создать этот объект, необходимо иметь системную привилегию CREATE ANY DIRECTORY. Пользователю, создающему объект DIRECTORY, автоматически предоставляются объектные привилегии READ и WRITE на этот объект, которые он может передать другим пользователям и ролям. 53 Пример создания объекта DIRECTORY: CREATE DIRECTORY dmpdir AS ‘c:/Pump/kurs’; Oracle не проверяет, действительно ли существует каталог в файловой системе. Описание объекта DIRECTORY можно вывести из представления DBA_ DIRECTORY. Утилита Data Pump Export предоставляет возможность высокоскоростного переноса данных их одной базы данных в другую. Например, можно экспортировать данные из одной базы данных, а затем утилитой Data Pump Import загрузить их в другую базу данных. Утилиты Data Pump можно вызвать из Enterprise Manager или из командной строки. Вызов справки по утилитам Data Pump из командной строки: EXPDP HELP=Y IMPDP HELP=Y Пример выгрузки всех объектов из схемы scott в файл scott.dmp, который будет находиться в каталоге, определенном объектом DIRECTORY=dmpdir: EXPDP scott/tiger@kurs DUMPFILE=scott.dmp DIRECTORY=dmpdir SCHEMAS=scott Пример загрузки данных из файла в схему scott: IMPDP scott/tiger@kurs DIRECTORY=dmpdir DUMPFILE=scott.dmp Кроме экспорта схемы (SCHEMAS) можно выгружать всю базу данных (FULL=y),отдельные таблицы (TABLES= список таблиц). При импорте данные могут быть загружены в схему с таким же, как у пользователя именем или другим (REMAP) . SQL*Loader Инструментальное средство SQL*Loader (SQLLDR) - высокопроизводительное средство массовой загрузки данных в СУБД Oracle. Это очень полезное средство, позволяющее поместить в базу данных Oracle данные из текстовых файлов множества различных форматов. Утилиту SQLLDR можно использовать для потрясающе быстрой загрузки огромных объемов данных. Она имеет два режима работы: • Обычная загрузка. В этом режиме SQLLDR для загрузки данных будет автоматически вставлять строки с помощью SQL-операторов. • Непосредственная загрузка. В этом режиме SQL не используется. Блоки данных в базе формируются непосредственно. Непосредственная загрузка позволяет читать данные из обычного файла и записывать их непосредственно в сформатированные блоки базы данных в обход SQL-машины (а также сегментов отката и журнала повторного выполнения). При распараллеливании непосредственная загрузка является самым быстрым способом наполнения базы данными, причем ускорить этот процесс невозможно. При вызове SQLLDR из командной строки без параметров выдается справочная информация: SQLLDR Назначение некоторых параметров: BAD - имя файла, который будет содержать отвергнутые записи по окончании загрузки. Если не указать его имя явно, оно будет создано автоматически по имени управляющего (CONTROL) файла использованного для загрузки. Например, если в качестве 54 управляющего использован файл foo.ctl, файл BAD по умолчанию получит имя foo.bad; именно в этот файл и будет помещать отвергнутые записи утилита SQLLDR (если файл существует, он будет перезаписан). BINDSIZE - размер (в байтах) буфера, используемого утилитой SQLLDR для вставки данных при обычной загрузке. При непосредственной загрузке этот параметр не используется. Размер буфера используется для определения размера массива, с помощью которого SQLLDR будет вставлять данные. CONTROL - имя управляющего (CONTROL) файла, описывающего формат данных и способ их загрузки в таблицу. Управляющий файл необходимо указывать при каждом вызове SQLLDR. DATA - имя файла, из которого надо считывать данные. DIRECT - допустимы значения True (непосредственная загрузка) и False (обычная загрузка), причем по умолчанию используется False. Таким образом, по умолчанию утилита SQLLDR выполняет обычную загрузку. DISCARD - имя файла, куда помещаются пропущенные записи, которые не должны загружаться. Утилиту SQLLDR можно использовать для фильтрования загружаемых записей - она позволяет загружать только записи, удовлетворяющие указанным критериям. DISCARDMAX - задает максимальное количество пропущенных записей, допустимое в процессе загрузки. Если пропущено больше записей, загрузка прекращается. По умолчанию загрузка не прекращается, даже если пропущены все записи. ERRORS - максимально допустимое количество ошибок, выявленных утилитой SQLLDR, прежде чем загрузка будет прервана. Это могут быть самые разные ошибки, например ошибка преобразования типов данных (скажем, попытка загрузить строку ABC в числовое поле), дублирование записей по ключу уникального индекса и т.д. Стандартно допускается не более 50 ошибок, после чего загрузка прекращается. Чтобы можно было в одном сеансе загрузить все допустимые записи (при этом отвергнутые попадают в BADфайл), укажите в качестве значения большое число, например 999999999. FILE - при использовании непосредственной загрузки с распараллеливанием, этот параметр позволяет явно указать утилите SQLLDR, в какой файл данных загружать записи. Это позволяет уменьшить конфликты доступа к файлам данных при параллельной загрузке и обеспечить запись данных в процессе каждого сеанса загрузки на отдельное устройство. LOAD - максимальное количество загружаемых записей. Обычно используется для загрузки небольшого образца данных из большого файла или совместно с параметром SKIP для загрузки из входного файла лишь записей определенного диапазона. LOG - задает имя журнального (LOG) файла. По умолчанию, утилита SQLLDR будет создавать журнальный файл с именем, созданным автоматически на основе имени управляющего файла, аналогично BAD-файлу. PARALLEL - допускаются значения TRUE или FALSE. Если указано значение TRUE, то выполняется параллельная непосредственная загрузка. Этот параметр необязателен при обычной загрузке, ее можно выполнять параллельно и без его установки. PARFILE - может использоваться для задания имени файла, содержащего все описываемые параметры в виде пар КЛЮЧЕВОЕ_СЛОВО=ЗНАЧЕНИЕ. Это позволяет не задавать параметры в командной строке. READSIZE - задает размер буфера, используемого при чтении данных. ROWS - количество строк, которое утилита SQLLDR должна вставить, прежде чем фиксировать изменения при обычной загрузке. При непосредственной загрузке задает количество строк, которые необходимо загрузить, прежде чем сохранять данные (это аналог 55 фиксации). При обычной загрузке стандартное значение – 64 строки. При непосредственной загрузке по умолчанию данные не сохраняются, пока загрузка не завершена. SILENT - подавляет выдачу информационных сообщений в ходе загрузки. SKIP - заставляет утилиту SQLLDR пропустить указанное в качестве значения этого параметра количество строк во входном файле. Чаще всего используется для продолжения прерванной загрузки (для пропуска уже загруженных записей) или для загрузки только части входного файла. USERID - строка подключения к базе данных в формате ИМЯ_ПОЛЬЗОВАТЕЛЯ/ПАРОЛЬ@БАЗА_ДАННЫХ. Используется для аутентификации в базе данных. SKIP_INDEX_MAINTENANCE - нe используется при обычной загрузке, поскольку в этом режиме поддерживаются все индексы. Если этот параметр установлен при непосредственной загрузке, СУБД Oracle не поддерживает индексы: они помечаются как недоступные для использования. После загрузки данных такие индексы необходимо пересоздать. SKIP_UNUSABLE_INDEXES - требует от утилиты SQLLDR разрешить загрузку строк в таблицу, по которой есть недоступные для использования индексы, если эти индексы – не уникальные. Чтобы использовать утилиту SQLLDR, необходим управляющий файл. Управляющий файл содержит информацию, описывающую загружаемые данные: их организацию, типы денных и т.д., а также указывает, в какую таблицу или таблицы эти данные необходимо загрузить. Управляющий файл может содержать даже данные, которые необходимо загрузить. В следующем примере создается простой управляющий файл и описываются используемые на каждом шаге команды: LOAD DATA - эта команда указывает утилите SQLLDR, что необходимо сделать (в данном случае - загрузить данные). А еще можно указывать действие CONTINUE_LOAD для возобновления загрузки. Эта опция используется только для продолжения непосредственной загрузки нескольких таблиц. INFILE * - эта конструкция указывает SQLLDR, что данные, которые необходимо загрузить, находятся в самом управляющем файле (см. ниже). В этой конструкции можно также указать имя другого файла, содержащего данные. При необходимости в командной строке можно переопределить имя файла, задаваемого в конструкции INFILE. Учтите, что опции командной строки имеют преимущество над установками, заданными в управляющем файле. INTO TABLE dept -эта конструкция указывает, в какую таблицу загружаются данные; в нашем случае это таблица dept. FIELDS TERMINATED BY ',' -эта конструкция указывает, что данные будут представлены в виде списка значений через запятую. Есть десятки способов описать загружаемые данные в SQLLDR, это - лишь один из наиболее часто используемых. (DEPTNO, DNAME, LOC) - эта конструкция указывает, какие столбцы загружаются, их порядок следования в загружаемых данных и типы. При этом указываются типы данных во входном потоке, а не типы соответствующих столбцов в базе данных. В нашем случае используется стандартный формат CHAR(255), что вполне подходит. BEGINDATA -эта конструкция указывает утилите SQLLDR, что описание загружаемых данных закончено и что со следующей строки идут данные, которые необходимо загрузить в таблицу dept: 10,Sales,Virginia 56 20.Accounting,Virginia 30,Consulting,Virginia 40,Finance,Virginia Итак, вот управляющий файл в одном из наиболее простых и типичных форматов для загрузки в таблицу данных со столбцами, определяемыми разделителем. LOAD DATA INFILE * INTO TABLE dept FIELDS TERMINATED BY ',' (DEPTNO, DNAME, LOC) BEGINDATA 10,Sales,Virginia 20.Accounting,Virginia 30,Consulting,Virginia 40,Finance,Virginia Чтобы применить этот управляющий файл, достаточно создать пустую таблицу dept: CRATE TABLE dept (deptno NUMBER(2) CONSTRAINT emp_pk PRIMARY KEY, dname VARCHAR2(14), loc VARCHAR (13) ) и выполнить следующую команду: SQLLDR USERID=scott/tiger@kurs CONTROL=demol.ctl Если таблица не пуста, будет выдано сообщение об ошибке: SQLLDR-601: For INSERT option, table must be empty. Error on table DEPT Так происходит потому, что в управляющем файле используются почти исключительно стандартные установки, а стандартно при загрузке выполняется операция INSERT (еще возможны APPEND, TRUNCATE или REPLACE). При выполнении операции INSERT предполагается, что таблица пуста. Если необходимо добавить записи в таблицу DEPT, можно указать операцию APPEND, а для замены данных в таблице DEPT - операцию REPLACE или TRUNCATE. При каждой попытке загрузки генерируется журнальный файл. Журнальный файл для нашего примера будет иметь следующий вид: Control File: demol.ctl Data File: demol.ctl Bad File: demol.bad Discard File: none specified (Allow all discards) Number to load: ALL Number to skip: 0 Errors allowed: 50 Bind array: 64 rows, maximum of 65536 bytes Continuation: none specified 57 Path used: Conventional Table DEPT, loaded from every logical record. Insert option in effect for this table: INSERT 457 Column Name Position Len Term Encl Datatype DEPTNO FIRST *, CHARACTER DNAME NEXT *, CHARACTER LOC NEXT *, CHARACTER Table DEFT: 4 Rows successfully loaded. 0 Rows not loaded due to data errors. 0 Rows not loaded because all WHEN clauses were failed. 0 Rows not loaded because all fields were null. Space allocated for bind array: 49536 bytes(64 rows) Space allocated for memory besides bind array: 0 bytes Total logical records skipped: 0 Total logical records read: 4 Total logical records rejected: 0 Total logical records discarded: 0 Run began on Sat Apr 14 10:58:02 2010 Run ended on Sat Apr 14 10:58:02 2010 Elapsed time was: 00:00:00.11 CPU time was: 00:00:00.04 В журнальном файле представлены различные характеристики выполненной загрузки. Можно увидеть использованные опции (стандартные или явно указанные). Можно узнать, сколько записей прочитано, сколько из них было загружено и т.д. Указаны имена файлов BAD и DISCARD. Указана даже продолжительность загрузки. Журнальные файлы позволяют проверить, успешно ли прошла загрузка, и не было ли сообщений об ошибках. Если при загрузке данных были ошибки SQL (загружаемые данные - "плохие", и соответствующие записи помещены в BAD-файл), эти ошибки записываются в журнальный файл. Описание метаданных В Oracle для получения информации о метаданных используется словарь базы данных. Основой словаря данных является набор базовых таблиц. Базовые таблицы словарей – это объекты, создаваемые первыми в базе данных. Они создаются автоматически командным файлом sql.bsq во время выполнения команды CREATE DATABASE. Сервер Oracle записывает и считывает информацию из этих таблиц. Пользователи базы данных очень редко обращаются к ним непосредственно, так как они содержат информацию в виде, удобном для программы, a не для человека. Никогда не изменяйте напрямую содержимое таблиц с помощью команд DML, за исключением тех, работа с которыми описана в документации (например, таблица аудита AUD$). Это может привести к непредсказуемым последствиям: многие таблицы всегда обновляются сервером Oracle одновременно. В качестве примера базовой таблицы можно привести таблицу IND$, которая содержит информацию об индексах. Представления словаря данных создаются при выполнении командного файла catalog.sql. Эти представления представляют информацию базовых таблиц словаря данных в удобном виде, соединяя базовые таблицы между собой и вводя предложение WHERE для 58 выборки нужных данных. Например, в представлениях словаря данных используются имена объектов вместо номеров объектов, хранимых в базовых таблицах. Эти представления защищают программистов от ошибок, допуская только запросы SELECT. Словарь данных содержит: - Определения всех объектов схем базы данных (таблицах, представлениях, индексах, синонимах, последовательностях, процедурах, функциях, пакетах, триггерах, и т.д.); - Информацию о пространстве, выделенном для объектов схемы и его использовании; - Значения столбцов по умолчанию; - Ограничения целостности; - Имена пользователей Oracle; - Привилегии и роли, выделенные пользователю; - Результаты протоколирования ( кто обращался к объектам схемы и изменял их). Информация в базовых таблицах словаря данных необходима серверу Oracle для выполнения его функций. Поэтому только сервер Oracle должен писать и изменять информацию в словарях данных. В ходе обработки сервер читает словарь данных, чтобы получить подтверждение существования объектов схемы и наличия прав доступа пользователей к этим объектам. Информация в словарях постоянно изменяется сервером Oracle, отражая изменения в структурах базы данных, протоколах, привилегиях и структурах данных. Пользователи могут ссылаться на словари данных и использовать их имена в командах SQL. Некоторые словари доступны всем пользователям Oracle, другие только администраторам базы данных. Представления с префиксом DBA выводят информацию обо всех объектах базы данных. Доступ к этим представлениям имеет администратор базы данных или любой пользователь, которому предоставлена системная привилегия SELECT ANY TABLE. Следующий оператор SQL позволяет вывести список всех объектов базы данных: SELECT owner, object_name, object_type FROM dba_objects; Представления с префиксом ALL выводят информацию об объектах, к которым пользователь имеет доступ через общие или явно предоставленные привилегии и роли, что включает и объекты, которыми он сам владеет. Например, следующий запрос возвращает информацию обо всех объектах, к которым пользователь имеет доступ: SELECT owner, object_name, object_type FROM all_objects; Представления с префиксом USER доступны любому пользователю и, в большинстве своем, выводят информацию об объектах , которыми данный пользователь владеет, например, USER_TABLES содержит информацию о всех таблицах пользователя. Представления с префиксом USER обеспечивают подмножество информации представлений ALL. Они включают одинаковые столбцы с представлениями выше описанных категорий, за исключением того, что в них отсутствует столбец OWNER, так как все объекты представлений с префиксом USER принадлежат текущему пользователю. Следующий запрос возвращает информацию о всех объектах в схеме пользователя: SELECT object_name, object_type FROM user_objects; Полный список представлений словаря данных и их описание можно получить запросив представление DICTIONAКY или его синоним DICT: SELECT * FROM dictionary; Смысл столбцов словарей можно получить из представления DICT_COLUMNS. Таким образом, чтобы успешно получить информацию о метаданных, нет необходимости 59 запоминать названия всех представлений. Достаточно запомнить названия DICT и DICT_COLUMNS. Сервер Oracle представляет оперативную информацию о работе базы данных в виде набора “виртуальных” отображений, называемых динамическими представлениями производительности. Они являются интерфейсом для администратора к внутренним структурам памяти экземпляра и существуют только, когда экземпляр запущен. Информация для них поступает из внутренних структур памяти экземпляра и управляющего файла. Динамические представления производительности позволяют получить ответы на такие вопросы, как: Объект в оперативном состоянии и доступен? Объект открыт? Какие блокировки установлены? Сеанс активен? Чтобы ознакомиться с динамическими представлениями производительности, можно выполнить запрос к представлению DICTIONARY или использовать представление V$FIXED_TABLE, которое содержит только динамические представления производительности. SQL-запросы и подзапросы СУБД Oracle является реляционной, для хранения информации используются двумерные таблицы, а доступ к базе данных осуществляется путем выполнения команд SQL (язык структурированных запросов), которые делятся на: - Команды манипулирования данными (DML) INSERT, UPDATE, DELETE, MERGE; - Команды определения данных (DDL) CREATE, ALTER, DROP, RENAME, TRUNCATE; - Команды управления данными (DCL) предоставляют или изымают права доступа, как к базе данных, так и к структурам в ней; - Команда SELECT производит выборку данных из базы данных - Команды COMMIT, ROLLBACK, SAVEPOINT – управляют изменениями, производимыми с помощью команд DML. Изменения можно группировать в логические транзакции. С помощью команды SELECT можно производить следующие действия: - Проекция – позволяет задать столбцы, возвращаемые запросом. - Выбор – позволяет осуществлять выборочный вывод строк таблицы. Для этого можно задать различные критерии выбора. - Соединение - позволяет объединять данные из различных таблиц на базе соединения между ними. Базовый оператор SELECT Синтаксис базового оператора SELECT: SELECT *|{[DISTINCT] column|expression [alias],...} FROM table [WHERE condition(s)] [ORDER BY {column, expr, alias} [ASC|DESC]]; Далее будут приведены примеры основных конструкций оператора SELECT: 60 - Выборка значений всех столбцов: SELECT * FROM departments; - Выборка значений нескольких столбцов: SELECT department_id, location_id FROM departments; - Использование в выборке арифметического выражения: SELECT last_name, salary, salary + 300 FROM employees; SELECT last_name, salary, 12*salary+100 FROM employees; SELECT last_name, salary, 12*(salary+100) FROM employees; - Использования алиасов для столбцов: SELECT last_name AS name, commission_pct comm. FROM employees; SELECT last_name "Name" , salary*12 "Annual Salary" FROM employees; - Объединение двух символьных столбцов: SELECT - last_name||job_id AS "Employees" FROM employees; Объединение символьных столбцов с символьной строкой: SELECT last_name ||' is a '||job_id - AS "Employee Details" FROM employees; Исключение дублирующих записей: SELECT DISTINCT department_id FROM employees; - Ограничение выборки записей: SELECT employee_id, last_name, job_id, department_id FROM employees WHERE department_id = 90 ; - для числового поля SELECT last_name, job_id, department_id FROM employees WHERE last_name = 'Whalen' ; - для символьного поля SELECT last_name FROM employees WHERE hire_date = '17-FEB-96’; - для поля с датой - Использование для ограничения выборки записей BETWEEN (между): SELECT last_name, salary FROM employees WHERE salary BETWEEN 2500 AND 3500; - верхняя и нижняя границы входят в выборку - Вхождения значений из списка: SELECT employee_id, last_name, salary, manager_id FROM employees WHERE manager_id IN (100, 101, 201) ; - Использование условия LIKE (поиск по шаблону): SELECT first_name FROM employees WHERE first_name LIKE '_o%'; _ - обозначает один символ, % - обозначает ноль или несколько символов. - Использования логических условий: SELECT employee_id, last_name, job_id, salary FROM employees 61 WHERE salary >= 10000 AND job_id LIKE '%MAN%' ; SELECT employee_id, last_name, job_id, salary FROM employees WHERE salary >= 10000 OR job_id LIKE '%MAN%' ; SELECT last_name, job_id FROM employees WHERE job_id NOT IN ('IT_PROG', 'ST_CLERK', 'SA_REP') ; - Использование скобок для определения порядка операций: SELECT last_name, job_id, salary FROM employees WHERE (job_id = 'SA_REP' OR job_id = 'AD_PRES') AND salary > 15000; - Использования сортировки выбранных записей: SELECT last_name, job_id, department_id, hire_date FROM employees ORDER BY hire_date; - Использования сортировки по убыванию: SELECT last_name, job_id, department_id, hire_date FROM employees ORDER BY hire_date DESC; - Сортировка по алиасу столбца : SELECT employee_id, last_name, salary*12 annsal FROM employees ORDER BY annsal; - Сортировка по позиции столбца: SELECT last_name, job_id, department_id, hire_date FROM employees ORDER BY 3; - Сортировка по нескольким столбцам: SELECT last_name, department_id, salary FROM employees ORDER BY department_id, salary DESC; Работа с NULL Если в строке отсутствует значение какого-либо столбца, считается, что столбец содержит неопределенное значение (NULL). Неопределенное значение – это значение, которое недоступно, не присвоено, неизвестно или неприменимо. Это не ноль и не пробел. Неопределенные значения допускаются в столбцах с данными любого типа за исключением случаев, когда столбец создан с ограничением NOT NULL или PRIMARY KEY. Столбец commission_pct таблицы employees показывает, что получать комиссионные могут только продавцы. Другие служащие права на комиссионные не имеют. Это показано неопределенным значением в столбце. SELECT last_name, job_id, salary, commission_pct FROM employees; Если какой-либо столбец в арифметическом выражении содержит неопределенное значение, результат вычислений также будет неопределенным (NULL). Например, попытка деления на ноль заканчивается ошибкой. Но, если попытаться разделить число на неопределенное значение, результатом будет неопределенное значение. В следующем примере все служащие, не являющиеся продавцами, не получат комиссионных. SELECT last_name, 12*salary*commission_pct FROM employees; 62 Т.к. столбец commission_pct в арифметическом выражении содержит неопределенное значение, результатом также будет неопределенное значение. С помощью оператора IS NULL (IS NOT NULL ) производится проверка на неопределенные значения. SELECT last_name, manager_id FROM employees WHERE manager_id IS NULL; Логические операции с NULL: NULL AND FALSE = FALSE NULL OR TRUE = TRUE Все остальные операции с NULL дают в результате NULL. Соединение таблиц Иногда требуются данные из более, чем одной таблицы. Пример выборки данных из двух разных таблиц: SELECT employee_id, location_id FROM employees, departments WHERE employees.department_id = departments.department_id; employee_id существует в таблице employees, department_id существует как в таблице employees, так и в таблице departments, location_id существует в таблице departments. Если условие соединения недействительно или опущено, результатом запроса будет декартово произведение двух таблиц, включающее все комбинации строк. Все строки первой таблицы соединяются со всеми строками второй таблицы. Как правило, декартово произведение содержит большое количество строк, и результат получается не очень полезным. Поэтому, если вывод всех комбинаций строк не требуется, следует использовать предложение WHERE с правильным условием соединения. Декартово произведение может быть полезным для генерации большого количества текстовых строк, если необходимо смоделировать выбор большого объема информации. Пример получения декартова произведение при отсутствии условия соединения таблиц: SELECT employee_id, location_id FROM employees, departments; Для соединения таблиц СУБД Oracle позволяет использовать синтаксис SQL99. До версии Oracle9i синтаксис соединений отличался от стандарта ANSI и поддерживается в последующих версиях. Рассмотрим конструкции соединения таблиц в обоих стандартах. В стандарте Oracle для выборки данных из нескольких таблиц требуется условие соединения. Строки одной таблицы соединяются со строками другой с помощью общих значений в соответствующих столбцах – обычно в столбцах первичных и внешних ключей. Для вывода данных из двух или более взаимосвязанных таблиц можно задать условие соединения в предложении WHERE. Для соединения n таблиц требуется не менее (n-1) условий соединения. Эквисоединения (простое или внутреннее соединение) – для выборки данных из нескольких таблиц в качестве условия соединения используются одинаковые значения в соответствующих столбцах. Например: SELECT employee_id, location_id FROM employees, departments 63 WHERE employees.department_id = departments.department_id; Не-эквисоединения – при соединении таблиц в качестве условия соединения используется оператор, отличный от оператора равенства. Например: SELECT e.last_name, e.salary, j.grafe_level FROM employee e, job_grades о WHERE e.salary BETWEEN j.lovest_sal AND j.highest_sal; Этот запрос создан для оценки категории заработной платы служащего. Она должна быть между любой парой нижней и верхней границ диапазона. Внешние соединения – используются для выборки строк, не удовлетворяющих обычным условиям соединения, оператором внешнего соединения является знак плюс (+). Он помещается на “стороне” соединения у таблицы с недостающей информацией. В результате применения оператора в этой таблице создается одна или более строк с неопределенными значениями (NULL), к которым можно присоединить одну или более строк из таблицы, где имеется вся необходимая информация. SELECT employee_id, location_id FROM employees e, departments d WHERE e.department_id (+) = d.department_id; В результате выполнения запроса в список будет включен и отдел, не имеющий сотрудников. Оператор внешнего соединения может использоваться только на одной стороне выражения – на стороне, где недостаточно информации. Условия, предполагающие внешнее соединение, не могут использовать оператор IN или быть связанными с другими условиями с помощью оператора OR. Соединения таблицы с собой – используются при работе с таблицей, имеющей иерархическую структуру. Чтобы найти менеджера каждого служащего, необходимо соединить таблицу employees с этой же самой таблицей. В этом процессе таблица просматривается дважды. В первый раз выбираются имена служащих и номера их менеджеров, во второй раз по номеру менеджера ищется его имя: SELECT w.last_name||’ работает на ‘|| m.last_name FROM employees w, employees m WHERE w.manager_id = m.employee_id; Используя синтаксис стандарта SQL99 для соединения таблиц можно получить такие же результаты, что уже были рассмотрены: SELECT table1.column, table2.column FROM table1 [NATURAL JOIN table2] | [JOIN table2 USING (column_name)] | [JOIN table2 ON (table1.column_name = table2.column_name)]| [LEFT|RIGHT|FULL OUTER JOIN table2 ON (table1.column_name = table2.column_name)]| [CROSS JOIN table2]; Где: NATURAL JOIN - соединяет две таблицы на основе одинаковых столбцов, JOIN table USING (column_name) – выполняет эквисоединение на основе имени столбца, JOIN table ON (table1.column_name = table2.column_name) – выполняет эквисоединение на основе условия в предложении ON, LEFT|RIGHT|FULL OUTER – левое (правое), полное внешнее соединение таблиц, CROSS JOIN – возвращает декартово произведение двух таблиц. 64 - Рассмотрим эти конструкции на примерах: Перекрестные соединения – аналог декартова произведения. SELECT last_name, department_name FROM employees CROSS JOIN departments; Натуральные соединения - основываются на всех столбцах таблиц, имеющих одинаковые имена. У столбцов с одинаковыми именами должны быть и одинаковые типы данных. SELECT department_id, department_name, location_id, city FROM departments NATURAL JOIN locations; Опция USING – можно указать только те столбцы, которые следует использовать в соединении (в отличие от натурального соединения, где используются все столбцы с согласованными именами и типами данных). SELECT employee_id, last_name, location_id, department_id FROM employees JOIN departments USING (department_id) ; Условие ON – используется для определения условия соединения. Этот подход позволит вам отделить условия соединения от других условий поиска или отбора, которые задаются в предложении WHERE. SELECT e.employee_id, e.last_name, e.department_id, d.department_id, d.location_id FROM employees e JOIN departments d ON (e.department_id = d.department_id); Для соединения трех таблиц: SELECT employee_id, city, department_name FROM employees e JOIN departments d ON d.department_id = e.department_id JOIN locations l ON d.location_id = l.location_id; - Не-эквисоединения SELECT e.last_name, e.salary, j.grade_level FROM employees e JOIN job_grades j ON e.salary BETWEEN j.lowest_sal AND j.highest_sal; - Левое внешнее соединение: SELECT e.last_name, e.department_id, d.department_name FROM employees e LEFT OUTER JOIN departments d ON (e.department_id = d.department_id); В этом запросе возвращаются все строки таблицы employees, даже если они не соответствуют строкам таблицы departments, так как таблица employees является левой таблицей во внешнем соединении. - Правое внешнее соединение: SELECT e.last_name, d.department_id, d.department_name FROM employees e RIGHT OUTER JOIN departments d ON (e.department_id = d.department_id) ; Полное внешнее соединение возвращает результаты внутреннего соединения, а также левого и правого внешнего соединений. Полное внешнее соединение не возможно в стандарте Oracle. 65 SELECT e.last_name, d.department_id, d.department_name FROM employees e FULL OUTER JOIN departments d ON (e.department_id = d.department_id); Произвольные условия соединения помощью оператора AND или WHERE. – дополнительные условия можно указать с SELECT e.employee_id, e.last_name, e.department_id, d.department_id, d.location_id FROM employees e JOIN departments d ON (e.department_id = d.department_id) AND e.manager_id = 149; или SELECT e.employee_id, e.last_name, e.department_id, d.department_id, d.location_id FROM employees e JOIN departments d ON (e.department_id = d.department_id) WHERE e.manager_id = 149; Подзапросы Подзапрос – это более сложное использование команды SELECT, создание одного запроса внутри другого. Значение, возвращаемое внутренним запросом или подзапросом, используется внешним или главным запросом. Использовать подзапрос – то же самое, что последовательно выполнить два запроса, используя результаты первого в качестве критерия поиска во втором. Синтаксис подзапроса: SELECT select_list FROM table WHERE expr operator (SELECT select_list FROM table); Рассмотрим конструкции следующих подзапросов: - Однострочный подзапрос – запросы, в которых внутренняя команда SELECT возвращает только одну строку. SELECT last_name, job_id, salary FROM employees WHERE job_id = (SELECT job_id FROM employees WHERE last_name = 'Taylor') AND salary > (SELECT salary FROM employees WHERE last_name = 'Taylor'); - Многострочный подзапрос - запросы, в которых внутренняя команда SELECT возвращает более одной строки. В таких подзапросах используются операторы IN, ANY, ALL, EXISTS. SELECT emp.last_name FROM employees emp WHERE emp.employee_id IN (SELECT mgr.manager_id FROM employees mgr); Подзапрос возвращает список значений, основной запрос обрабатывает список (IN). Оператор ANY сравнивает значение с любым значением, возвращаемым подзапросом: SELECT employee_id, last_name, job_id, salary FROM employees WHERE salary < ANY (SELECT salary FROM employees WHERE job_id = 'IT_PROG') 66 AND job_id <> 'IT_PROG'; Запрос возвращает список служащих, которые не являются программистами подразделения информационных технологий и оклады которых меньше, чем у любого программиста. Оператор ALL сравнивает значение с каждым значением, возвращаемым подзапросом. SELECT employee_id, last_name, job_id, salary FROM employees WHERE salary < ALL (SELECT salary FROM employees WHERE job_id = 'IT_PROG') AND job_id <> 'IT_PROG'; Этот запрос возвращает список служащих, которые не являются программистами и оклады которых меньше, чем у всех программистов. SELECT * FROM departments WHERE NOT EXISTS (SELECT * FROM employees WHERE employees.department_id=departments.department_id); Оператор EXISTS обеспечивает, что поиск в подзапросе не будет продолжаться, как только будет найдена хотя бы одна строка, удовлетворяющая условию WHERE в подзапросе: SELECT emp.last_name FROM employees emp WHERE EXISTS (SELECT 1 FROM employees mgr WHERE emp.employee_id = emp.employee_id ); Внутренний подзапрос не должен возвращать конкретное значение. Поэтому может быть выбрана константа (а не столбец), что с точки зрения производительности быстрее. Конструкция EXISTS может быть использована в качестве альтернативы для оператора IN. Как и NOT EXISTS для NOT IN. Связанные подзапросы – подзапрос связывается с столбцом таблицы, на который ссылается основной запрос.. Связанный подзапрос выполнится один раз для каждой строки, обрабатываемой основным запросом. SELECT last_name, salary, department_id FROM employees outer_table WHERE salary > (SELECT AVG(salary) FROM employees inner_table WHERE inner_table.department_id = outer_table.department_id); Запрос определяет сотрудников, зарабатывающих больше среднего оклада по отделу, в котором они работают. Связанный запрос выполняет подсчет среднего оклада для каждого конкретного отдела. - Многостолбцовые (или парные) подзапросы - запросы, в которых внутренняя команда SELECT возвращает более одного столбца. SELECT employee_id, manager_id, department_id FROM employees WHERE (manager_id, department_id) IN (SELECT manager_id, department_id FROM employees WHERE employee_id IN (178,174)); 67 - Подзапросы в операторе FROM: SELECT a.last_name, a.salary, a.department_id, b.salary FROM employees a, (SELECT department_id, AVG (salary) salary FROM employees GROUP by department_id) b WHERE a.department_id = b.department_id AND a.salary = b.salary; - Подзапросы в операторе ORDER BY: SELECT employee_id, last_name FROM employees e ORDER BY (SELECT department_name FROM departments d WHERE e.department_id=d.department_id): - Подзапросы в операторе CASE, DECODE SELECT employee_id, last_name, (CASE WHEN department_id= (SELECT department_id FROM departments WHERE lacation_id=1800) THEN ‘Canada’ ELSE ‘USA’ END) location FROM employees; - Подзапросы в предложении WITH: WITH above_average AS ( SELECT * FROM .employees WHERE salary > (SELECT AVG (salary) FROM hr.employees) ) SELECT last_name, first_name FROM above_average; Использование предложения WITH позволяет повысить производительность выполнения запроса. Преимуществом предложения WITH является то, что оно обрабатывается только один раз, даже если оно появляется несколько раз в запросе. Внутреннее предложение WITH реализуется либо как встроенное представление, либо как временная таблица. Оптимизатор выбирает подходящее решение в зависимости от стоимости или принимая во внимание преимущества временного хранения результатов выполнения предложения WITH. К тому же упрощается чтение запроса. Предложение WITH может содержать более одного запроса, которые в этом случае отделяются запятой: WITH dept_costs AS ( SELECT d.department_name, SUM (e.salary) AS dept_total FROM employees e JOIN departments d ON e.department_id = d.department_id GROUP BY d.department_name), avg_cost AS ( 68 SELECT SUM(dept_total)/COUNT(*) AS dept_avg FROM dept_costs) SELECT * FROM dept_costs WHERE dept_total > (SELECT dept_avg FROM avg_cost) ORDER BY department_name; - Подзапрос в команде CREATE: CREATE TABLE dept_ninety AS SELECT employee_id, last_name, first_name FROM employees WHERE department_id = 90; или с переименованием столбцов CREATE TABLE dept_ninety (id, first, last) AS SELECT employee_id, last_name, first_name FROM employees WHERE department_id = 90; Составные запросы (операторы SET) Операторы SET объединяют в один запрос результаты одного или нескольких запросов. UNION – результатом выполнения являются все отличные строки, выбранные обоими запросами: SELECT employee_id, job_id FROM employees UNION SELECT employee_id, job_id FROM job_history; UNION ALL - результатом выполнения являются все строки (включая повторяющиеся) выбранные обоими запросами: SELECT employee_id, job_id, department_id FROM employees UNION ALL SELECT employee_id, job_id, department_id FROM job_history ORDER BY employee_id; INTERSECT – результатом выполнения являются все отличные строки, входящие в пересечение строк обоих запросов: SELECT employee_id, job_id FROM employees INTERSECT SELECT employee_id, job_id FROM job_history; MINUS – результатом выполнения являются все отличные строки, выбранные первой командой SELECT, которые не выбраны второй командой SELECT: SELECT employee_id FROM employees MINUS SELECT employee_id 69 FROM job_history; Графически операторы SET представлены на Рис.5: Рис.5 Операторы изменения данных в БД Команды DML – это основа языка SQL. Они выполняются при следующих операциях: - вставка новых строк в таблицу (INSERT). - изменение существующих строк в таблице (UPDATE). - удаление существующих строк из таблицы (DELETE). - изменение или вставка строки при определенных условиях (MERGE). Совокупность команд DML, образующих логическую единицу работы, является транзакция. Управление логикой транзакции осуществляется с помощью команд COMMIT, SAVEPOINT, ROLLBACK. INSERT Команда INSERT используется для вставки новых строк в таблицу. Синтаксис: 70 INSERT INTO table [(column [, column...])] VALUES (value [, value...]); Команда INSERT с предложением VALUES позволяет вставлять в таблицу только по одной строке. INSERT INTO departments (department_id, department_name, manager_id, location_id) VALUES (70, 'Public Relations', 100, 1700); Если вставляемая строка содержит значения для всех столбцов, то перечисление столбцов в предложении INSERT не обязательно: INSERT INTO departments VALUES (70, 'Public Relations', 100, 1700); Следует помнить, что последовательность самих значений должна соответствовать последовательности столбцов в этой таблице. Лучше явно указывать список столбцов в команде INSERT. Символьные значения и даты обрамляются апострофами; числовые значения в апострофы не заключаются, так кА при этом будет выполняться неявное преобразование в тип данных NUMBER. Для вставки неопределенных значений существует два метода. - Неявный – неопределенные столбцы исключаются из списка вставляемых столбцов: INSERT INTO departments (department_id, department_name) VALUES (30, 'Purchasing'); - Явный – задается ключевое слово NULL в списке VALUES: INSERT INTO departments VALUES (100, 'Finance', NULL, NULL); Задание пустой строки ‘’ в списке VALUES допускается только для символьных значений и дат. Для вставки специальных значений в таблицу можнл использовать функции. Пример использования функции SYSDATE для вставки в столбец hire_date текущей даты и времени: INSERT INTO employees (employee_id, first_name, last_name, email, phone_number, hire_date, job_id, salary, commission_pct, manager_id, department_id) VALUES (113, 'Louis', 'Popp', 'LPOPP', '515.124.4567', SYSDATE, 'AC_ACCOUNT', 6900, NULL, 205, 110); Для вставки конкретного значения даты используется функция TO_DATE: INSERT INTO employees VALUES (114, 'Den', 'Raphealy', 'DRAPHEAL', '515.127.4561', 71 TO_DATE ('ФЕВ 3, 2009', 'MON DD, YYYY'), 'SA_REP', 11000, 0.2, 100, 60); Синтаксис многотабличного INSERT: INSERT [ALL|FIRST] [WHEN condition THEN] [insert_into_clause values_clause] [ELSE] [insert_into_clause values_clause] [insert_into_clause values_clause] (subquery) Пример INSERT ALL INTO sal_history VALUES(EMPID,HIREDATE,SAL) INTO mgr_history VALUES(EMPID,MGR,SAL) SELECT employee_id EMPID, hire_date HIREDATE, salary SAL, manager_id MGR FROM employees WHERE employee_id > 200; INSERT ALL WHEN HIREDATE < '01-ЯНВ-95' THEN INTO emp_history VALUES(EMPID,HIREDATE,SAL) WHEN COMM IS NOT NULL THEN INTO emp_sales VALUES (EMPID,COMM,SAL) SELECT employee_id EMPID, hire_date HIREDATE, salary SAL, commission_pct COMM FROM employees; INSERT ALL INTO sales_info VALUES (employee_id,week_id,sales_MON) INTO sales_info VALUES (employee_id,week_id,sales_TUE) INTO sales_info VALUES (employee_id,week_id,sales_WED) INTO sales_info VALUES (employee_id,week_id,sales_THUR) INTO sales_info VALUES (employee_id,week_id, sales_FRI) SELECT EMPLOYEE_ID, week_id, sales_MON, sales_TUE, sales_WED, sales_THUR,sales_FRI FROM sales_source_data; INSERT FIRST WHEN salary < 5000 THEN INTO sal_low VALUES (employee_id, last_name, salary) WHEN salary between 5000 and 10000 THEN INTO sal_mid VALUES (employee_id, last_name, salary) ELSE INTO sal_high VALUES (employee_id, last_name, salary) SELECT employee_id, last_name, salary FROM employees; UPDATE Для обновления существующих строк используется команда UPDATE. Синтаксис: UPDATE table 72 SET column = value [, column = value, ...] [WHERE condition]; Можно одновременно обновлять несколько строк. Предложение WHERE позволяет изменить конкретную строку или строки: UPDATE employees SET department_id = 50 WHERE employee_id = 113; Если предложение WHERE отсутствует, обновляются все строки таблицы: UPDATE copy_emp SET department_id = 110; DELETE Удалять существующие строки модно с помощью команды DELETE. Синтаксис: DELETE [FROM] table [WHERE condition]; Удалить из таблицы конкретную строку или строки можно с помощью предложения WHERE. Если ни одна строка не была удалена, выдается сообщение “0 rows deleted”. DELETE FROM departments WHERE department_name = 'Finance'; Если предложение WHERE отсутствует, удаляются все строки таблицы: DELETE FROM copy_emp; Для удаления всех строк таблицы и освобождения памяти лучше использовать команду DDL TRUNCATE – усечение таблицы. Синтаксис команды: TRUNCATE TABLE table_name; Пример: TRUNCATE TABLE copy_emp; Откат команды невозможен. Команда DELETE удаляет строки из таблицы, но не освобождает место в табличном пространстве, занимаемое строками. Команда TRUNCATE выполняется быстрее. MERGE Изменять или вставлять новые строки при определенных условиях, можно, используя команду MERGE (слияние). Синтаксис: MERGE INTO table_name table_alias USING (table|view|sub_query) alias ON (join condition) WHEN MATCHED THEN UPDATE SET col1 = col1_val, col2 = col2_val WHEN NOT MATCHED THEN 73 INSERT (column_list) VALUES (column_values); Пример: MERGE INTO copy_emp c USING (SELECT * FROM EMPLOYEES ) e ON (c.employee_id = e.employee_id) WHEN MATCHED THEN UPDATE SET c.first_name = e.first_name, c.last_name = e.last_name, ... WHEN NOT MATCHED THEN INSERT VALUES(e.employee_id, e.first_name, e.last_name, e.email, e.phone_number, e.hire_date, e.job_id, e.salary, e.commission_pct, e.manager_id, e.department_id); Обрабатывается условие (c.employee_id = e.employee_id). Taк как таблица copy_emp не заполнена, условие возвращает значение ложно (false) – нет соответствий. Выполняется условие WHEN NOT MATCHES и по команде MERGE вставляются строки из таблицы employees в таблицу copy_emp. Если строки в таблице copy_emp существуют и номер служащего (employee_id) совпадает в обеих таблицах, существующие строки таблицы copy_emp изменяются так, чтобы соответствовать строкам таблицы employees. COMMIT, SAVEPOINT, ROLLBACK Сервер Oracle обеспечивает согласованность данных на основе транзакций. Транзакции состоят из команд DMLб составляющих одно согласованное изменение данных. Транзакция начинается с первой исполняемой команды SQL и заканчивается, когда свершается одно из следующих событий: - команда COMMIT или ROLLBACK; команда DDL, например, CREATE; команда DCL; пользователь выходит из программы iSQL*Plus; отказы системы; Когда одна транзакция завершена, следующая исполняемая команда SQL автоматически начинает следующую транзакцию. Результаты выполнения команды DDL или DCL фиксируются автоматически. Следовательно, эти команды неявно завершают транзакцию. Управление логикой транзакций осуществляется с помощью следующих команд: COMMIT – завершает текущую транзакцию, делая все незафиксированные изменения постоянными, SAVEPOINT имя – создает точку сохранения в текущей транзакции, ROLLBACK – прекращает текущую транзакцию, отменяя все произведенные изменения в данных, 74 ROLLBACK TO SAVEPOINT имя – отменяет все произведенные изменения до точки сохранения. Так как точки сохранения создаются логически, просмотреть список созданных точек сохранения нельзя. Пример: UPDATE... SAVEPOINT update_done; INSERT... ROLLBACK TO update_done; DELETE FROM employees WHERE employee_id = 99999; INSERT INTO departments VALUES (290, 'Corporate Tax', NULL, 1700); COMMIT; DELETE FROM copy_emp; ROLLBACK; Каждое изменение данных, выполненное в ходе транзакции, является временным до тех пор, пока транзакция не будет зафиксирована. Операции манипулирования данными изменяют только буфер базы данных, следовательно, предыдущее состояние данных может быть восстановлено. Текущий пользователь может проверить результаты своих операций манипулирования данными путем запросов к таблицам. Другие пользователи не могут видеть результаты манипулирования данными, выполняемых текущим пользователем. Сервер Oracle обеспечивает согласованность чтения, и каждый пользователь видит данные такими, какими они были зафиксированы последней командой COMMIT. Строки, с которыми в данный момент проводятся операции, блокируются: другие пользователи изменять их не могут. Все временные изменения становятся постоянными после из фиксации командой COMMIT. После выполнения команды COMMIT, измененные данные записываются в базу данных. Прежнее состояние данных безвозвратно теряется. Все пользователи могут, идет результаты транзакции. Блокировки с измененных строк снимаются, другие пользователи получают возможность изменять данные в этих строках. Все точки сохранения удаляются. Любые не зафиксированные изменения можно отменить командой ROLLBACK. После выполнения команды ROLLBACK изменения в данных отменяются. Восстанавливается прежнее состояние данных. Блокировка с соответствующих строк снимаются. Подзапросы в командах DML INSERT INTO (SELECT employee_id, last_name, email, hire_date, job_id, salary,department_id FROM employees WHERE department_id = 50) VALUES (99999, 'Taylor', 'DTAYLOR', TO_DATE ('07-JUN-99', 'DD-MON-RR'), 'ST_CLERK', 5000, 50); UPDATE departments SET (manager_id, location_id) = (SELECT manager_id, location_id FROM departments WHERE department_id = 10) WHERE department_id = 20; UPDATE (SELECT salary, commission_pct FROM employees WHERE DEPARTMENT_ID = 80) SET salary = salary * 1.1 75 WHERE commission_pct < .2; DELETE FROM empl6 WHERE employee_id = (SELECT employee_id FROM emp_history WHERE employee_id = E.employee_id); DELETE FROM (SELECT * FROM job_history WHERE employee_id = 101); Использование однострочных и групповых функций Функции увеличивают мощность простого блока запроса и используются для манипулирования значениями данных. Рассмотрим однострочные функции для работы с числами, строками, датами, функции преобразования данных из одного типа в другой. А также групповые функции – функции для получения сводной информации по группам строк. Однострочные функции Однострочные функции работают только с одной строкой и возвращают по одному результату для каждой строки. Синтаксис однострочных функций: function_name [(arg1, arg2,...)] где function_name – имя функции, arg1, arg2,.. – аргументы, которые могут быть столбцами, выражениями, константами или значениями переменных . Символьные Однострочные символьные функции принимают на входе символьные данные, а возвращают символьное или числовое значение. Символьные функции делятся на: – функции преобразования регистра символов - LOWER, UPPER, INITCAP. Примеры преобразований с помощью функций: LOWER ('SQL Course') результат => sql course – преобразует алфавитные символы в нижний регистр, UPPER ('SQL Course') => SQL COURSE – преобразует алфавитные символы в верхний регистр, INITCAP ('SQL Course')=> Sql Course – преобразует алфавитные символы: первая буква каждого слова становится заглавной, остальные – строчные. Пример использования функций в операторе SELECT: SELECT employee_id, last_name, department_id FROM employees WHERE last_name = 'higgins'; - данные не найдены. SELECT employee_id, last_name, department_id FROM employees WHERE LOWER(last_name) = 'higgins'; - выборка успешна. – функции манипулирования символами - CONCAT, SUBSTR, LENGTH, INSTR, LPAD, RPAD, TRIM, REPLACE. Примеры преобразований с помощью функций: CONCAT ('Hello', 'World')=> HelloWorld – присоединяет первое символьное значение ко второму, эквивалентно оператору конкатенации (||), SUBSTR ('HelloWorld',1,5) => Hello – возвращает 5 символов символьного выражения, начиная с символа 1. Если на месте второго аргумента стоит отрицательное число, 76 отсчет начинается с конца символьной строки. Если третий аргумент отсутствует, возвращаются все символы до конца строки. LENGTH ('HelloWorld') => 10 – возвращает количество символов в аргументе. INSTR ('HelloWorld', 'W') => 6 – возвращает номер позиции строки второго аргумента в символьном значении первого аргумента. Дополнительно можно задать позицию начала поиска (третий аргумент) и число обнаружений (четвертый аргумент). По умолчанию третий и четвертый аргументы равны 1, что означает выполнение поиска в первом аргументе, начиная с первой позиции до первого обнаружения. LPAD (salary,10,'*') => *****24000 – дополняет символьное значение первого аргумента слева до длины второго аргумента (10) символами третьего аргумента ('*'). RPAD (salary, 10, '*') => 24000***** 24000 – дополняет символьное значение первого аргумента справа до длины второго аргумента (10) символами третьего аргумента ('*'). REPLACE ('JACK and JUE','J','BL') => BLACK and BLUE – выполняет поиск строки второго аргумента ('J') по текстовому значению первого аргумента и в случае обнаружения производится ее замена на строку третьего аргумента ('BL'). TRIM (LEADING 'H' FROM 'HelloWorld') – elloWorld – позволяет вырезать из исходной строки ('HelloWorld') начальные (LEADING) или конечные (TRAILING) символы или и те, и другие (BOTH). Пример использования функций в операторе SELECT: SELECT employee_id, CONCAT (first_name, last_name) NAME, job_id, LENGTH (last_name), INSTR (last_name, 'a') "Contains 'a'?" FROM employees WHERE SUBSTR(job_id, 4) = 'REP'; Числовые - ROUND, TRUNC, MOD Числовые функции принимают на входе числовые данные и возвращают числовые значения. ROUND (45.926, 2) => 45.93 - округляет значение первого аргумента до десятичных разрядов, определяемых вторым аргументом. Если второй аргумент опущен, то до целого. Если второй аргумент отрицательный, округляются разряды слева от десятичной точки. TRUNC (45.926, 2) => 45.92 - усекается значение первого аргумента до десятичных разрядов, определяемых вторым аргументом. Если второй аргумент опущен, то до целого. Если второй аргумент отрицательный, округляются разряды слева от десятичной точки. MOD (1600, 300) => 100 – возвращает остаток от деления значения первого аргумента на значение второго аргумента. Для работы с датами - SYSDATE, MONTHS_BETWEEN, ADD_MONTHS, NEXT_DAY, LAST_DAY, ROUND, TRUNC Oracle хранит даты во внутреннем числовом формате, представляющем столетие, год, месяц, число, часы, минуты и секунды. SYSDATE – это функция, возвращающая текущую дату и время сервера базы данных. Можно использовать SYSDATE также, как любое другое имя столбца. Обычно для получения текущей даты используют запрос из фиктивной таблицы DUAL: SELECT sysdate FROM dual; 77 С датами можно выполнять арифметические действия сложения и вычитания. Прибавлять и вычитать можно числовые константы и даты. При вычитании даты из даты в результате получается количество дней: SELECT last_name, (SYSDATE-hire_date)/7 AS WEEKS FROM employees WHERE department_id = 90; Примеры функций для работы с датами: MONTHS_BETWEEN ('01-СЕН-95','11-ЯНВ-94') => 19.6774194 – число месяцев разделяющих две даты. ADD_MONTHS (‘31-ЯНВ-96',1) => '29-ФЕВ-96' – добавление календарных месяцев к дате. NEXT_DAY ('01-СЕН-95','ПЯТНИЦА') => '08-СЕН-95' – ближайшая дата, когда наступит заданный день недели. LAST_DAY ('01-ФЕВ-95') => '28-ФЕВ-95' – последняя дата текущего месяца. ROUND (SYSDATE,'MONTH') => 01-АВГ-03 – возвращает дату, округленную до месяца. ROUND (SYSDATE ,'YEAR') => 01-ЯНВ-04 - возвращает дату, округленную до года. TRUNC (SYSDATE ,'MONTH') => 01-ИЮЛ-03 - возвращает дату, усеченную до месяца. TRUNC (SYSDATE ,'YEAR') => 01-ЯНВ-03 - возвращает дату, усеченную до года. Функции преобразования типов данных - TO_CHAR, TO_DATE, TO_NUMBER. В некоторых случаях сервер Oracle допускает данные какого-то типа там, где он ожидает данные другого типа. Это допускается, если сервер Oracle может автоматически привести данные к определенному типу. Такое преобразование типов данных может производиться неявно сервером Oracle или явно пользователем. Синтаксис функции TO_CHAR: TO_CHAR(число|дата, {формат}, {nlsparams}) – преобразует число или дату в строку символов в соответствии с форматом. Nlsparams определяет при преобразовании чисел знак отделения десятичных разрядов, знак отделения групп символов, знак обозначения валюты. При преобразовании дат nlsparams устанавливает язык, на котором будут отображаться наименования месяцев и дней. Если nlsparams или формат опущены, то функция использует параметры по умолчанию, установленные для данной сессии. Пример применения функции TO_CHAR: SELECT last_name, TO_CHAR(hire_date, 'fmDD Month YYYY') AS HIREDATE FROM employees; Элементы формата даты: YYYY – полный год цифрами. YEAR – год прописью. MM – двузначное цифровое обозначение месяца. MONTH – полное название месяца. DY – трехзначное алфавитное сокращенное название дня недели. DAY – полное название дня недели. DD – номер дня месяца. Элемент fm (fill mode) используется для удаления конечных пробелов и ведущих нулей. 78 Синтаксис функции TO_NUMBER: TO_NUMBER(строка, {формат}, {nlsparams}) – преобразует символьную строку, содержащую цифры, в число в соответствии с форматом. Синтаксис функции TO_DATE: TO_DATE(строка, {формат}, {nlsparams}) – преобразует символьную строку, содержащую дату, в дату в соответствии с форматом. Общие функции - NVL, NVL2, NULLIF, COALEASCE. Эти функции работают с любыми типами данных и используются для обработки неопределенных значений списка выражений. Пример использования функции NVL для преобразования неопределенного значения (NULL) в действительное: SELECT last_name, salary, NVL(commission_pct, 0), (salary*12) + (salary*12*NVL(commission_pct, 0)) AN_SAL FROM employees; В примере функция NVL используется для преобразования неопределенных значений в ноль, чтобы вычислить годовой доход служащих. Пример использования функции NVL2: SELECT last_name, salary, commission_pct, NVL2 (commission_pct, 'SAL+COMM', 'SAL') income FROM employees WHERE department_id IN (50, 80); Функция NVL2 проверяет значение первый аргумент. Если он определен, тогда функция NVL2 возвращает значение второго аргумента. Если значение первого аргумента не определено, результатом работы функции будет значение третьего аргумента. Пример использования функции NULLIF: SELECT first_name, LENGTH (first_name) "expr1", last_name, LENGTH (last_name) "expr2", NULLIF (LENGTH (first_name), LENGTH (last_name)) result FROM employees; Функция NULLIF сравнивает значения двух аргументов. Если они равны, функция возвращает неопределенное значение, если нет – значение первого аргумента. Пример использования функции COALESCE: SELECT last_name, employee_id, COALESCE (TO_CHAR (commission_pct), TO_CHAR (manager_id), 'No commission and no manager') FROM employees; Функция COALESCE возвращает значение первого определенного аргумента в списке. Функции условной обработки – CASE, DECODE. Пример использования выражения CASE: SELECT last_name, job_id, salary, CASE job_id WHEN 'IT_PROG’ THEN 1.10*salary WHEN 'ST_CLERK' THEN 1.15*salary WHEN 'SA_REP' THEN 1.20*salary ELSE salary END "REVISED_SALARY" FROM employees; 79 Выражение CASE позволяет производить логическую обработку оператора IF-THENELSE в командах SQL, не вызывая процедуру. Функция DECODE действует аналогичным образом. Синтаксис: DECODE (столбец|выражение, вариант1, результат1 [, вариант2, результат2,...,] [, результат по умолчанию]) Функция DECODE расшифровывает столбец или выражение после сравнения его с каждым искомым значением варианта. Если выражение равно искомому значению, функция возвращает соответствующий результат. Если выражение не совпадает ни с одним из искомых значений, а результат по умолчанию не задан, функция возвращает неопределенное значение. Пример использования функции DECODE: SELECT last_name, job_id, salary, DECODE (job_id, 'IT_PROG', 1.10*salary, 'ST_CLERK', 1.15*salary, 'SA_REP', 1.20*salary, salary) REVISED_SALARY FROM employees; Многострочные функции Многострочные функции работают с группой строк и выдают по одному результату для каждой группы. Их часто называют групповыми функциями. Это: AVG – среднее значение; COUNT - количество строк; MAX – максимальное значение; MIN – минимальное значение; STDDEV - стандартное отклонение значений; SUM – суммирование значений; VARIANCE – дисперсия значений. Синтаксис применения групповых функций: SELECT group_function (column), ... FROM table [WHERE condition] [ORDER BY column]; Пример использования групповых функций: SELECT AVG (salary), MAX (salary), MIN (salary), SUM(salary) FROM employees WHERE job_id LIKE '%REP%'; Запрос вычисляет средний, самый высокий, самый низкий оклад и сумму окладов всех торговых представителей. Для функции COUNT имеется три формата: - COUNT(*) – возвращает количество строк в таблице, которые удовлетворяют ограничениям, заданным в команде SELECT. При этом учитываются и строки дубликаты и строки с неопределенными значениями. 80 Пример: SELECT COUNT(*) FROM employees WHERE department_id = 50; - COUNT(выражение) – возвращает количество строк с определенными значениями, заданными выражением: SELECT COUNT(commission_pct) FROM employees WHERE department_id = 80; - COUNT(DISTINCT выражение) – возвращает количество уникальных, определенных значений в столбце, заданном выражением: SELECT COUNT(DISTINCT department_id) FROM employees; C помощью предложения GROUP BY можно разделить строки таблицы на группы. Затем можно использовать групповые функции для получения сводной информации по каждой группе. Синтаксис: SELECT column, group_function (column) FROM table [WHERE condition] [GROUP BY group_by_expression] [ORDER BY column]; Пример: SELECT department_id, AVG (salary) FROM employees GROUP BY department_id; Если используется предложение GROUP BY, все столбцы из списка SELECT, к которым не применяются групповые функции, должны быть включены в предложение GROUP BY. В примере выдаются номера отделов и средний оклад по каждому отделу. Столбец, указанный в GROUP BY, может отсутствовать в списке SELECT: SELECT AVG (salary) FROM employees GROUP BY department_id; Чтобы получить результаты по группам и подгруппам, следует указать несколько столбцов в предложении GROUP BY. Синтаксис: SELECT column, group_function (column) FROM table [WHERE condition] [GROUP BY group_by_expression] [ORDER BY column]; Пример: SELECT department_id, job_id, SUM (salary) FROM employees WHERE department_id > 40 GROUP BY department_id, job_id ORDER BY department_id; 81 Предложение GROUP BY указывает, как должны быть сгруппированы строки: сначала по номерам отделов, затем по должностям внутри отделов. Таким образом, функция SUM применяется к столбцу окладов по каждой должности внутри каждого отдела. Если в одной о той же команде SELECT указываются отдельные элементы и групповые функции, предложение GROUP BY со списком отдельных элементов обязательно. При выполнении следующего запроса будет выдаваться сообщение об ошибке: SELECT department_id, COUNT (last_name) FROM employees; Предложение WHERE для исключения групп не используется. Следующая команда SELECT вызовет ошибку: SELECT department_id, AVG (salary) FROM employees WHERE AVG (salary) > 8000 GROUP BY department_id; Для исключения групп следует использовать предложение HAVING. Синтаксис: SELECT column, group_function FROM table [WHERE condition] [GROUP BY group_by_expression] [HAVING group_condition] [ORDER BY column]; Пример применения предложение HAVING: SELECT department_id, MAX (salary) FROM employees GROUP BY department_id HAVING MAX (salary)>10000; Выводятся только те номера отделов и максимальный оклад только тех отделов, где он превышает 10000. Пример применения предложения WHERE для ограничения по столбцу совместно с ограничением по групповой функцией: SELECT job_id, SUM (salary) PAYROLL FROM employees WHERE job_id NOT LIKE '%REP%' GROUP BY job_id HAVING SUM (salary) > 13000 ORDER BY SUM (salary); Выводятся должности и общая сумма окладов за месяц по каждой должности, если эта общая сумма превышает 13000. Из выходных данных исключаются данные о торговых представителях. Список сортируется по возрастанию общей суммы окладов. В команде запроса в предложении GROUP BY можно задавать операторы ROLLUP и CUBE. Операция ROLLUP выдает результат группировки, содержащий обычные сгруппированные строки и строки промежуточных итогов. При использовании операции 82 CUBE выбранные строки группируются на основе значений всех возможных комбинаций заданных выражений. В результате возвращается по одной строке итоговой информации для каждой группы. Оператор CUBE можно использовать для получения строк сводных перекрестных отчетов. Для использования операторов ROLLUP и CUBE необходимо, чтобы столбцы, указанные в предложении GROUP BY, были связаны значимыми и реальными отношениями друг с другом, иначе операторы вернут не относящуюся к делу информацию. Пример использования ROLLUP: SELECT department_id, job_id, SUM (salary) FROM employees WHERE department_id<60 GROUP BY ROLLUP (department_id, job_id); В примере общая сумма окладов по каждой должности внутри отделов, номера которых меньше 60, выводится на основе предложения GROUP BY. Оператор ROLLUP выдает: - общую сумму окладов сотрудников отделов, номера которых меньше 60. - общую сумму окладов сотрудников всех отделов, номера которых меньше 60, независимо от должности. Оператор ROLLUP вырабатывает промежуточные итоговые данные, которые сворачиваются (roll up) с наименьшего уровня до самого высокого уровня в порядке, заданном списком группируемых столбцов в предложении GROUP BY. Пример использования CUBE: SELECT department_id, job_id, SUM (salary) FROM employees WHERE department_id<60 GROUP BY CUBE (department_id, job_id); В примере общая сумма окладов по каждой должности внутри отделов, номера которых меньше 60, выводится на основе предложения GROUP BY. Оператор CUBE выдает: - общие суммы окладов сотрудников по каждому отделу, номер которого меньше 60. - общие суммы окладов сотрудников по всем должностям независимо от отдела, полученных на основе данных, в которых номер отдела меньше 60. - общие суммы окладов сотрудников всех отделов, номера которых меньше 60, независимо от должности. Оператор CUBE выполняет операцию ROLLUP для выработки промежуточных итогов по отделам, номера которых меньше 60, независимо от наименования должности. Дополнительно оператор CUBE выводит общий оклад по каждой должности независимо от отделов. Вместе с оператором CUBE или оператором ROLLUP может быть использована функция GROUPING. Она помогает понять, как были получены итоговые значения. Функция GROUPING использует единственный столбец в качестве аргумента. Значение этого аргумента должно соответствовать одному из выражений в предложении GROUP BY. Функция GROUPING возвращает 0 или 1. Значения, возвращаемые функцией GROUPING, полезны для: 83 - - определения уровня агрегирования данного промежуточного итога, т.е. для нахождения группы или групп, на которых основывается промежуточная итоговая сумма; определения того, неопределенное значение (NULL) в выражении столбца – это результат который отражает факт, что: значение NULL получено из базовой таблицы, хранящей значение NULL; значение NULL создано ROLLUP/CUBE, как результат групповой функции, примененной к этому выражению. Значение 0, возвращенное функцией GROUPING, отражает одну из следующих ситуаций: - выражение использовалось для подсчета агрегированного значения; значение NULL в выражении – это хранимое значение NULL. Значение 1, возвращенное функцией GROUPING, отражает одну из следующих ситуаций: - выражение не использовалось для подсчета агрегированного значения. Значение NULL в выражении столбца создано функциями ROLLUP или CUBE как результат группировки. Пример использования функции GROUPING: SELECT department_id deptid, job_id job, SUM(salary), GROUPING (department_id) grp_dept, GROUPING (job_id) grp_job FROM employees WHERE department_id<50 GROUP BY ROLLUP (department_id, job_id); Дальнейшее расширение предложения GROUP BY – это GROPING SETS, которое позволяет задавать несколько группировок данных. Такие действия повышают эффективность агрегирования, а также помогают анализировать данные в зависимости от многих размерностей. Использование GROUPING SETS позволяет написать только одну команду SELECT и определить в ней различные группировки (которые могут также включать операторы ROLLUP или CUBE). Без такой возможности необходимо определять несколько команд SELECT, объединенных операторами UNION ALL. Например, можно написать команду: SELECT department_id, job_id, manager_id, AVG (salary) FROM employees GROUP BY GROUPING SETS ((department_id, job_id, manager_id), (department_id, manager_id), (job_id, manager_id)); По этой команде подсчитываются итоговые значения для трех группировок; (department_id, job_id, manager_id), (department_id, manager_id) и (job_id, manager_id). Без этой дополнительной возможности потребовалось бы объединение с помощью UNION ALL нескольких запросов для получения такого же результата. Многозапросный подход неэффективен, так как требует многократного сканирования одних и тех же данных. 84 Аналитические функции Некоторые вопросы об информации в базе данных задают часто, но реализующие их запросы сложно написать на обычном языке SQL (кроме того, такие запросы не всегда быстро работают). Эти проблемы часто можно решить с помощью аналитических функций, которые добавляют расширения языка SQL, упрощающие создание запросов и существенно повышающие производительность по сравнению с обычным SQL-запросом. Ряд запросов, которые сложно сформулировать на обычном языке SQL, весьма типичны: - Подсчет промежуточной суммы. Показать суммарную зарплату сотрудников отдела построчно, чтобы в каждой строке выдавалась сумма зарплат всех сотрудников вплоть до указанного. - Подсчет процентов в группе. Показать, какой процент от общей зарплаты по отделу составляет зарплата каждого сотрудника. Берем его зарплату и делим на сумму зарплат по отделу. - Запросы первых N. Найти N сотрудников с наибольшими зарплатами или N наиболее продаваемых товаров по регионам. - Подсчет скользящего среднего. Получить среднее значение по текущей и предыдущим N строкам. - Выполнение ранжирующих запросов. Показать относительный ранг зарплаты сотрудника среди других сотрудников того же отдела. Сервер Oracle предлагает 26 аналитических функций. Они разбиваются на четыре основных класса по возможностям. Первый класс образуют различные функции ранжирования, позволяющие строить запросы типа "первых N". Второй класс образуют оконные функции, позволяющие вычислять разнообразные агрегаты. В качестве функции агрегирования можно использовать SUM, COUNT, AVG, MIN, МАХ и т.д. К третьему классу относятся различные итоговые функции. Они очень похожи на оконные, поэтому имеют те же имена: SUM, MIN, MAX и т.д. Тогда как оконные функции используются для работы с окнами данных, итоговые функции работают со всеми строками фрагмента или группы. Есть также функции LAG и LEAD, позволяющие получать значения из предыдущих или следующих строк результирующего множества. Это помогает избежать самосоединения данных. Например, если в таблице записаны даты визитов пациентов к врачу и необходимо вычислить время между визитами для каждого их них, очень пригодится функция LAG. Можно просто фрагментировать данные по пациентам и отсортировать их по дате. После этого функция LAG легко сможет вернуть данные предыдущей записи для пациента. Останется вычесть из одной даты другую. До появления аналитических функций для получения этих данных приходилось организовывать сложное соединение таблицы с ней же самой. Пример применения аналитических функций для подсчета промежуточной суммы зарплат по отделам: SELECT last_name, department_id, salary, SUM (salary) OVER (ORDER BY department_id, last_name) running_total, 85 SUM (salary) OVER (PARTITION BY department_id ORDER BY last_name) department_total, ROW_NUMBER () OVER (PARTITION BY department_id ORDER BY last_name) seq FROM employees ORDER BY department_id, last_name Запрос выдал следующие значения: running_total – сумма зарплат в целом. Это было сделано по всему упорядоченному результирующему множеству с помощью конструкции SUM (SAL) OVER (ORDER BY department_id , last_name). department_total – подсчет промежуточных сумм по отделам, сбрасывая их в ноль при переходе к следующему отделу. Этого удалось добиться благодаря конструкции PARTITION BY department_id - в запросе была указана конструкция, задающая условие разбиения данных на группы. SEQ - последовательная нумерация строк в каждой группе, в соответствии с критериями упорядочения, использовалась функция ROW_NUMBER() . Синтаксис вызова аналитической функции имеет следующую конструкцию: ИМЯ_ФУНКЦИИ (<аргумент>,< аргумент >, . . . ) OVER (<конструкция_фрагментации> <конструкция_упорядочения> <конструкция_окна>) Вызов аналитической функции может содержать до четырех частей: аргументы, конструкция фрагментации, конструкция упорядочения, конструкция, задающая окно. В представленном выше примере: SUM (sal) OVER (PARTITION BY department_id ORDER BY last_name) department_total, SUM - имя функции. (salary) - аргумент аналитической функции. Аналитические функции принимают от нуля до трех аргументов. В качестве аргументов передаются выражения, т.е. вполне можно было бы использовать SUM(salary + commission_pct). OVER - ключевое слово, идентифицирующее эту функцию как аналитическую. В противном случае синтаксический анализатор не мог бы отличить функцию агрегирования SUM() от аналитической функции SUM(). Конструкция после ключевого слова OVER описывает срез данных, "по которому" будет вычисляться аналитическая функция. PARTITION BY department_id - необязательная конструкция фрагментации. Если конструкция фрагментации не задана, все результирующее множество считается одним большим фрагментом. Это используется для разбиения результирующего множества на группы, так что аналитическая функция применяется к группам, а не ко всему результирующему множеству. В первом примере главы, когда конструкция фрагментации не указывалась, функция SUM по столбцу salary вычислялась для всего результирующего множества. Фрагментируя результирующее множество по столбцу department_id, мы вычисляли SUM по столбцу salary для каждого отдела (department_id), сбрасывая промежуточную сумму для каждой группы. Конструкция ORDER BY определяет, как упорядочиваются данные в группах при вычислении аналитической функции. В нашем 86 случае упорядочивать по department_id и last_name не нужно, потому что по столбцу department_id выполнялась фрагментация, т.е. неявно предполагается, что столбцы, по которым выполняется фрагментация, по определению входят в ключ сортировки (конструкция ORDER BY применяется к каждому фрагменту поочередно). ORDER BY last_name - необязательная конструкция ORDER BY; для некоторых функций она обязательна, для других - нет. Функции, зависящие от упорядочения данных, например LAG и LEAD, которые позволяют обратиться к предыдущим и следующим строкам в результирующем множестве, требуют обязательного указания конструкции ORDER BY. Другие функции, например AVG, не требуют. • Конструкция окна в данном примере отсутствует. Теперь более детально рассмотрим каждую из четырех частей вызова аналитической функции, чтобы понять, как их можно задавать. Конструкция фрагментации Конструкция PARTITION BY логически разбивает результирующее множество на N групп по критериям, задаваемым выражениями фрагментации. Слова "фрагмент" и "группа" в пособии и в документации Oracle используются как синонимы. Аналитические функции применяются к каждой группе независимо, - для каждой новой группы они сбрасываются. Например, в примере при демонстрации функции, вычисляющей промежуточную сумму зарплат, фрагментация выполнялась по столбцу department_id. Когда значение в столбце department_id в результирующем множестве изменялось, происходил сброс промежуточной суммы в ноль, и суммирование начиналось заново. Если не указать конструкцию фрагментации, все результирующее множество считается одной группой. В примере использовалась функция SUM(salary) без конструкции фрагментации, чтобы получить промежуточные суммы для всего результирующего множества. Каждая аналитическая функция в запросе может иметь уникальную конструкцию фрагментации. Для столбца running_total конструкция фрагментации не была задана, поэтому целевой группой было все результирующее множество. Для столбца departmental_total результирующее множество фрагментируется по отделам, что позволило вычислять промежуточные суммы для каждого из них. Синтаксис конструкции фрагментации прост и очень похож на синтаксис конструкции GROUP BY в обычных SQL-запросах: PARTITION BY выражение [, выражение] [, выражение] Конструкция упорядочения Конструкция ORDER BY задает критерий сортировки данных в каждой группе (в каждом фрагменте). Это, несомненно, влияет на результат выполнения любой аналитической функции. При наличии (или отсутствии) конструкции ORDER BY аналитические функции вычисляются по-другому. В качестве примера рассмотрим, что происходит при использовании функции AVG() с конструкцией ORDER BY и без оной: SELECT last_name, salary, avg (salary) OVER () FROM employees; SELECT last_name, salary, avg (salary) OVER (ORDER BY last_name) FROM employees ORDER BY last_name; 87 В отсутствие конструкции ORDER BY среднее значение вычисляется по всей группе, и одно и то же значение выдается для каждой строки (функция используется как итоговая). Когда функция AVG () используется с конструкцией ORDER BY, среднее значение в каждой строке является средним по текущей и всем предыдущим строкам (функция используется как оконная). Конструкция ORDER BY в аналитических функциях имеет следующий синтаксис: ORDER BY выражение [ASC | DESC] [NULLS FIRST | NULLS LAST] Она совпадает с конструкцией ORDER BY для запроса, но будет упорядочивать строки только в пределах фрагментов и может не совпадать с конструкцией ORDER BY для запроса в целом (или любого другого фрагмента). Конструкции NULLS FIRST и NULLS LAST позволяют указать, где при упорядочении должны быть значения NULL - в начале или в конце. Конструкция окна Конструкция вида RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, задает стандартное окно при использовании конструкции ORDER BY. Конструкция окна позволяет задать перемещающееся или жестко привязанное окно (набор) данных в пределах группы, с которым будет работать аналитическая функция. Например, конструкция диапазона RANGE UNBOUNDED PRECEDING означает: "применять аналитическую функцию к каждой строке данной группы, с первой по текущую". Стандартным является жестко привязанное окно, начинающееся с первой строки группы и продолжающееся до текущей. Если используется следующая аналитическая функция: SUM (salary) OVER (PARTITION BY department_id ORDER BY last_name ROWS 2 PRECEDING) department_total2, то будет создано перемещающееся окно в группе, и сумма зарплат будет вычисляться по столбцу salary текущей и двух предыдущих строк в этой группе. Если необходимо создать отчет, показывающий сумму зарплат текущего и двух предыдущих сотрудников отдела, соответствующий сценарий может выглядеть так: SELECT department_id, last_name, salary, sum (salary) OVER (PARTITION BY department_id ORDER BY last_name ROWS 2 PRECEDING) sliding_total FROM employees ORDER BY department_id, last_name; Конструкция, определяющая фрагментацию, приводит к вычислению SUM (salary) по отделам, независимо от других групп (значение SUM(salary) сбрасывается при изменении номера отдела). Конструкция ORDER BY last_name приводит к сортировке данных в каждом отделе по столбцу last_name; это позволяет с помощью конструкции окна, ROWS 2 PRECEDING, при суммировании зарплат обращаться к двум предыдущим строкам в соответствии с заданным порядком сортировки. Можно создавать окна по двум критериям: по диапазону (RANGE) значений данных или по смещению (ROWS) относительно текущей строки. 88 Конструкция RANGE UNBOUNDED PRECEDING, например, требует брать все строки вплоть до текущей, в соответствии с порядком, задаваемым конструкцией ORDER BY. Для использования окон необходимо задавать конструкцию ORDER BY. Окна диапазона Окна диапазона объединяют строки в соответствии с заданным порядком. Если в запросе сказано, например, "range 5 preceding", то будет сгенерировано перемещающееся окно, включающее предыдущие строки группы, отстоящие от текущей строки не более чем на 5 строк. Диапазон можно задавать в виде числового выражения или выражения, значением которого является дата. Применять конструкцию RANGE с другими типами данных нельзя. Если имеется таблица employees со столбцом hire_date типа даты и задана аналитическая функция COUNT (*) OVER (ORDER BY hire_date ASC RANGE 100 PRECEDING) она найдет все предыдущие строки фрагмента, значение которых в столбце hire_date лежит в пределах 100 дней от значения hire_date текущей строки. В этом случае, поскольку данные сортируются по возрастанию (ASC), значения в окне будут включать все строки текущей группы, у которых значение в столбце hire_date меньше значения hire_date текущей строки, но не более чем на 100 дней. Если использовать функцию COUNT (*) OVER (ORDER BY hire_date DESC RANGE 100 PRECEDING) и сортировать фрагмент по убыванию (DESC), базовая логика работы останется той же, но, поскольку группа отсортирована иначе, в окно попадет другой набор строк. В рассматриваемом случае функция найдет все строки, предшествующие текущей, где значение в поле hire_date больше значения hire_date в текущей строке, но не более чем на 100 дней. Пример поможет это прояснить. Будет использоваться запрос с аналитической функцией FIRST_VALUE. Эта функция возвращает вычисленное значение для первой строки окна. Так можно понять, где начинается окно: SELECT last_name, salary, hire_date, hire_date-100 windowtop, FIRST_VALUE (last_name) OVER (ORDER BY hire_date ASC RANGE 100 PRECEDING) name_prec, FIRST_VALUE (hire_date) OVER (ORDER BY hire_date ASC RANGE 100 PRECEDING) hire_date_prec FROM employees ORDER BY hire_date ASC Мы упорядочили один фрагмент по критерию hire_date ASC. При этом использовалась аналитическая функция FIRST_VALUE для поиска первого значения last_name и первого значения hire_date в соответствующем окне. Аналитическая функция затем вычисляется для всех строк отсортированного фрагмента, предшествующих найденной строке и имеющих значение в столбце hire_date в диапазоне 100 дней. Первое значение last_name и будет выдано аналитической функцией в столбце name_prec. Упорядочив данные по критерию hire_date DESC, мы получим: SELECT last_name, salary, hire_date, hire_date+100 windowtop, FIRST_VALUE (last_name) OVER (ORDER BY hire_date DESC RANGE 100 PRECEDING) name_prec, FIRST_VALUE (hire_date) OVER (ORDER BY hire_date DESC RANGE 100 PRECEDING) hire_date_prec FROM employees ORDER BY hire_date DESC 89 Теперь для той же строки будет выбрано другое окно, поскольку данные фрагмента отсортированы по-иному. Иногда достаточно сложно понять, какие значения будут входить в диапазон. Использование функции FIRST_VALUE удобным методом, помогающим увидеть диапазоны окна и проверить, корректно ли установлены параметры. Теперь, представив диапазоны окон, мы используем их для вычисления чего-то более существенного. Пусть необходимо выбрать зарплату каждого сотрудника и среднюю зарплату всех принятых на работу в течение 100 предыдущих дней, а также среднюю зарплату всех принятых на работу в течение 100 следующих дней. Соответствующий запрос будет выглядеть так: SELECT last_name, salary, hire_date, AVG (salary) OVER (ORDER BY hire_date ASC RANGE 100 PRECEDING) avg_sal_100_days_before, AVG (salary) OVER (ORDER BY hire_date DESC RANGE 100 PRECEDING) avg_sal_100_days_after FROM employees ORDER BY hire_date Окна диапазона можно задавать только по данным типа NUMBER или DATE, поскольку нельзя добавить или вычесть N единиц из значения типа VARCHAR2. Еще одно ограничение для таких окон состоит в том, что в конструкции ORDER BY может быть только один столбец - диапазоны по природе своей одномерны. Нельзя задать диапазон в Nмерном пространстве. Окна строк Окна срок задаются в физических единицах, строках. Перепишем вступительный пример из предыдущего раздела, задав окно строк: COUNTt (*) OVER (ORDER BY x ROWS 5 PRECEDING) Это окно будет включать до 6 строк: текущую и пять предыдущих (порядок определяется конструкцией ORDER BY). Для окон по строкам нет ограничений, присущих окнам по диапазону; данные могут быть любого типа и упорядочивать можно по любому количеству столбцов. Вот пример, сходный с рассмотренном ранее: SELECT last_name, salary, hire_date, FIRST_VALUE (last_name) OVER (ORDER BY hire_date ASC ROWS 5 PRECEDING) name_prec, FIRST_VALUE (hire_date) OVER (ORDER BY hire_date ASC ROWS 5 PRECEDING) hire_date_prec FROM employees ORDER BY hire_date ASC Аналитическая функция вычисляется для всех строк отсортированного фрагмента, предшествующих найденной строке на 5 строк. При сортировке группы по возрастанию окна изменяются. Теперь можно вычислить среднюю зарплату для указанного сотрудника и пяти принятых на работу до него и после него: SELECT last_name, salary, hire_date, AVG (salary) OVER (ORDER BY hire_date ASC ROWS 5 PRECEDING) avg_sal_5_before, COUNT (*) OVER (ORDER BY hire_date ASC ROWS 5 PRECEDING) count_before, AVG (salary) OVER (ORDER BY hire_date DESC ROWS 5 PRECEDING) avg_sal_5_after, COUNT (*) OVER (ORDER BY hire_date DESC ROWS 5 PRECEDING) count_after FROM employees ORDER BY hire_date 90 Функция COUNT(*) позволяет понять, по какому количеству строк было вычислено среднее значение. Задание окон Теперь, понимая различие между окнами диапазонов и окнами строк, можно изучать способы задания окон. В простейшем случае, окно задается с помощью одной из трех следующих взаимоисключающих конструкций. • UNBOUNDED PRECEDING. Окно начинается с первой строки текущей группы и заканчивается текущей обрабатываемой строкой. • CURRENT ROW. Окно начинается (и заканчивается) текущей строкой. • Числовое_выражение PRECEDING. Окно начинается со строки за числовое_выражение строк до текущей, если оно задается по строкам, или со строки, меньшей по значению столбца, упомянутого в конструкции ORDER BY, не более чем на числовое выражение, если оно задается по диапазону. Окно CURRENT ROW в простейшем виде, вероятно, никогда не используется, поскольку ограничивает применение аналитической функции одной текущей строкой, а для этого аналитические функции не нужны. В более сложном случае для окна задается также конструкция BETWEEN. В ней CURRENT ROW можно указывать в качестве начальной или конечной строки окна. Начальную и конечную строку окна в конструкции BETWEEN можно задавать с использованием любой из перечисленных выше конструкций и еще одной, дополнительной: • Числовое_выражение FOLLOWING. Окно заканчивается (или начинается) со строки, через числовое_выражение строк после текущей, если оно задается по строкам, или со строки, большей по значению столбца, упомянутого в конструкции ORDER BY, не более чем на числовое_выражение, если оно задается по диапазону. Примеры такого задания окон: SELECT department_id, last_name, hire_date, COUNT (*) OVER (PARTITION BY department_id ORDER BY hire_date NULLS FIRST RANGE 100 PRECEDING) cnt_range, COUNT (*) OVER (PARTITION BY department_id ORDER BY hire_date NULLS FIRST ROWS 2 PRECEDING) cnt_rows FROM employees WHERE department_id IN (10, 20) ORDER BY department_id, hire_date Окно RANGE 100 PRECEDING содержит только строки текущего фрагмента, предшествующие текущей строке, и те, значение которых hire_date находится в диапазоне hire_date -100 и hire_date относительно текущей. Окно ROWS 2 PRECEDING определяет, как далеко текущая строка находится от начала группы. Для первой строки группы имеем значение 1 (предыдущих строк нет). Для следующей строки в группе таких строк 2. Наконец, для третьей и далее строк значение COUNT(*) остается постоянным, поскольку мы считаем только текущую строку и две предыдущие. Пример использование конструкции BETWEEN. Все заданные до сих пор окна заканчивались текущей строкой и возвращались по результирующему множеству в поисках 91 дополнительной информации. Можно задать окно так, что обрабатываемая строка не будет последней, а окажется где-то в середине окна: SELECT last_name, hire_date, FIRST_VALUE (last_name) OVER (ORDER BY hire_date ASC RANGE BETWEEN 100 PRECEDING AND 100 FOLLOWING), LAST_VALUE (last_name) OVER (ORDER BY hire_date ASC RANGE BETWEEN 100 PRECEDING AND 100 FOLLOWING) FROM employees ORDER BY hire_date ASC Теперь в окно входят строки для тех, кто принят на работу за 100 дней до и (а не или, как прежде) после текущего сотрудника. Итак, синтаксис четырех компонентов вызова аналитической функции: • имя функции; • конструкция фрагментации, используемая для множества на независимые группы; разбиения результирующего • конструкция ORDER BY, сортирующая данные в группе для оконных функций; • конструкция окна, задающая набор строк, к которым применяется аналитическая функция. ИМЯ_ФУНКЦИИ (<аргумент>, <аргумент>, . . . ) OVER (конструкция фрагментации> <конструкция упорядочений <конструкция окна>) Создание иерархических запросов При разработке приложений довольно часто приходится иметь дело с иерархическим представлением информации. Допустим, вы решаете задачу, в которой требуется учитывать отношения подчиненности между сотрудниками фирмы или подразделениями. Если таблица содержит иерархические данные, вы можете получить их в соответствующем виде, используя оператор SELECT. Синтаксис иерархического запроса: SELECT [LEVEL], column, ... FROM table [WHERE condition(s)] START WITH column0 = value CONNECT BY PRIOR column1 = column2; Здесь START WITH определяет корневую запись (записи) иерархии. Допускается любое правильное условие. CONNECT BY PRIOR определяет отношение между подчиненными и родительскими записями в иерархии и задает направление выполнения запроса. Оператор PRIOR ссылается на строку родителя. Чтобы найти потомков родительской строки, сервер Oracle обрабатывает выражение PRIOR родительской строки и другие выражения каждой строки таблицы. Строки, для которых выражение верно, являются потомками родителя. Сервер Oracle всегда выбирает потомков, обрабатывая условие CONNECT BY на основе текущей родительской строки. 92 Если в выражении CONNECT BY PRIOR column1 = column2 column1 = ключ родителя (предка) column2 = ключ потомка (подчиненного), то направление обхода дерева (иерархии) сверху вниз. Если column1 = ключ потомка (подчиненного) column2 = ключ родителя (предка), то направление обхода дерева (иерархии) внизу вверх. Оператор PRIOR может располагаться в операторе и так CONNECT BY column1 = PRIOR column2; Результат запроса будет тот же. Условие WHERE накладывает дополнительные ограничения на результирующий набор строк. Для каждой результирующей строки иерархического запроса существует псевдостолбец, называемый LEVEL, который содержит значение уровня иерархии. Для корневой записи его значение равно 1. Рассмотрим пример с использованием иерархических запросов. SELECT LPAD(' ',2*(LEVEL-1)) || last_name org_chart, FROM employees START WITH job_id='AD_PRES' CONNECT BY PRIOR employee_id = manager_id; employee_id, manager_id, job_id Запрос возвращает имена всех служащих согласно служебной иерархии. Корневая запись содержит имя директора организации. В поле manager_id содержится значение поля employee_id - скажем, начальника какого-нибудь отдела. Для визуализации подчиненности использовался псевдостолбец LEVEL и функция LPAD. Только в иерархических запросах доступны следующие псевдоколонки: CONNECT_BY_ISCYCLE CONNECT_BY_ISLEAF Дополнительно к предложению CONNECT BY они улучшают запросы иерархических данных возвратом всех пар предок-потомок: CONNECT_BY_ISCYCLE - псевдоколонка возвращающая 1, если текущая строка имеет подчиненную запись, которая также является предшественником. В противном случае возвращает 0. CONNECT_BY_ISLEAF - псевдоколонка возвращающая 1, если текущая строка представляет собой лист дерева определяемого условием CONNECT BY. В противном случае возвращает 0. CONNECT_BY_ROOT - унарный оператор, который верен только в иерархических запросах. Когда вы указываете колонку с этим оператором, то Oracle возвращает значение колонки, используя данные их корневой строки. SYS_CONNECT_BY_PATH - возвращает путь значений от корня до узла, со значениями колонок разделенными символом для каждой строки, возвращаемой условием CONNECT BY. 93 LEVEL - для каждой строки возвращенной иерархическим запросом, эта колонка возвращает 1 для корня, 2 для дочерней записи от корня, и т.д. Добавление параметра NOCYCLE в условие CONNECT BY, можно заставить Oracle вернуть строки, игнорируя повтор. Псевдоколонка CONNECT_BY_ISCYCLE покажет, как строки проходят в цикле: SELECT last_name, CONNECT_BY_ISCYCLE, LEVEL, SYS_CONNECT_BY_PATH (last_name,’/’) path FROM employees START WITH last_name='King' CONNECT BY NOCYCLE PRIOR employee_id = manager_id; AND LEVEL <= 4; Если убрать условие NOCYCLE и выполнить запрос, то получим сообщение об ошибке. Использование объектно-реляционных средств В базах данных сервера Oracle могут использоваться объектно-реляционные средства. Выходя за пределы стандартных скалярных типов NUMBER, DATE и строк символов, объектно-реляционные средства Oracle позволяют расширить набор поддерживаемых типов данных. Можно создавать собственные типы данных, включающие: • атрибуты, каждый из которых может быть скалярной величиной или набором (массивом) других объектных/скалярных типов; • методы для работы с данными этого типа; • статические методы; • необязательный метод сравнения, используемый для сортировки и сравнения данных. Затем этот новый тип можно использовать для создания таблиц, столбцов таблиц, представлений или для расширения возможностей языков SQL и PL/SQL. Вновь созданный пользовательский тип данных можно использовать точно так же, как и базовый тип данных DATE. Объектно-реляционные средства сервера Oracle используются преимущественно для естественного расширения возможностей языка PL/SQL. Объектный тип - прекрасный способ добавить в PL/SQL новые функциональные возможности аналогично тому, как классы позволяют сделать это в С++ или Java. Объектные типы можно также использовать для стандартизации. Можно создать новый тип, скажем, ADDRESS_TYPE, который инкапсулирует определение адреса или отдельных компонентов, из которых состоит адрес. Можно даже добавить служебные функции (методы) для этого типа, которые, например, возвращают адрес в формате, подходящем для распечатки на почтовых наклейках. Теперь при создании таблицы, в которой должны содержаться данные об адресе, можно просто указать столбец типа ADDRESS_TYPE. Атрибуты адреса при этом будут добавлены в таблицу автоматически. Создадим простой тип данных ADDRESS_TYPE: CREATE OR REPLACE TYPE Address_Type AS OBJECT (street_addrl VARCHAR2(25), 94 street_addr2 VARCHAR2(25), city VARCHAR2(30), state VARCHAR2(2), zip_code NUNBER ) Этот тип можно сразу использовать в таблицах и в PL/SQL-коде: CREATE TABLE people (name VARCHAR2(10), home_address address_type, work_address address_type ) DECLARE l_home_address address_type; l_work_address address_type; BEGIN l_home_address := Address_Type('123 Main S t r e e t ' , null, 'Reston', 'VA', 45678); l_work_address :=Address_Type('l OracleWay', null, 'Redwood', 'CA', 23456); INSERT INTO people (name, home_address, work_address) VALUES ('TomKyte', l_home_address, l_work_address); END; SELECT * FROM people; SELECT name, P.home_address.state, P.work_address.state FROM people P; Если использовать опцию SET DESCRIBE утилиты SQL*Plus, можно заставить эту утилиту показывать всю структуру объектного типа: SET DESCRIBE DEPTH ALL DESC people Добавим функцию выдачи адреса в удобном формате в виде одного поля. Для этого можно добавить в тело типа соответствующую функцию-член: ALTER TYPE Address_Type REPLACE AS OBJECT (street_addrl VARCHAR2(25), street_addr2 VARCHAR2(25), city VARCHAR2(30), state VARCHAR2(2), zip_code NUMBER, MEMBER FUNCTION toString RETURN VARCHAR2 ) CREATE OR REPLACE TYPE BODY Address_Type AS MEMBER FUNCTION toString RETURN VARCHAR2 IS BEGIN IF (street_addr2 IS NOT NULL) 95 THEN RETURN street_addrl || chr (10) || street_addr2 || chr(10) || city || ', ' || state || ' ' || zip_code; ELSE RETURN street_addrl || chr (10) || city || ', ' || state || ' ' || zip_code; END IF; END; END; SELECT name, p.home_address.toString () FROM people P Каждый метод вызывается с неявным параметром SELF. Можно добавить этот префикс к атрибутам STREET_ADDR1,STREET_ADDR2 и т.д.: SELF.street_addrl || chr (10) || SELF.street_addr2 ... но он и так добавляется неявно. Тут вы вполне резонно можете заметить: "Ведь все это можно сделать с помощью реляционной таблицы и PL/SQL-пакета". Это действительно так. Однако использование объектного типа с методами, как показано выше, дает определенные преимущества. • Обеспечивается более совершенный механизм инкапсуляции. Тип ADDRESS_TYPE инкапсулирует и поддерживает адрес, со всеми его атрибутами и функциональными возможностями. • Методы более тесно привязываются к специфическим данным. Это очень важный момент. Если используются скалярные столбцы и PL/SQL-функция, форматирующая их для вывода адреса на печать, эту функцию можно вызвать с любыми данными. Можно передать значение столбца EMPLOYEE_NUMBER в качестве почтового индекса, фамилию - вместо названия улицы и т.д. Привязывая метод к атрибутам, мы гарантируем, что метод TOSTRING может работать только с данными адреса. Пользователи, вызывающие этот метод, не должны задумываться о передаче соответствующих данных - они "уже здесь". С объектными типами также связаны специфические методы MAP и ORDER. Они используются при сортировке, сравнении или группировке экземпляров объектных типов. Если у объектного типа нет функции MAP или ORDER, при попытке выполнения этих операций вы получите следующее сообщение об ошибке: SELECT * FROM people ORDER BY home_address; ORA-22950: cannot ORDER objects without MAP or ORDER method Метод MAP - это функция, работающая с одним экземпляром объекта и возвращающая значение одного из скалярных типов, которое сервер Oracle будет использовать для сравнения с другими однотипными объектами. Например, если объектный тип представляет точку на плоскости с координатами X и Y, функция MAP может возвращать квадратный корень из (X*X+Y*Y) - расстояние от начала координат. Метод ORDER принимает два экземпляра объекта - SELF и объект для сравнения с SELF. Метод ORDER возвращает 1, если SELF больше этого объекта, -1, если SELF меньше другого объекта или 0, если объекты равны. Метод MAP предпочтительнее, поскольку работает намного быстрее и даже может вызываться в параллельных запросах (метод ORDER нельзя использовать при распараллеливании). Метод MAP достаточно вызвать для экземпляра объекта один раз, и после этого сервер Oracle может использовать это значение при сортировке. Метод ORDER при сортировке большого множества, возможно, придется вызывать сотни или тысячи раз с одними и теми же данными. Продемонстрируем оба метода. Сначала - метод ORDER: ALTER TYPE Address_Type 96 REPLACE AS OBJECT (street_addrl VARCHAR2(25), street_addr2 VARCHAR2(25), city VARCHAR2(30), state VARCHAR2(2), zip_code NUMBER, MEMBER FUNCTION toString RETURN VARCHAR2, ORDER MEMBER FUNCTION order_function (compare2 in Address_type) RETUTN NUMBER ) CREATE OR REPLACE TYPE BODY Address_Type AS MEMBER FUNCTION toString RETURN VARCHAR2 IS BEGIN IF (street_addr2 IS NOT NULL) THEN RETURN street_addrl || chr (10) || street_addr2 || chr(10) || city || ', ' || state || ' ' || zip_code; ELSE RETURN street_addrl || chr (10) || city || ', ' || state || ' ' || zip_code; END IF; END; END; ORDER MEMBER FUNCTION order_function (compare2 in Address_type) RETURN NUMBER IS BEGIN IF (nvl (self.zip_code,-99999) <> nvl (compare2.zip_code,-99999)) THEN RETURN sign (nvl (self.zip_code,-99999) - nvl (compare2.zip_code,-99999)); END IF; IF (nvl (self.city, chr (0)) > nvl (compare2.city, chr (0))) THEN RETURN 1; ELSIF (nvl (self.city, chr (0)) <nvl (compare2.city, chr (0))) THEN RETURN -1; END IF; IF (nvl (self.street_addrl, chr (0)) > nvl (compare2.street_addr1, chr (0))) THEN RETURN 1; ELSIF (nvl (self.street_addrl, chr (0)) < nvl (compare2.street_addr1, chr (0))) THEN RETURN -1; 97 END IF; IF (nvl (self.street addr2, chr (0)) > nvl (compare2. street_addr2, chr (0))) THEN RETURN 1; ELSIF (nvl (self.street_addr2, chr (0)) < nvl (compare2.street_addr2,chr (0))) THEN RETURN -1; END IF; RETURN 0; END; END; Этот метод сравнивает два адреса по следующему алгоритму. 1. Если значение почтового индекса (ZIP_CODE) у объекта SELF меньше, чем у объекта COMPARE2, вернуть -1, а если больше - 1. 2. Если значение города (CITY) у объекта SELF меньше, чем у объекта COMPARE2, вернуть -1, а если больше - 1. 3. Если значение первого компонента адреса (STREET_ADDR1) у объекта SELF меньше, чем у объекта COMPARE2, вернуть -1, а если больше - 1. 4. Если значение второго компонента адреса (STREET_ADDR2) у объекта SELF меньше, чем у объекта COMPARE2, вернуть -1, а если больше - 1. 5. Иначе вернуть 0 (адреса совпадают). При сравнении приходится постоянно проверять, не переданы ли значения NULL, и т.п. В результате метод получился достаточно большим и сложным. Он, несомненно, неэффективен. Представленное выше сравнение лучше переписать в виде метода MAP. Методы нельзя удалять - их можно только добавлять с помощью оператора ALTER TYPE, а нам надо избавиться от существующего метода ORDER. Полный пример должен был бы включать операторы DROP TABLE PEOPLE, DROP TYPE ADDRESS_TYPE и CREATE TYPE и лишь затем - следующий оператор ALTER TYPE: ALTER TYPE Address_Type REPLACE AS OBJECT (street_addrl VARCHAR2(25), street_addr2 VARCHAR2(25), city VARCHAR2(30), state VARCHAR2(2), zip_code NUMBER, MEMBER FUNCTION toString RETURN VARCHAR2, MAP member function mapping_function RETURN VATCHAR2 ) CREATE OR REPLACE TYPE BODY Address_Type AS MEMBER FUNCTION toString RETURN VARCHAR2 IS BEGIN IF (street_addr2 IS NOT NULL) 98 THEN RETURN street_addrl || chr (10) || street_addr2 || chr (10) || city || ', ' || state || ' ' || zip_code; ELSE RETURN street_addrl || chr (10) || city || ', ' || state || ' ' || zip_code; END IF; END; END; MAP MEMBER FUNCTION mapping_function RETURN VATCHAR2 IS BEGIN RETURN to_char (nvl (zip_code,0), 'fm00000') || lpad (nvl (city,' ' ) , 30) || lpad (nvl (street_addrl,' '), 25) || lpad (nvl (street_addr2,' '), 25); END; END; Возвращая строку фиксированной длины, содержащую значение ZIP_CODE, затем CITY и поля STREET_ADDR, можно переложить задачу сравнений и сортировки на сервер Oracle. Еще один тип наборов - вложенные таблицы. Вложенная таблица, один из двух типов наборов в Oracle, очень похожа на подчиненную таблицу в традиционной для реляционной модели паре таблиц главная/подчиненная. Это неупорядоченный набор элементов данных одного типа, встроенного или объектного. Но при использовании вложенных таблиц создается впечатление, что каждая строка в главной таблице имеет отдельную подчиненную таблицу. Если в главной таблице - 100 строк, то имеется 100 виртуальных вложенных таблиц. Физически же имеется только одна главная и одна подчиненная таблица. Кроме того, между вложенными и главными/подчиненными таблицами есть много синтаксических и семантических различий. Существует небольшая реляционная модель данных, которая реализуется следующим образом с помощью первичного и внешнего ключей: CREATE TABLE dept (deptno NUMBER(2) PRIMARY KEY, dname VARCHAR2(14), loc VARCHAR2(13) ); CREATE TABLE emp (empno NUMBER(4) PRIMARY KEY, ename VARCHAR2(10), job VARCHAR2(9), mgr NUMBER(4) REFERENCES emp (empno), hiredate DATE, sal NUMBER(7, 2), соmm NUMBER(7, 2), deptno NUMBER(2) REFERENCES dept (deptno) ); Реализуем эту же модель с помощью вложенной таблицы, содержащей данные о сотрудниках: CREATE OR REPLACE TYPE emp_type 99 AS OBJECT (empno NUMBER(4) , ename VARCHAR2(10), job VARCHAR2(9), mgr NUMBER(4) , hiredate DATE, sal NUMBER(7, 2), соmm NUMBER(7, 2), deptno NUMBER(2) ); CREATE OR REPLACE TYPE emp_tab_type AS TABLE OF emp_type; Для создания таблицы с вложенной таблицей необходим тип данных для вложенной таблицы. Представленный выше код создает сложный объектный тип EMP_TYPE и тип вложенной таблицы, который называется EMP_TAB_TYPE. В языке PL/SQL с ним можно было бы работать как с массивом. В языке SQL будет создана физическая вложенная таблица. Вот простой оператор CREATE TABLE, использующий этот тип: CREATE TABLE dept_and_emp (deptno NUMBER(2) PRIMARY KEY, dname VARCHAR2(14), loc VARCHAR2(13), amps emp_tab_type ) NESTED TABLE emps STORE AS emps_nt; ALTER TABLE emps_nt ADD CONSTRAINT emps_empno_unique UNIQUE(empno) Важная часть этого оператора создания таблицы - включение столбца EMPS типа EMP_TAB_TYPE и соответствующая конструкция NESTED TABLE EMPS STORE AS EMPS_NT. При этом, помимо таблицы DEPT_AND_EMP, отдельно создается реальная физическая таблица EMPS_NT. Ограничение по столбцу EMPNO непосредственно для вложенной таблицы, чтобы обеспечить уникальность значения EMPNО,как это было в исходной реляционной модели. Вложенные таблицы не поддерживают требования целостности ссылок, поскольку не могут ссылаться на другие таблицы, даже на самих себя. Заполним таблицу данными из существующих таблиц ЕМР и DEPT: INSERT INTO dept_and_emp SELECT dept.*, CAST (MULTISET (SELECT empno, ename, job, mgr, hiredate, sal, comm FROM scott.emp WHERE emp.deptno = dept.deptno) AS emp_tab_type) FROM scott.dept; Ключевое слово MULTISET используется, чтобы сообщить серверу Oracle, что подзапрос может вернуть несколько строк (подзапросы в списке выбора оператора SELECT ранее могли возвращать только одну строку). Оператор CAST используется, чтобы преобразовать возвращаемое множество в тип набора; в данном случае с помощью CAST мы преобразуем многоэлементное множество (MULTISET) в данные типа EMP_TAB_TYPE. Оператор CAST позволяет выполнять преобразование типов в общем случае, не только для 100 наборов. Например, если необходимо извлечь столбец EMPNO из таблицы ЕМР как данные типа VARCHAR2(20), а не NUMBER(4), можно выполнить запрос SELECT CAST (empno as VARCHAR2(20)) e FROM emp. Извлечение значений: SELECT d.deptno, d.dname, emp.* FROM dept_and_emp D, TABLE (d.emps) emp; Изменение данных: UPDATE TABLE (SELECT emps FROM dept_and_emp WHERE deptno = 10 ) SET comm = 100; В условии оператора SELECT есть таблица значений. Оператор UPDATE показывает, что для каждой строки есть таблица. Мы выбрали отдельную таблицу для изменения; у этой таблицы нет имени - только идентифицирующий ее запрос. Если бы использовался запрос SELECT, возвращающий более одной виртуальной таблицы или не возвращающий ни одной, были бы выданы сообщения об ошибках. Примеры базового синтаксиса операторов для запросов и изменения вложенных таблиц: INSERT INTO TABLE (SELECT emps FROM dept_and_emp WHERE deptno = 10) VALUES (1234, 'NewEmp', 'CLERK', 7782, SYSDATE, 1200, NULL); DELETE FROM TABLE (SELECT emps FROM dept_and_emp WHERE deptno = 20) WHERE ename = 'SCOTT'; SELECT d.dname, e.empno, ename FROM dept_and_emp d, TABLE (d.emps) e WHERE d.deptno IN (10, 20); Следующий тип наборов - VARRAY. Массив VARRAY используется для хранения массива данных, связанных с одной строкой. Массив VARRAY во многом похож на вложенную таблицу, но реализуется абсолютно иначе. Например, если необходимо хранить в таблице PEOPLE дополнительные таблицы (скажем, массив прежних адресов проживания, начиная с самого старого), можно сделать следующее: CREATE OR REPLACE TYPE Address_Array_Type as VARRAY (25) of Address_Type ALTER TABLE people ADD previous_addresses Address_Array_Type SET DESCRIBE DEPTH ALL DESC PEOPLE Массивы VARRAY либо хранятся в строке как столбец типа RAW, либо (при достаточно большом объеме) как большой объект. Дополнительных ресурсов для поддержки данных типа VARRAY (по сравнению с вложенной таблицей) надо очень мало, что делает массив VARRAY привлекательным методом хранения повторяющихся данных. Поиск по массиву VARRAY можно реализовать, преобразовав его данные в таблицу, что сделает его не менее гибким, чем вложенные таблицы: UPDATE people SET previous_addresses = Address_Array_Type ( Address_Type ('312 Johnston Dr', null,'Bethlehem', 'PA', 18017), Address_Type ('513 Zulema S t ' , 'Apartment #3", 'Pittsburg', 'PA', 18123), Address_Type ('840 South Frederick S t ' , null, 'Alexandria', 'YA', 20654)); SELECT name, prev.city, prev.state, prev.zip_code FROM people p, TABLE (p.previous_addresses) prev WHERE prev.state = 'PA'; 101 Существенное различие состоит в том, что при реализации с помощью вложенной таблицы можно создать индекс по столбцу STATE вложенной таблицы, и оптимизатор этот индекс использовал бы. В данном случае столбец STATE проиндексировать нельзя. Итак, основные отличия вложенных таблиц от массивов переменной длины (VARRAY) представлены в следующей таблице. Вложенная таблица Элементы "массива" не упорядочены. Данные из набора могут возвращаться совсем не в том порядке, в каком они туда вставлялись. Вложенные таблицы физически хранятся в виде пары родительской и дочерней таблиц с суррогатными ключами. Вложенные таблицы не имеют ограничения на количество хранящихся элементов. Вложенные таблицы можно изменять (добавлять/изменять/удалять в них данные) с помощью языка SQL. Для получения данных строки при использовании вложенных таблиц необходимо выполнять реляционное соединение. В случае небольших наборов это повлечет чрезмерное использование ресурсов. VARRAY VARRAY - настоящие массивы. Данные после вставки остаются упорядоченными. В рассмотренном ранее примере данные добавлялись в конец массива. Это означает, что самый старый адрес идет в массиве первым, а последний по времени адрес находится в конце массива. При использовании внешней таблицы для упорядочения адресов по давности потребуется дополнительный атрибут. Массивы VARRAY хранятся как столбец типа RAW или как большой объект. При этом для обеспечения их работы требуются минимальные дополнительные ресурсы. Для массивов VARRAY при создании типа задается ограничение на количество хранящихся элементов. Массивы VARRAY необходимо изменять процедурно. Нельзя выполнять операторы вида: INSERT INTO TABLE (SELECT P.PREVIOUS ADDRESSES FROM PEOPLE P) VALUES ... как для вложенной таблицы. Для добавления адреса придется использовать процедурный код. Для получения данных массива VARRAY соединение выполнять не нужно. В случае небольших наборов данные хранятся в самой строке; если же наборы большие - в сегменте большого объекта. При обращении к элементам массива VARRAY требуется меньше ресурсов, чем при обращении к вложенной таблице. При изменении же данных массива VARRAY ресурсов требуется больше, чем при изменении вложенной таблицы, поскольку заменять приходится весь массив, а не один элемент. Применение основных стандартных пакетов Причина использования стандартных пакетов проста: гораздо проще разрабатывать и сопровождать код, использующий стандартные средства, чем создавать их самому. Если корпорация Oracle поставляет пакет для определенных целей (например, шифрования 102 данных), не имеет смысла писать такой пакет самому. Часто разработчики по незнанию создают средства, уже существующие в базе данных. Знание того, какие готовые инструментальные средства имеются, существенно упрощает разработку. Рассмотрим основные стандартные пакеты. DBMS_APPLICATION_INFO Пакет DBMS_APPLICATION_INFO позволяет устанавливать значения трех столбцовCLIENT_INFO, ACTION и MODULE - соответствующей сеансу строки в представлении V$SESSION. Пакет предоставляет функции не только для установки этих значений, но и для их получения. Значения, устанавливаемые в представлениях V$, сразу же доступны в других сеансах. Фиксировать их не нужно, что позволяет эффективно использовать эти представления для взаимодействия с "внешним миром". Пакет DBMS_APPLICATION_INFO позволяет задать значения в представлении динамической производительности V$SESSION_LONGOPS (LONG OPerationS). Это удобно для записи сделанного в продолжительных заданиях. Многие инструментальные средства Oracle, например SQL*Plus, уже используют возможности этого пакета. Для установки соответствующих значений в представлении V$SESSION используются следующие вызовы. • SET_MODULE. Эта процедура позволяет установить в представлении V$SESSION значения столбца MODULE. В качестве имени модуля обычно указывается имя приложения. • SET_ACTION. Эта процедура позволяет установить в представлении V$SESSION значение столбца ACTION (действие). Это значение должно быть таким, чтобы можно было понять, какая часть кода программы выполняется. В качестве действия можно указывать, например, имя текущей активной экранной формы, имя функции в программе на Pro*C или имя PL/SQL-подпрограммы. • SET_CLIENT_INFO. Эта процедура позволяет сохранить до 64 байт специфической информации о приложении, которая может понадобиться. Обычно так сохраняются параметры представления или запроса. Процедура SET_CLIENT_INFO позволяет установить не только значения в столбцах представления V$SESSION, но и значение переменной CLIENT_INFO, которое можно получить с помощью встроенных функций userenv или sys_context. Например, можно создать параметризованное представление, результаты выборки данных из которого зависят от значения переменной CLIENT_INFO. Эту идею можно проиллюстрировать следующим примером: BEGIN DBMS_APPLICATION_INFO.SET_CLIENT_INFO ('KING'); END; SELECT userenv ('CLIENT_INFO') FROM dual; SELECT sys_context ('userenv','client_info') FROM dual; Многие действия в базе данных могут выполняться достаточно долго. При выполнении этих продолжительных действий информация о ходе работы записывается в представление динамической производительности V$SESSION_LONGOPS, что позволяет оценить объем сделанного. Это представление можно использовать и в приложениях. В представлении отражается состояние действий, выполняющихся в базе данных более шести секунд. Другими словами, в функции сервера Oracle, которые, по предположению разработчиков, обычно выполняются дольше шести секунд, включены вызовы процедуры, вставляющей данные в представление V$SESSION_LONGOPS. Это не означает, что при 103 выполнении любого действия продолжительностью более шести секунд в это представление автоматически будет что-то записываться. К таким действиям сейчас относятся многие функции резервного копирования и восстановления, сбора статистической информации и выполнение запросов. В каждой новой версии Oracle появляются новые действия. Изменения в этом представлении сразу же доступны для других сеансов, т.е. транзакцию фиксировать не нужно. Можно контролировать ход процесса, изменяющего это представление, из другого сеанса с помощью запросов к представлению V$SESSION_LONGOPS. Разработчики могут добавлять строки в это представление. Обычно приложение вставляет и изменяет одну строку, но при необходимости можно вставлять и несколько. Процедура для установки значений в этом представлении имеет следующие параметры: • RINDEX. Указывает серверу, какую строку в представлении V$SESSION_LONGOPS необходимо изменить. Если в качестве значения этого параметра указать DBMS_APPICATION_INFO.SET_SESSION_LONGOPS_NOHINT, в это представление будет автоматически вставлена новая строка, индекс которой будет записан в RINDEX. При указании в последующих вызовах процедуры SET_SESSION_LONGOPS этого индекса в качестве значения параметра RINDEX будет изменяться добавленная строка. • SLNO. Служебное значение. Первоначально надо передать значение NULL, а полученное в результате выполнения значение - игнорировать. При каждом вызове надо передавать одно и то же значение. • OP_NAME. Имя продолжительно выполняющегося процесса. Его длина не должна превышать 64 байт, а в качестве значения необходимо задавать строку, по которой легко определить, что именно выполняется. • TARGET. Обычно используется для передачи идентификатора объекта, с которым выполняется продолжительное действие (например, идентификатор таблицы, в которую загружаются данные). Можно передать любое значение, в том числе NULL. • CONTEXT. Число, задаваемое пользователем. Это число должно быть информативным для пользователя. Передать можно любое число. • SOFAR. В качестве значения можно передавать любое число, но если это число будет представлять собой процент или другую количественную характеристику уже выполненной части действия, сервер сможет автоматически оценить, сколько времени осталось до завершения действия. Например, если необходимо обработать 25 объектов и на обработку каждого из них уходит примерно одинаковое время, можно задать в качестве значения параметра SOFAR количество обработанных объектов, а общее их количество передать как следующий параметр, TOTALWORK. Сервер определит, сколько времени потребовалось для выполнения уже сделанной части, и оценит время, необходимое для завершения действия. • TOTALWORK. В качестве значения можно передавать любое число, но имеет смысл сопоставлять его со значением параметра SOFAR. Если SOFAR представляет собой процент от TOTALWORK, отражающий ход выполнения действия, сервер сможет вычислить, сколько времени осталось до завершения действия. • TARGET_DESC. Этот параметр описывает значение TARGET, представленное выше. Если в качестве параметра TARGET передан идентификатор объекта, параметр может содержать имя объекта с этим идентификатором. 104 • UNITS. Описательный параметр, задающий единицу измерения для значений параметров SOFAR и TOTALWORK. Это могут быть, например, файлы, итерации или вызовы. Перечисленные выше значения может устанавливать пользователь. Если обратиться к представлению V$SESSION_LONGOPS, то в нем можно обнаружить намного больше столбцов, чем описано выше: DESC v$session_longops Результат: SID NUMBER SERIAL# NUMBER OPNAME VARCHAR2(64) ** TARGET VARCHAR2(64) ** TARGET_DESC VARCHAR2(32) ** SOFAR NUMBER ** TOTALWORK NUMBER ** UNITS VARCHAR2(32) ** START_TIME DATE LAST_UPDATE_TIME DATE TIME_REMAINING NUMBER ELAPSED_SECONDS NUMBER CONTEXT NUMBER ** MESSAGE VARCHAR2(512) USERNAME VARCHAR2(30) SQL_ADDRESS RAW(4) SQL_HASH_VALUE NUMBER QCSID NUMBER Значения столбцов, помеченных двумя звездочками (**), может устанавливать пользователь. Остальные столбцы имеют следующие значения. • Столбцы SID и SERIAL# используются при соединении с представлением V$SESSION для получения информации о сеансе. • Столбец START_TIME содержит время создания записи (обычно это время первого вызова процедуры DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS, с помощью которого и была создана строка). • Столбец LAST_UPDATE_TIME представляет время последнего вызова процедуры SET_SESSION_LONGOPS. • Столбец TIME_REMAINING содержит предполагаемое время, оставшееся до завершения действия. Его значение вычисляется как: ROUND(ELAPSED_SECONDS*((TOTALWORK/SOFAR)-1)). • Столбец ELAPSED_SECONDS содержит количество секунд, прошедших с начала выполнения продолжительного действия до последнего изменения строки. 105 • Столбец MESSAGE - производный. Он представляет собой конкатенацию значений столбцов OPNAME, TARGET_DESC, TARGET, SOFAR, TOTALWORK и UNITS, описывающую выполняемое действие. • Значение в столбце USERNAME - имя пользователя, выполняющего действие. • Значения столбцов SQL_ADDRESS и SQL_HASH_VALUE можно использовать для поиска в представлении V$SQLAREA последнего SQL-оператора, выполненного процессом. • Значение столбца QCSID используется при распараллеливании запроса. Это идентификатор сеанса-координатора параллельного запроса. Итак, что же можно получить с помощью данного представления? Небольшой пример поможет прояснить это. Выполним в одном сеансе следующий блок кода: DECLARE l_nohint NUMBER DEFAULT dbms_application_info.set_session_longops_nohint; l_rindex NUMBER DEFAULT l_nohint; l_slno NUNBER; BEGIN FOR i IN 1 .. 25 LOOP dbms_lock.sleep(2); dbms_application_info.set_session_longops (rindex => l_rindex, slno => l_slno, op_name => 'my long running operation', target => 1234, target_desc => '1234 is my target', context => 0, sofar => i, totalwork => 25, units => 'loops' ); END LOOP; END; Этот блок кода представляет собой продолжительное действие, выполняющееся в течение 50 секунд (вызов DBMS_LOCK.SLEEP просто приостанавливает выполнение на две секунды). В другом сеансе можно следить за ходом данного сеанса с помощью представленного ниже запроса: BEGIN print_table('SELECT b.* FROM v$session a, v$session_longops b WHERE a.sid = b.sid AND a.serial# = b.serial#'); END; Может возникнуть вопрос: зачем представление V$SESSION_LONGOPS соединяется с представлением V$SESSION, если из V$SESSION не выбирается информация? 106 Дело в том, что представление V$SESSION_LONGOPS содержит строки, как для текущего, так и для прежних сеансов. По завершении сеанса это представление не очищается. Данные остаются, пока какой-нибудь другой сеанс не использует повторно соответствующий слот. Поэтому, чтобы получить информацию только для текущих сеансов, необходимо использовать соединение или подзапрос. Этот пример демонстрирует, что из представления V$SESSION_LONGOPS можно получить информацию, весьма ценную для пользователей и администраторов базы данных, которым необходимо контролировать ход выполнения продолжительных хранимых процедур, пакетных заданий, отчетов и т.п. Добавив лишь несколько вызовов, можно получить точную информацию в производственной среде. Вместо того чтобы гадать, что именно происходит в задании и сколько оно будет выполняться, можно получить точную информацию о том, что сделано, и обоснованную оценку времени, необходимого для завершения работы. Oracle Enterprise Manager (OEM) и многие инструментальные средства независимых производителей обращаются к этому представлению и автоматически отображают информацию, которую приложения в нем устанавливают. DBMS_JOB Пакет DBMS_JOB позволяет запланировать однократное или регулярное выполнение заданий в базе данных. Задание представляет собой хранимую процедуру, анонимный блок PL/SQL или внешнюю процедуру на языке С или Java. Эти задания выполняются серверными процессами в фоновом режиме. Задания можно выполнять регулярно (в 2 часа ночи каждые сутки) или однократно (выполнить задание сразу после фиксации транзакции и удалить его из очереди). Задания выполняются в той же среде (пользователь, набор символов и т.п.), из которой они посланы на выполнение. Задания выполняются аналогично процедурам с правами создателя. Рекомендуется вообще не использовать средства ОС для планирования заданий в базе данных, а вместо этого написать хранимую процедуру, выполняющую необходимые действия, и запланировать ее выполнение с помощью средств пакета DBMS_JOB. При этом ни имя пользователя, ни пароль нигде не хранится, и задание выполнится только в том случае, когда доступна база данных. Если сервер базы данных не будет работать в соответствующий момент, задание, естественно, не выполнится, поскольку именно сервер базы данных и отвечает за выполнение задания. Для корректной работы пакета DBMS_JOB необходимо выполнить небольшую настройку сервера. Надо установить два параметра инициализации. • job_queue_interval. Задает периодичность (в секундах) проверки очередей и поиска заданий, готовых к выполнению. Если задание должно выполняться раз в 30 секунд, но параметр job_queue_interval имеет (стандартное) значение 60, это задание не будет выполняться раз в 30 секунд - в лучшем случае, раз в 60 секунд. • job_queue_processes. Задает количество фоновых процессов для выполнения заданий. Значением может быть целое число от 0 (по умолчанию) до 36. Это значение можно менять без перезапуска сервера с помощью оператора ALTER SYSTEM SET job_queue_processes = <nn>. Если оставить стандартное значение 0, задания из очереди автоматически никогда выполняться не будут. 107 Многие системы работают со значением параметра job_queue_interval, равным 60 (другими словами, проверяют очереди раз в минуту), и значением параметра job_queue_processes, равным 1 (выполняют одновременно не более одного задания). При интенсивном использовании заданий или возможностей, для реализации которых используются задания (в частности, репликация и поддержка материализованных представлений используют очереди заданий), может потребоваться добавление дополнительных процессов и увеличение значения параметра инициализации job_queue_processes. После настройки и автоматического запуска очередей заданий можно начинать их использование. Основная процедура пакета DBMS_JOB - процедура SUBMIT. Назначение аргументов процедуры SUBMIT: • JOB. Идентификатор задания. Присваивается системой (этот параметр передается в режиме OUT). Его можно использовать для получения информации о задании из представлений USER_JOBS или DBA_JOBS по идентификатору задания. Кроме того, некоторые процедуры, в частности RUN и REMOVE, требуют единственного параметра - идентификатора задания для определения того, какое именно задание выполнять или удалять из очереди. • WHAT. Действие, которое необходимо выполнить. Можно передавать PL/SQL-оператор или блок кода. • NEXT_DATE. Время следующего (а поскольку мы только посылаем задание - первого) выполнения задания. Стандартное значение - SYSDATE - означает "выполнять немедленно" (после фиксации транзакции). • INTERVAL. Строка, содержащая функцию, вычисляющую время следующего выполнения задания. Можно считать, что значение этой функции выбирается с помощью оператора 'select ... from dual'. Если передать строку sysdate+l, сервер выполнит SELECT sysdate+l INTO :NEXT_DATE FROM DUAL. Ниже описаны особенности задания интервала выполнения задания, предотвращающие его смещение. • NO_PARSE. Указывает, анализировался ли параметр WHAT при отправке задания. Анализируя строку, можно определить, выполнимо ли вообще задание. В общем случае параметру NO_PARSE надо всегда оставлять стандартное значение, False. При установке значения True параметр WHAT принимается "как есть", без проверки допустимости. • INSTANCE. Этот параметр имеет значение только в режиме Parallel Server, когда сервер Oracle работает на кластере слабо связанных машин. Он задает экземпляр, на котором будет выполняться задание. По умолчанию он имеет значение ANY_INSTANCE. • FORCE. Этот параметр также имеет значение только в режиме Parallel Server. При установке значения True (принятого по умолчанию) можно посылать задание с любым идентификатором экземпляра, даже если при отправке соответствующий экземпляр недоступен. При установке значения False отправка задания завершится неудачно, если указанный экземпляр недоступен. В пакете DBMS_JOB есть и другие подпрограммы. Задание посылается на выполнение с помощью процедуры SUBMIT, a остальные подпрограммы позволяют манипулировать заданиями в очередях и выполнять действия по их запуску (RUN), удалению из очереди (REMOVE) и изменению (CHANGE). Ниже описаны наиболее часто используемые подпрограммы, включая входные данные и назначение. 108 REMOVE< номер задания> - удаляет задание из очереди. Учтите, что если задание выполняется, удаление не остановит его выполнение. Удаление из очереди гарантирует, что задание не будет выполнено снова, но уже выполняющееся задание при этом не останавливается. Для остановки выполняющегося задания необходимо прекращать работу соответствующего сеанса с помощью оператора ALTER SYSTEM. CHANGE <номер задания, WHAT, NEXT_DATE, INTERVAL, INSTANCE, FORCE > - эта процедура работает как оператор UPDATE для представления JOBS. Она позволяет изменить любой параметр задания. BROKEN <номер задания, BROKEN (булево задание), NEXT_DATE > - позволяет "разрушить" или "восстановить" задание. Разрушенное задание не выполняется. Задание, не выполнившееся успешно 16 раз подряд, автоматически помечается как разрушенное, и сервер Oracle больше не будет его выполнять. RUN <номер задания> - выполняет задание немедленно, в приоритетном режиме (в пользовательском сеансе). Полезно при отладке не срабатывающих заданий. Предположим, необходимо ежесуточно в 3 часа ночи выполнять анализ всех таблиц в определенной схеме. Для этого можно использовать следующую хранимую процедуру: CREATE OR REPLACE PROCEDURE analyze_my_tables AS BEGIN FOR x IN (SELECT table_name FROM user_tables) LOOP EXECUTE IMMEDIATE 'analyze table ' || x.table_name || ' compute statistics'; END LOOP; END; Чтобы запланировать ее выполнение сегодня ночью в 3 часа (точнее, завтра утром), а затем ежесуточно в 3 часа ночи, можно использовать следующий вызов: DECLARE l_job NUMBER; BEGIN DBMS_JOB.SUBMIT (JOB => l_job, WHAT => 'analyze_my_tables;', NEXT_DATE => TRUNC (SYSDATE) +l+3/24, INTERVAL => 'TRUNC (SYSDATE)+l+3/24'); END; SELECT job, to_char (sysdate,'dd-mon'), to_char (next_date,'dd-mon-yyyy hh24:mi:ss'), interval, what FROM user_jobs; Здесь использованы функции для работы с датами, так что при выполнении, независимо от времени вызова, всегда будет возвращаться 3 часа следующего утра. Это важно. Точно такую же функцию, но в виде строки мы передали в качестве значения параметра INTERVAL. Используется функция, всегда возвращающая 3 утра завтрашнего дня, независимо от времени ее выполнения. Это предотвращает смещение заданий (jobs sliding). Может показаться, что, поскольку первый раз задание выполняется в 3 часа утра, можно задать интервал sysdate+l. Если выполнить это вычисление в 3 утра во вторник, в результате 109 мы получим 3 утра среды. Получим, если задание гарантированно выполнится в указанное время, но это вовсе не обязательно. Задания в очереди обрабатываются последовательно, в соответствии с указанным временем выполнения. При наличии одного процесса обработки очереди сообщений и двух заданий, назначенных на 3 утра, очевидно, что одно из них не выполнится точно в 3 утра. Придется подождать, пока завершится выполнение первого задания. Даже при отсутствии заданий, назначенных на то же время, очереди заданий просматриваются периодически, например каждые 60 секунд. Задание, назначенное на выполнение в 3 утра, может быть выбрано из очереди в 3:00:45 утра. Если использовать функцию sysdate+l, в следующий раз задание может быть поставлено на выполнение в 3:00:46 утра. На следующий день в 3:00:45 утра задание еще не будет готово для выполнения и выполнится при следующем просмотре очереди, в 3:01:45 утра. Время выполнения задания медленно сдвигается. Однако это не самое худшее. Предположим, на следующий день в 3 утра с таблицами будут работать, и проанализировать их не удастся. Хранимая процедура не сработает и будет возвращена в очередь для выполнения. Теперь это задание окажется смещенным на несколько минут позже 3 утра. Поэтому, чтобы предотвратить смещение времени выполнения заданий, необходимо использовать функцию, возвращающую фиксированный момент времени, если выполнение в конкретный момент времени является существенным. Если важно, чтобы задание выполнялось именно в 3 утра, надо использовать функцию, всегда возвращающую время 3 утра, независимо от времени ее выполнения. Для контроля заданий в базе данных используется три основных представления. • USER_JOBS. Список всех заданий, посланных текущим зарегистрированным пользователем. У этого представления есть также общедоступный синоним, ALL_JOBS. ALL_JOBS содержит ту же информацию, что и USER_JOBS. • DBA_JOBS. Полный список всех заданий, находящихся в очередях базы данных. • DBA_JOBS_RUNNING. Список выполняющихся заданий. Обычно представление USER_JOBS доступно всем пользователям, а представления DBA_* - только пользователям с привилегией DBA или получившим привилегию SELECT непосредственно для этих представлений. В этих представлениях находится следующая информация. • LAST_DATE/LAST_SEC. Когда последний раз выполнялось задание. LAST_DATE столбец типа DATE. LAST_SEC - строка символов, содержащая только время (в формате часов:минут:секунд). • THIS_DATE/HIS_SEC. Если задание в настоящий момент выполняется, в этом столбце будет время начала выполнения. Как и пара столбцов LAST_DATE/LAST_SEC, столбец THIS_DATE содержит дату и время, а столбец THIS_SEC - символьную строку, в которой указано только время. • NEXT_DATE/NEXT_SEC. Время, когда задание будет выполнено в следующий раз. • TOTAL_TIME. Общее время выполнения задания в секундах. Включает время выполнения предыдущих прогонов - это суммарное значение. BROKEN. Флаг Yes/No, показывающий, что задание разрушено. Разрушенные задания не выполняются процессами обработчик очередей. Задание разрушается после 16 неудачных попыток выполнения. Для разрушения задания можно использовать процедуру DBMS_JOB.BROKEN (что временно предотвращает его выполнение). 110 • INTERVAL. Функция, возвращающая дату, которая вызывается в начале следующего выполнения задания, чтобы определить, когда снова выполнять задание. • FAILURES. Сколько раз подряд задание не было успешно выполнено. При успешном выполнении задания в этом столбце устанавливается значение 0. • WHAT. Текст задания. • NLS_ENV. Среда NLS (National Language Support - поддержка национальных языков), в которой будет выполняться задание. Включает язык, формат даты, формат чисел и т.п. Среда NLS полностью наследуется из среды, откуда было послано задание. При изменении этой среды и повторной отправке задания оно будет выполняться в измененной среде. • INSTANCE. Имеет смысл только в режиме Parallel Server. Это идентификатор экземпляра, на котором может выполняться задание, а в представлении DBA_JOBS_RUNNING экземпляра, на котором оно выполняется. Предположим, в этих представлениях обнаружено задание с положительным значением в столбце FAILURES. Где найти сообщения об ошибках для этого задания? Они не хранятся в базе данных, а записываются в журнал уведомлений (alert log) базы данных. UTL_FILE Стандартный пакет UTL_FILE позволяет читать и создавать текстовые файлы в файловой системе сервера в среде PL/SQL. Здесь существенны следующие ключевые слова. • Текстовые файлы. Пакет UTL_FILE позволяет читать и создавать простые текстовые файлы. В частности, его нельзя использовать для чтения или создания двоичных файлов. Специальные символы, содержащиеся в двоичных данных, приводят к некорректной работе пакета UTL_FILE. • В файловой системе сервера. Пакет UTL_FILE позволяет читать и записывать файлы только в файловой системе сервера баз данных. Он не позволяет читать или записывать в файловую систему компьютера, на котором работает клиент, если последний не подключен локально к серверу. Пакет UTL_FILE подходит для создания отчетов и сброса данных из базы в текстовые файлы, а также для чтения и загрузки данных. Пакет UTL_FILE также помогает при отладке. Наиболее существенным недостатком пакета UTL_FILE в ранних версиях Oracle было то, что в файле параметров инициализации в параметре UTL_FILE_DIR необходимо было явно перечислять все каталоги, в которых предполагается чтение и изменение файлов с помощью пакета. Изменять этот параметр инициализации в процессе работы сервера нельзя. Для добавления или удаления каталога необходимо перезапускать экземпляр. Параметр инициализации UTL_FILE_DIR задавался следующим способом: UTL_FILE_DIR = (c:\temp, c:\temp2) Начиная с версии Oracle 9i, пользователь может указать путь к каталогу в хранимом объекте DIRECTORY или использовать уже созданный DIRECTORY, получив доступ к нему. Синтаксис команды на создание DIRECTORY: CREATE DIRECTORY <имя директория> AS ‘<полный путь к каталогу>’ Пример: CREATE DIRECTORY my_dir AS ‘C: /Oracle/utl’; 111 Синтаксис команды, дающей право пользователю на чтение и запись существующего директория: GRANT READ, WRITE ON DIRECTORY <имя директория> TO <имя пользователя>; Пример: GRANT READ, WRITE ON DIRECTORY my_dir TO scott; Далее приведен пример создания простейшей процедуры, которая с помощью пакета UTL_FILE записывает фразу 'Пример использования пакета UTL_FILE' в файл под именем 'file.txt', который будет находится в каталоге, определенном директорией my_dir: CREATE OR REPLACE PROCEDURE proc_utl IS f UTL_FILE.FILE_TYPE; s VARCHAR2(200); t VARCHAR2(200); BEGIN t:='file.txt'; -- имя файла (создается пакетом) f := UTL_FILE.FOPEN ('MY_DIR', t, 'W', 32000); s:='Пример использования пакета UTL_FILE'; UTL_FILE.PUT_LINE (f,s); UTL_FILE.FCLOSE (f); END; Пакет UTL_FILE не позволяет получить список файлов каталога, чтобы можно было обработать все находящиеся в нем файлы. Пример чтения содержимого файла 'file.txt', содержащего несколько строк: DECLARE f UTL_FILE.FILE_TYPE; s VARCHAR2(200); t VARCHAR2(2000); BEGIN s:='file.txt'; f := UTL_FILE.FOPEN ('MY_DIR', s, 'R'); LOOP UTL_FILE.GET_LINE (f,t); DBMS_OUTPUT.PUT_LINE (t); END LOOP; EXCEPTION WHEN NO_DATA_FOUND THEN UTL_FILE.FCLOSE (f); END; DBMS_LOB DBMS_LOB - это стандартный пакет для работы с большими объектами (Large OBjects - LOBs) в базе данных. Большими объектами называют данные новых типов. Большие объекты поддерживают хранение и обработку до 4 Гбайт данных в одном столбце. Методов загрузки больших объектов немного. При наличии каталога с файлами для загрузки 112 BLOB проще всего использовать тип данных BFILE, объект DIRECTORY и процедуру LOADBLOBFROMFILE. CREATE OR REPLACE PROCEDURE load_blob (prod_id VARCHAR2, fname VARCHAR2) AS fileref BFILE; lob_locator BLOB; dst_offset INTEGER := 1; src_offset INTEGER := 1; src_charset NUMBER := 0; lang_ctxt NUMBER := 0; warnings INTEGER := 0; BEGIN SELECT product_image INTO lob_locator FROM product WHERE product_id = prod_id; IF lob_locator IS NULL THEN UPDATE product SET product_image = EMPTY_BLOB() WHERE product_id = prod_id RETURNING product_image INTO lob_locator; END IF; fileref := BFILENAME (MY_DIR', fname); DBMS_LOB.OPEN (fileref); DBMS_LOB.LOADBLOBFROMFILE (lob_locator, fileref, DBMS_LOB.LOBMAXSIZE, dst_offset, src_offset); DBMS_LOB.CLOSE (fileref); END; DBMS_ALERT Пакеты DBMS_ALERT и DBMS_PIPE - очень мощные средства межпроцессного взаимодействия. Оба они обеспечивают возможность взаимодействия сеансов базы данных. Пакет DBMS_ALERT по функциональности во многом аналогичен сигналам операционной системы UNIX, a DBMS_PIPE очень похож на именованный канал UNIX. Пакет DBMS_ALERT создавался для того, чтобы сеанс мог сигнализировать об определенном событии в базе данных. Другие сеансы, которых касается это событие, получают уведомление о том, что оно произошло. Уведомления эти посылаются в рамках транзакций, т.е. можно сигнализировать о событии в триггере или хранимой процедуре, но пока соответствующая транзакция не зафиксирована, уведомление ожидающим сеансам не посылается. Если транзакция отменена, уведомление не будет послано. Важно понимать, что сеанс, которому необходимо получить уведомление о событии в базе данных, должен либо периодически опрашивать базу данных, не поступил ли соответствующий сигнал, либо просто ждать в заблокированном состоянии возникновения соответствующего события. Если для приложения требуется получить уведомление, то наиболее существенными окажутся следующие подпрограммы. • REGISTER. Зарегистрироваться на получение указанного сигнала. В сеансе можно многократно вызывать подпрограмму REGISTER c разными именами сигналов, чтобы получать уведомления при наступлении одного из нескольких событий. 113 • REMOVE. Снять регистрацию на получение сигнала, чтобы сервер не пытался уведомить о наступлении события. • REMOVEALL. Снять для сеанса регистрацию на получение всех сигналов. • WAITANY. Ожидать уведомления о сигналах, на получение которых сеанс зарегистрирован. Эта подпрограмма выдает имя поступившего сигнала и обеспечивает доступ к сопровождающему его короткому сообщению. Ждать можно либо заданное время, либо вообще не ждать (что позволяет из приложения эпизодически опрашивать систему, чтобы узнать, не произошло ли событие, не блокируя при этом дальнейшую обработку ожиданием события). • WAITONE. Ожидать уведомления об указанном сигнале. Как и в случае WAITANY, ждать можно определенное время или вообще не ждать. Приложение, желающие послать сигнал, или уведомить о событии, может сделать это с помощью следующей подпрограммы. • SIGNAL. Послать сигнал о наступлении события при фиксации текущей транзакции. При откате посылка сигнала отменяется. Клиентское приложение, для которого требуется получение уведомления о событии, может содержать код вида: BEGIN DBMS_ALERT.REGISTER (' MyMert') ; END; DECLARE l_status NUMBER; l_msg VARCHAR2(1800); BEGIN DBMS_ALERT.WAITONE (name => 'MyMert', message => l_msg, status => l_status, timeout => dbms_alert.maxwait); IF (l_status = 0) THEN dbms_output.put_line ('Сообщение события: ' || l_msg); END IF; END; Мы зарегистрировались на получение сигнала MyAlert, а затем вызвали процедуру DBMS_ALERT.WAITONE, ожидая поступление этого сигнала. Поскольку использована константа DBMS_ALERT.MAXWAIT из пакета DBMS_ALERT, сеанс при выполнении этого вызова начнет ждать бесконечно. Сеанс блокируется в ожидании соответствующего события. Можно задать для клиентского приложения период ожидания 0 секунд, что исключит ожидание, и опрашивать сервер о наступлении события. Чтобы послать сигнал, достаточно выполнить следующее в другом сеансе: BEGIN DBMS_ALERT.SIGNAL ('MyMert', 'Hello World'); END; 114 COMMIT; В сеансе, заблокированном в ожидании события, вы должны немедленно увидеть: Сообщение события: Hello World. То есть сеанс больше не заблокирован. Сеансы ждут сигнала с определенным именем. Пока в пославшем сигнал сеансе транзакция не зафиксирована, уведомление о сигнале не посылается. В этом легко убедиться с помощью двух сеансов SQL*Plus. Работа с сигналами становится более интересной, если задаться следующими вопросами. • Что происходит, если несколько сигналов более-менее одновременно отправляются разными сеансами? • Что происходит, если посыпать сигнал несколько раз: сколько сигналов будет сгенерировано в конечном итоге? • Что происходит, если более одного сеанса пошлют сигнал, после того как я зарегистрировался на его получение, но до вызова одной из процедур ожидания? А что произойдет, если несколько сеансов пошлют сигнал в промежутке между вызовами процедур ожидания? Ответы на эти вопросы позволят выявить побочные эффекты использования сигналов, которые необходимо учитывать. Если повторно выполнить рассмотренный пример, зарегистрировавшись на получение сигнала MyAlert и ожидая его в одном сеансе, а затем запустить два дополнительных сеанса, можно будет увидеть, что произойдет при одновременной передаче сигналов из нескольких сеансов. На этот раз в обоих сеансах выполним: BEGIN DBMS_ALERT.SIGNAL ('MyMert', 'Hello World'); END; (транзакции не фиксируются). Окажется, что сеанс, пославший сигнал вторым, заблокирован. Это показывает, что если N сеансов одновременно пытаются послать один и тот же сигнал, N-1 из них будут заблокированы при вызове DBMS_ALERT.SIGNAL. Продолжит работу только один из сеансов. Сигналы должны посылаться последовательно, и следует позаботиться о предотвращении подобных проблем. Например, при получении оперативных данных из внешнего источника пакет DBMS_ALERT вполне можно использовать, если данные в таблицу вставляет только один сеанс. Если же речь идет о таблице проверки, в которую часто вставляют данные все сеансы, средства пакета DBMS_ALERT лучше не использовать. Что произойдет, если послать одноименный сигнал в приложении несколько раз, а затем зафиксировать транзакцию? Сколько сигналов фактически будет послано? В данном случае ответ: один. Можно выполнить фрагмент кода, регистрирующий сеанс на уведомление о событии и вызывающий процедуру WAITONE в ожидании этого события. В другом сеансе выполняем: BEGIN FOR i IN 1 .. 10 LOOP DBMS_ALERT.SIGNAL ('MyAlert', 'Сообщение ' || i ) ; END LOOP; 115 END; COMMIT; И в первом окне получаем результат: Сообщение события: сообщение 10 Послано будет только последнее сообщение, о котором мы сигнализировали. Промежуточных сообщений никто никогда не увидит. Следует учитывать, что пакет DBMS_ALERT будет, как и задумано создателями, отбрасывать все предыдущие сообщения сеанса. С помощью этого пакета нельзя отправить в транзакции последовательность сообщений - это только механизм сигнализации. Он позволяет уведомить клиентское приложение, что "нечто произошло". Что произойдет, если сигнал будет послан несколькими сеансами после того, как на него поступил запрос, но прежде, чем вызвана процедура ожидания? Аналогичный вопрос: что произойдет, если между вызовами процедур ожидания несколько сеансов пошлют сигнал? Как и в случае многократного вызова DBMS_ALERT.SIGNAL в одном сеансе, запоминается только последний сигнал, и именно о нем получат уведомление сеансы. В этом можно убедиться, добавив команду PAUSE к используемому в примерах сценарию SQL*Plus: BEGIN DBMS_ALERT.REGISTER ('MyAlert'); END; PAUSE Затем в других сеансах вызовите процедуры DBMS_ALERT.SIGNAL с уникальными сообщениями (чтобы их можно было различать) и зафиксируйте каждое сообщение. Например, измените представленный ранее простой цикл следующим образом: BEGIN FOR i IN 1 .. 10 LOOP DBMS_ALERT.SIGNAL ('MyMert', 'Сообщение ' || i); COMMIT; END LOOP; END; После этого в исходном сеансе просто нажмите клавишу Enter, и блок кода, вызывающий процедуру WAITONE, будет выполнен. Поскольку ожидаемый сигнал уже послан, этот блок кода немедленно завершит работу и выдаст строку, свидетельствующую о получении последнего сообщения (о чем оповестил сигнал). Все промежуточные сообщения других сеансов потеряны, как и было задумано создателями пакета. Итак, пакет DBMS_ALERT подходит для тех случаев, когда необходимо уведомить о событиях в базе данных множество клиентов. Об этих именованных событиях должно сообщать как можно меньше сеансов, из-за существенных проблем с очередностью доступа к процедурам пакета DBMS_ALERT. Поскольку неоднократные сообщения теряются, пакет DBMS_ALERT подходит в качестве средства уведомления о событии. Его можно использовать для уведомления клиента, например, об изменении данных в таблице T, но попытка использовать его для уведомления об изменениях в отдельных строках таблицы T закончится неудачей (поскольку сохраняется только последнее сообщение). Пакет DBMS_ALERT очень прост в использовании и практически не требует настройки. 116 DBMS_PIPE Пакет DBMS_PIPE более универсален. Он позволяет одному или нескольким сеансам читать сообщения с одной стороны именованного канала и при этом записывать сообщения в этот канал с другой стороны. Только один из читающих сеансов может получить сообщение, причем адресовать сообщение конкретному сеансу по одному именованному каналу нельзя. Если читающих сеансов больше одного, прочитает записанное в канал сообщение любой из них. Каналы не поддерживают транзакции: если сообщение послано, оно будет доступным другим сеансам. Фиксировать транзакцию не надо, а фиксация или откат соответствующей транзакции не повлияет на результат передачи сообщения по каналу. Пакет DBMS_PIPE, в отличие от DBMS_ALERT, - это пакет, работающий в режиме реального времени. При вызове функции SEND_MESSAGE немедленно посылается сообщение. Сервер не ждет выполнения оператора COMMIT; передача сообщения выполняется вне транзакции. Это позволяет использовать пакет DBMS_PIPE в тех случаях, когда DBMS_ALERT не подходит (и наоборот). С помощью пакета DBMS_PIPE можно обеспечить диалоговое взаимодействие двух сеансов (что с помощью DBMS_ALERT сделать невозможно). Один сеанс может "попросить" другой выполнить некоторое действие. Выполнив его, второй сеанс возвращает первому результат. Предположим, второй сеанс - это С-программа, которая снимает показания термометра, подключенного к последовательному порту компьютера, и возвращает значение температуры первому сеансу. Первому сеансу надо записать текущую температуру в базу данных. Он может послать сообщение "дай мне значение температуры" второму сеансу, который определяет это значение и выдает ответ первому сеансу. Первый и второй сеансы могут работать на разных компьютерах, главное, оба они подключены к одной базе данных. При использовании пакета DBMS_PIPE не нужно знать имя хоста и номер порта для подключения - достаточно имени канала базы данных, в который надо отправить запрос. В базе данных есть два типа каналов - общедоступные и пользовательские. Общедоступный канал можно создать явно, вызвав CREATE_PIPE, либо неявно, послав в него сообщение. Основное отличие между явно и неявно созданными каналами состоит в том, что канал, созданный явным вызовом CREATE_PIPE, удаляется приложением по завершении работы, тогда как неявно созданный канал удаляется из области SGA как устаревший после определенного промежутка времени. Общедоступный канал устроен так, что любой сеанс, имеющий доступ к пакету DBMS_PIPE, может читать и записывать в него сообщения. Поэтому общедоступные каналы не подходят для передачи секретных или просто важных данных. Поскольку каналы обычно используются для диалога, а общедоступные каналы позволяют перехватывать или вмешиваться в этот диалог любому, злонамеренный пользователь может удалять сообщения из канала либо добавлять свои, "мусорные". Любое из этих действий нарушает диалог или протокол обмена данными между сеансами. Поэтому в большинстве приложений применяются пользовательские каналы. К данным в пользовательских каналах можно обращаться только сеансам, работающим с эффективным идентификатором пользователя-владельца канала, или от имени специальных пользователей (SYS). Это означает, что только с помощью хранимых процедур с правами создателя , принадлежащих владельцу канала, либо в сеансах от имени владельца канала, пользователя SYS можно читать или записывать данные в этот канал. Это существенно увеличивает надежность каналов, поскольку ни один другой сеанс или код не может вмешаться в протокол или перехватить данные. 117 Канал - это объект в области SGA экземпляра Oracle. Этот механизм вообще не связан с диском. Данные в каналах теряются при остановке и перезапуске сервера. Чаще всего каналы используют для создания специализированных служб или серверов. Поскольку делать попытку читать данные из канала и писать их туда может любое количество сеансов, необходимо реализовать алгоритм, гарантирующий доставку сообщений нужному сеансу. Если предполагается создание специализированной службы (например, представленного ранее сервера температуры) и ее добавление в базу данных, необходимо гарантировать получение ответа, предназначенного сеансу А, именно сеансом А, а не сеансом В. Для удовлетворения этого стандартного требования обычно запрос выдается в один канал с общеизвестным именем, а вместе с обращением передается уникальное имя канала, из которого мы хотим прочитать ответ. Одна из интересных особенностей каналов базы данных - возможность читать из канала несколькими сеансами. Помещенное в канал сообщение будет прочитано только одним сеансом, но читать сообщения из канала может несколько сеансов одновременно. Пример передачи сообщения 'ku-ku' в канал 'PIPE': DECLARE a NUMBER; b VARCHAR2(20); BEGIN a:=DBMS_PIPE.CREATE_PIPE ('PIPE'); DBMS_PIPE.PACK_MESSAGE ('ku-ku'); END; Пример приема сообщения из канала 'PIPE': DECLARE a NUMBER; b VARCHAR2(20); BEGIN a:=DBMS_PIPE.SEND_MESSAGE('PIPE'); a:=DBMS_PIPE.RECEIVE_MESSAGE('PIPE'); DBMS_PIPE.UNPACK_MESSAGE(b); DBMS_OUTPUT.PUT_LINE(b); END; DBMS_UTILITY Пакет DBMS_UTILITY - это набор процедур различного назначения. В него помещено несколько отдельных, не связанных между собой процедур. Процедуры в этом пакете не взаимосвязаны, как в большинстве остальных пакетов. Например, все подпрограммы пакета UTL_FILE имеют общее назначение - выполнение ввода-вывода в файл. Подпрограммы в пакете DBMS_UTILITY практически независимы. Рассмотрены некоторые из этих подпрограмм: Процедура COMPILE_SCHEMA предназначена для перекомпиляции недействительных (invalid) процедур, пакетов, триггеров, представлений, типов и других объектов схемы. Эта процедура выполняет оператор ALTER COMPILE от имени пользователя, который вызвал процедуру COMPILE_SCHEMA (т.е. она работает с правами вызывающего). Процедура COMPILE_SCHEMA требyeт передавать имена пользователей в верхнем регистре. Если вызвать: 118 BEGIN DBMS_UTILITY.COMPILE_SCHEMA ('scott'); END; скорее всего, ничего не произойдет, если при создании учетной записи имя пользователя scott не задано в нижнем регистре (как идентификатор в кавычках). Необходимо передать имя схемы как SCOTT. Типичное заблуждение состоит в том, что объекты надо компилировать в строго определенном порядке. На самом деле компилировать объекты можно в произвольном порядке и получить тот же результат, что и при компиляции в порядке, определяемом зависимостями. Алгоритм следующий: 1. Выбираем недействительный объект схемы, который мы еще не пытались перекомпилировать. 2. Компилируем его. 3. Возвращаемся к первому шагу, пока есть недействительные объекты, которые мы еще не пытались перекомпилировать. Определенного порядка придерживаться не обязательно. Причина - в побочном эффекте компиляции недействительного объекта. При этом все недействительные объекты, от которых он зависит, тоже будут скомпилированы. Надо только продолжать компилировать объекты, пока недействительных не останется. (На самом деле недействительные объекты могут остаться, но лишь потому, что скомпилировать их невозможно вообще.) Может оказаться, что при компиляции всего одной процедуры перекомпилированными окажутся 10 или 20 других объектов. Если не пытаться перекомпилировать эти 10 или 20 объектов вручную (при этом исходный объект снова станет недействительным), все будет в порядке. Процедура ANALYZE_SCHEMA делает именно то, что можно предположить по ее названию, - выполняет операторы ANALYZE для сбора статистической информации об объектах в пользовательской схеме. Не рекомендуется применять ее для схем SYS или SYSTEM. В особенности не надо этого делать для схемы SYS, поскольку рекурсивные SQLоператоры, которые СУБД Oracle генерирует уже многие годы, оптимизированы для обработки оптимизатором, основанным на правилах. При наличии статистической информации о таблицах в схеме SYS сервер будет работать медленнее, чем мог бы. Эту процедуру можно использовать для анализа созданных пользователями прикладных схем. Процедура ANALYZE_SCHEMA принимает пять аргументов. • SCHEMA. Схема, которую необходимо проанализировать. • METHOD. ESTIMATE, COMPUTE или DELETE. Если передано значение ESTIMATE, то одно из значений: ESTIMATE_ROWS, ESTIMATE_PERCENT должно быть ненулевым. • ESTIMATE_ROWS. Количество оцениваемых строк. • ESTIMATE_PERCENT. Процент оцениваемых строк. Если передано ненулевое значение параметра ESTIMATE_ROWS, этот параметр игнорируется. • METHOD_OPT [FOR TABLE] [FOR ALL [INDEXED] COLUMNS] [SIZE n] [FORALL INDEXES]. Это те же опции, что используются в операторе ANALYZE. Итак, например, все объекты в пользовательской схеме SCOTT можно проанализировать следующим образом. Начнем с удаления статистической информации, а затем соберем ее снова: 119 BEGIN dbms_utility.analyze_schema (user, 'delete'); END SELECT table_name, num_rows, and last_analyzed FROM user_tables; BEGIN dbms_utility.analyze_schema(user, 'compute'); END; SELECT table_name, num_rows, last_analyzed FROM user_tables; Этот простой пример показывает, что оператор ANALYZE COMPUTE выполняется столбцы NUM_ROWS и LAST_ANALYZED получили значения. Процедура ANALYZE_SCHEMA работает в соответствии со своим названием. Если необходимо анализировать объекты с разной степенью детализации, она не поможет. Процедура применяет один и тот же метод анализа ко всем типам объектов. Например, при эксплуатации большого хранилища данных, если необходимо использовать гистограммы по определенным столбцам или наборам столбцов только в некоторых таблицах, процедуру ANALYZE_SCHEMA применять нельзя. С помощью процедуры ANALYZE_SCHEMA можно либо получить гистограммы для всех столбцов, либо не получить их вообще избирательно обрабатывать столбцы нельзя. Если анализ объектов выходит за рамки элементарного, процедура ANALYZE_SCHEMA не позволит его выполнить. Она подходит для небольших и средних (по объему обрабатываемых данных) приложений. Если необходимо обрабатывать большие объемы данных, имеет смысл распараллелить анализ или использовать разные опции анализа для различных таблиц. Этого процедура ANALYZE_SCHEMA не обеспечивает. В процедуре ANALYZE_SCHEMA есть нерешенная проблема. Она не анализирует таблицы, организованные по индексу, если в них используется дополнительный сегмент. Стратегии и средства настройки приложений Настройка производительности является частью этапа проектирования, она выполняется на этапе разработки, в ходе тестирования, при внедрении системы и затем при ее эксплуатации. Существует три уровня настройки: • Настройка приложения, часть 1. Настройка изолированного приложения. Обеспечение его максимально быстрой работы в однопользовательском режиме. • Настройка приложения, часть 2. Настройка приложения в многопользовательском режиме. Обеспечение поддержки как можно большего количества одновременно работающих пользователей. • Настройка экземпляра/сервера. Настройка приложения, изолированно и в многопользовательском режиме, требует около 80 процентов всех усилий по настройке. Крайне маловероятно, что можно заставить запросы выполняться существенно быстрее простой установкой параметра в файле инициализации. Для настройки SQL – приложений не нужно понимать написанный код, можно рассматривать SQL как спецификацию – ясное и непротиворечивое описание того, какие строки из каких таблиц требуются для приложения. Вам не надо знать, зачем приложению 120 требуются те или иные строки, или даже какие именно данные в них содержатся. Просто обращайтесь с записями и таблицами как с абстрактными объектами. Все, что нужно знать – это как быстрее добраться до этих строк. А узнать это можно, исследуя SQL-запросы, таблицы и индексы при помощи простых обращений к базе данных, полностью независимых от содержания данных. Затем вы можете изменить запросы или базу данных (например, добавив необходимые индексы), причем простым способом, почти с математической точностью гарантирующим, что трансформированный запрос вернет те же самые строки в том же самом порядке, но будет получать данные по лучшему, более быстрому пути. Oracle использует основанный на SQL подход к созданию и отображению планов выполнения. При помощи SQL вы помещаете данные плана в таблицу, после чего можно просмотреть их, используя обычный SQL-запрос. Исходный, анализируемый SQL-запрос не выполняется, а только разбирается. Это осуществляется с помощью оператора SQL EXPLAIN PLAN. EXPLAIN PLAN До использования оператора EXPLAIN PLAN надо создать таблицу PLAN_TABLE, куда и будут записаны результаты разбора SQL – запроса. Скрипт на создание этой таблицы: @ [ORACLE_HOME]/rdbms/admin/utlxplan.sql Пример получения плана выполнения: EXPLAIN PLAN FOR SELECT COUNT (*) FROM hr.employees; Результат разбора можно посмотреть следующим образом: SELECT id, operation, cost FROM plan_table; Или можно именовать ( 'join1') план выполнения: EXPLAIN PLAN SET statement_id = 'join1' FOR SELECT last_name, department_name FROM hr.employees JOIN hr.departments USING (department_id) И результат можно посмотреть двумя способами: SELECT statement_id, id, operation, cost или FROM plan_table WHERE statement_id = ‘join1’; SELECT * FROM TABLE (dbms_xplan.display ('PLAN_TABLE,'join1', 'BASIC')); Вместо вида отображения BASIC можно еще получить TYPICAL (по умолчанию), SERIAL,ALL. Утилита SQL*Plus предлагает средство AUTOTRACE, позволяющее получать планы выполнения обрабатываемых запросов, а также информацию об используемых ресурсах, без выполнения команды EXPLAIN PLAN. Соответствующий отчет генерируется после успешного выполнения операторов SELECT, DELETE, UPDATE и INSERT. Средство AUTOTRACE можно настроить несколькими способами. • зарегистрироваться в SQL*Plus от имени SYSTEM; • запустить сценарий @utlxplan из каталога [ORACLE_HOME]/rdbms/admin; • выполнить оператор CREATE PUBLIC SYNONYM PLAN_TABLE FOR PLAN_TABLE; 121 • выполнить оператор GRANT ALL ON PLAN_TABLE TO PUBLIC. Можно заменить GRANT ... ТО PUBLIC оператором GRANT для конкретного пользователя. Предоставляя привилегию роли PUBLIC, вы фактически разрешаете трассировать операторы в SQL*Pius любому пользователю. Это неплохо - пользователи могут не устанавливать собственные таблицы планов. Альтернатива этому - запуск сценария @UTLXPLAN в каждой схеме, где необходимо использовать средство AUTOTRACE. Следующий шаг - создание и предоставление всем роли PLUSTRACE: • зарегистрироваться в SQL*Plus от имени SYS; • запустить сценарий @plustrce из каталога [ORACLE_HOME]/sqlplus/admin; • выполнить оператор GRANT PLUSTRACE TO PUBLIC. И в этом случае, можно заменить PUBLIC в операторе GRANT именем конкретного пользователя. Управлять информацией, выдаваемой в отчете о плане выполнения, можно с помощью установки системной переменной AUTOTRACE. SET AUTOTRACE OFF - Отчет AUTOTRACE не генерируется. Так происходит по умолчанию. SET AUTOTRACE ON EXPLAIN - В отчете AUTOTRACE показывается только выбранный оптимизатором план. SET AUTOTRACE ON STATISTICS - В отчете AUTOTRACE показывается только статистическая информация о выполнении оператора SQL SET AUTOTRACE ON - В отчет AUTOTRACE включается как выбранный оптимизатором план, так и статистическая информация о выполнении оператора SQL. SET AUTOTRACE TRACEONLY - Аналогично SET AUTOTRACE ON, но подавляет выдачу результатов выполнения запроса. План выполнения отражает выбранный оптимизатором способ выполнения запроса. Каждая строка плана выполнения имеет порядковый номер. Утилита SQL*Plus также выдает номер строки родительской операции. План выполнения состоит из четырех столбцов, выдаваемых в следующем порядке: ID_PLUS_EXP - Показывает порядковый номер шага выполнения PARENT_ID_PLUS_EXP - Показывает для каждого шага родительский шаг. Этот столбец полезен в больших отчетах. PLAN_PLUS_EXP - Показывает описание шага выполнения. OBJECT_NODE_PLUS_EXP - Показывает использованные базы данных или серверы для параллельного запроса. SQL_TRACE, TIMED_STATISTICS и TKPROF Параметр SQL_TRACE включает регистрацию всех операторов SQL, выполняемых приложением, информации о производительности, полученной в ходе выполнения этих операторов SQL, и фактически использованных планов выполнения операторов. 122 AUTOTRACE иногда показывает неверный план, а вот параметр SQL_TRACE и утилита TKPROF показывают именно тот план, который реально использован. TIMED_STATISTICS это параметр, при установке которого сервер регистрирует продолжительность выполнения каждого шага. Наконец, TKPROF - это простая программа, используемая для преобразования файла трассировки в более удобочитаемый вид. Продемонстрируем, как использовать установку SQL_TRACE и утилиту TKPROF, и разъясним значение содержимого используемых ими файлов. Параметр TIMED_STATISTICS управляет тем, будет ли сервер Oracle собирать информацию о времени выполнения различных действий в базе данных. Он может иметь одно из двух значений: TRUE или FALSE. Обычно устанавливают значение TRUE, даже когда не занимаются настройкой - влияние этого значения на производительность СУБД, как правило, незначительно. Значение этого параметра можно устанавливать как на уровне системы, так и на уровне сеанса, а также глобально, в файле параметров инициализации экземпляра. Достаточно просто добавить в файл INIT.ORA для экземпляра строку: TIMED_STATISTICS = TRUE и при следующем перезапуске СУБД этот параметр будет включен. Для установки его на уровне сеанса выполните следующую команду: ALTER SESSION SET timed_statistics=true; А для включения учета времени во всей системе: ALTER SYSTEM SET timed_statistics=true; Параметр SQL_TRACE также можно устанавливать на уровне системы или сеанса. При его установке генерируется так много данных и работа системы так замедляется, что включать ею лучше избирательно (редко или вообще никогда его не устанавливают для системы в файле init.ora). Параметр SQL_TRACE тоже может иметь одно из двух значений, TRUE или FALSE. Если установлено значение TRUE, в каталоге, задаваемом параметром USER_DUMP_DEST файла init.ora при подключении к выделенному серверу или BACKGROUND_DUMP_DEST - при подключении к многопотоковому (MTS) серверу, будут генерироваться трассировочные файлы. Не рекомендуется использовать SQL_TRACE при подключении в режиме MTS, поскольку результаты запросов сеанса будут записываться в различные трассировочные файлы при переходе сеанса с одного разделяемого сервера на другой. При подключении в режиме MTS интерпретация результатов SQL_TRACE практически невозможна. Еще один важный параметр в файле init.ora MAX_DUMP_FILE_SIZE. Он ограничивает максимальный размер генерируемого сервером трассировочного файла. Если обнаружится, что трассировочные файлы - усеченные, увеличьте значение этого параметра. Это можно сделать с помощью оператора ALTER SYSTEM или ALTER SESSION. Параметр MAX_DUMP_FILE_SIZE можно задать тремя способами. • Числовое значение параметра MAX_DUMP_FILE_SIZE задает максимальный размер в блоках файловой системы. • Число, за которым следует суффикс К или М, задает размер в килобайтах или мегабайтах, соответственно. • Строка UNLIMITED означает, что ограничения на размер трассировочных файлов нет - они могут иметь любой размер, допускаемый операционной системой. 123 Не рекомендуется устанавливать значение UNLIMITED - так можно заполнить всю файловую систему; значения в диапазоне от 50 до 100 Мбайт обычно более чем достаточно. Существуют следующие способы включения параметра SQL_TRACE: • ALTER SESSION SET SQL_TRACE=TRUE|FALSE. Выполнение этого оператора SQL позволит включить стандартный режим трассировки SQL_TRACE в текущем сеансе. Этот оператор наиболее полезен в интерактивной среде типа SQL*Plus или при встраивании в приложение, так чтобы из приложения можно было при необходимости включать и отключать трассировку. Возможность просто включать и отключать SQL_TRACE средствами приложения - будь-то опция командной строки, пункт меню или параметр конфигурации полезна в любом приложении. • SYS.DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION. Эта процедура позволяет устанавливать и сбрасывать трассировку для любого существующего сеанса. Для этого необходимо указать лишь параметры SID и SERIAL# соответствующего сеанса - их можно получить из представления динамической производительности V$SESSION. • ALTER SESSION SET EVENTS. Можно установить событие, обеспечивающее трассировку с большим объемом регистрируемой информации, чем обычно получается при установке ALTER SESSION SET SQL_TRACE=TRUE. С помощью этого события можно не только получить всю информацию, выдаваемую при установке SQL_TRACE, но и значения связываемых переменных в SQL-операторах, а также информацию о том, какие события ожидаются (что замедляет работу) при выполнении этих SQL-операторов. Методы установки SQL_TRACE оператором ALTER SESSION SET SQL_TRACE и вызовом SYS.DBMS_SYSTEM - очень просты и очевидны. Использование события несколько менее тривиально. При этом используется внутренний механизм событий сервера Oracle.Используются следующие команды: ALTER SESSION SET EVENTS '10046 trace name context forever, level <N>'; ALTER SESSION SET EVENTS '10046 trace name context off; где N может иметь одно из следующих значений: N=1. Включает стандартные средства SQL_TRACE. Результат не отличается от установки SQL_TRACE=true. N=4. Включает стандартные средства SQL_TRACE и добавляет в трассировочный файл значения связываемых переменных. N=8. Включает стандартные средства SQL_TRACE и добавляет в трассировочный файл информацию об ожидании событий на уровне запросов. N=12. Включает стандартные средства SQL_TRACE и добавляет как значения связываемых переменных, так и информацию об ожидании событий. Как обеспечить трассировку, если приходится работать с приложением стороннего производителя или с существующим приложением, не поддерживающим включение SQL_TRACE? Используются два подхода. Один из них подходит для клиент-серверного приложения, постоянно подключенного к базе данных. Достаточно запустить приложение и подключиться к базе данных. Затем, выполнив запрос к представлению V$SESSION, можно определить параметры SID и SERIAL# соответствующего сеанса. Теперь можно вызвать SYS.DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION для включения трассировки указанного сеанса. 124 Второй способ трассировки - с помощью триггера базы данных на событие LOGON. CREATE OR REPLACE TRIGGER logon_trigger AFTER LOGON ON DATABASE BEGIN IF (user = 'SCOTT') THEN EXECUTE IMMEDIATE 'ALTER SESSION SET EVENTS ''10046 TRACE NAME CONTEXT FOREVER, LEVEL 4 ' " ; END IF; END; Он обеспечивает включение трассировки при каждом подключении к базе данных. Приложение не надо менять для установки SQL_TRACE - мы это сделаем сами. Для интерпретации результатов трассировки используется утилита TKPROF. TKPROF - это средство командной строки для преобразования трассировочного файла в более удобочитаемый вид. Выполним запрос с включенной трассировкой и рассмотрим соответствующий отчет TKPROF: Проверим включение параметра TIMED STATISTICS – должен быть = TRUE: SHOW PARAMETER TIMED STATISTICS; Включает трассировку: ALTER SESSION SET sql_trace=true; Выполняем запрос, который будем анализовать: SELECT owner, count (*) FROM all_objects GROUP BY owner; Запрос для получения идентификатора серверного процесса (SPID - server process ID) он потребуется для идентификации соответствующего трассировочного файла. SELECT a.spid FROM v$process a, v$session b WHERE a.addr = b.paddr AND b.audsid = userenv ('sessionid'); Значение SPID - часть имени файла трассировки. Другой способ для идентификации файла трассировки – задание префикса трассировочного файла до начала трассировки: ALTER SESSION SET TRACEFILE_IDENTIFIER = 'SCOTT'; Теперь, получив трассировочный файл, необходимо его сформатировать. Можно читать его и непосредственно. Но около 90 процентов нужной информации легко получить из хорошо сформатированного отчета. Остальные 10 процентов информации обычно не нужны, но если она потребуется, придется получать эту информацию непосредственно из трассировочного файла. Для форматирования трассировочного файла используется утилита командной строки TKPROF. В простейшем случае достаточно выполнить: TKPROF ora01124_scott.trc report.txt Параметрами команды TKPROF являются имена файла исходных данных и файла результатов обработки. Теперь достаточно открыть файл REPORT.TXT в текстовом редакторе. После текста исходного запроса идет общая информация о выполнении запроса: Здесь можно увидеть три основные стадии выполнения запроса. 125 • Стадия PARSE. На этом этапе сервер Oracle находит запрос в разделяемом пуле (мягкий разбор) или создает новый план его выполнения (жесткий разбор). • Стадия EXECUTE. Это действия, выполняемые сервером Oracle при открытии курсора или выполнении запроса. Для операторов SELECT соответствующие столбцы часто будут "пустыми", тогда как для операторов UPDATE именно на этой стадии все и делается. • Стадия FETCH. Для оператора SELECT именно на этом этапе выполняется основная работа, что и будет отражено в отчете, но для операторов типа UPDATE ничего делаться не будет (при выполнении этого оператора данные не извлекаются). Заголовки столбцов в этом разделе отчета имеют следующие значения: • CALL. Может иметь одно из значений: PARSE, EXECUTE, FETCH или TOTAL. Показывает, о какой стадии обработки запроса идет речь. • COUNT. Показывает, сколько раз произошло событие • CPU. Показывает, сколько секунд процессорного времени заняла эта стадия выполнения запроса. Этот столбец заполняется, только если установлен параметр TIMED_STATISTICS. • ELAPSED. Показывает, сколько реального времени потребовала эта стадия выполнения запроса. Этот столбец заполняется, только если установлен параметр TIMED STATISTICS. • DISK. Показывает, сколько физических операций ввода/вывода с диска потребовалось для выполнения запроса. • QUERY. Показывает, сколько блоков обработал запрос в режиме согласованного чтения. Сюда входят блоки, прочитанные из сегмента отката для получения предыдущего состояния блока. • CURRENT. Показывает, сколько блоков было прочитано в режиме 'CURRENT'. Блоки в режиме CURRENT читаются в том виде, как они есть на момент чтения, а не в режиме согласованного чтения. Обычно блоки для запроса получаются в том виде, как они были на момент начала запроса. В текущем режиме блоки извлекаются в том виде, как они существуют на момент их чтения, а не какими они были ранее. В ходе выполнения оператора SELECT можно увидеть извлечения в режиме CURRENT, связанные с чтением словаря данных в поисках следующего экстента таблицы при полном просмотре (необходима текущая информация об этом, а не согласованное чтение). В ходе изменения мы будем также обращаться к блокам в режиме CURRENT. • ROWS. Показывает, сколько строк было затронуто на данной стадии обработки. При выполнении оператора SELECT строки будут обрабатываться на стадии FETCH. При выполнении оператора UPDATE количество обработанных строк будет показано на стадии EXECUTE. В этом разделе отчета надо обратить внимание на следующие особенности. Если количество выполнений - более одного, то будет значительный процент (около 100) разборов по отношению к выполнениям. Берем количество разборов оператора и делим на количество выполнений. Если получаем в результате 1, значит, запрос разбирался при каждом выполнении, и это надо исправить. Желательно, чтобы это отношение стремилось к нулю. В идеале разбор должен быть один, а выполнений - более одного. Если наблюдается значительное количество разборов, значит, выполняется многократный мягкий разбор запроса. Это может существенно снизить масштабируемость и производительность даже единственного пользовательского сеанса. Необходимо обеспечить однократный разбор и 126 многократное выполнение запроса в сеансе; от разбора SQL-оператора при каждом выполнении надо избавляться. Одно выполнение для всех или почти всех операторов SQL. Если в отчете TKPROF указано, что все операторы SQL выполняются только один раз, скорее всего, не используются связываемые переменные (выполняются похожие запросы, которые отличаются лишь используемыми константами). В трассировочных файлах реальных приложений уникальных операторов SQL обычно немного; одни и те же SQL-операторы выполняются многократно. Слишком большое количество уникальных SQL-операторов обычно означает, что недостаточно используются связываемые переменные. Существенное отличие процессорного и реального времени выполнения запроса. Это означает, что приходится долго чего-то ждать. Если для выполнения необходима одна секунда процессорного времени, но реально запрос выполнялся 10 секунд, это означает, что 90 процентов времени ушло на ожидание освобождения ресурса. Это ожидание может быть вызвано несколькими причинами. Например, на выполнение изменения, заблокированного другим сеансом, уйдет намного больше реального времени, чем процессорного. SQL-запрос, выполняющий большой объем физического ввода/вывода с диска, может долго ждать завершения ввода/вывода. Длительное процессорное или реальное время выполнения. Сокращение продолжительности длительно выполняющихся запросов - ваша ближайшая цель. Если удастся ускорить их выполнение, программа заработает быстрее. Зачастую, один запросмонстр тормозит всю работу; настройте его, и приложение будет отлично работать. Большая величина отношения (FETCH COUNT)/(количество извлеченных строк). Для ее вычисления берем количество действий типа FETCH и делим на количество извлеченных строк. Если полученный результат близок к одному и извлечено более одной строки, приложение не выполняет множественные извлечения. Любой язык или функциональный интерфейс позволяет это делать - извлекать несколько строк одним вызовом. Если возможность множественного извлечения не используется, на пересылки информации с клиента на сервер и обратно уйдет намного больше времени. Этот постоянный обмен информацией, помимо того, что чрезвычайно загружает сеть, выполняется намного медленнее, чем получение нескольких строк одним вызовом. Как организовать множественное извлечение данных в приложении, зависит от используемого языка и/или функционального интерфейса. Слишком большое количество физических обращений к диску. Простое правило для этого параметра придумать сложнее, но если DISK COUNT = QUERY + CURRENT MODE BLOCK COUNT, значит, все блоки читались с диска. Будем надеяться, что при следующем выполнении этого запроса часть блока будет найдена в области SGA. Большое количество обращений к диску - предупреждающий сигнал о том, что необходимо провести дополнительное исследование. Возможно, надо увеличить размер буферного кэша в SGA или придумать другой запрос, требующий чтения меньшего количества блоков. Слишком большое количество обработанных блоков (QUERY или CURRENT). Это показывает, что запрос обрабатывает большой объем информации. Проблема это или нет судить вам. Некоторым запросам действительно необходимо обработать много данных, как в представленном ранее примере. Часто выполняемый запрос, однако, должен обрабатывать сравнительно немного блоков. Если сложить значения QUERY и CURRENT количества обработанных блоков и поделить на значение столбца count в строке EXECUTE, должно получаться небольшое число. 127 Следующая часть отчета: Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 69 Из этого можно сделать вывод, что выполненный запрос был найден в разделяемом пуле (количество не найденных в библиотечном кэше запросов равно 0). То есть, выполнялся мягкий разбор запроса. При самом первом выполнении запроса в этой строке будет значение 1. Если практически у всех выполнявшихся запросов будет значение 1, значит, не используются связываемые переменные (это надо исправить). Операторы SQL не используются повторно. Вторая строка показывает режим работы оптимизатора при выполнении запроса. Эта информация - для справки; выбранный и использованный план выполнения запроса зависит от этого режима. Наконец, представлен идентификатор пользователя, разобравшего запрос. По этому идентификатору можно получить имя пользователя: SELECT * FROM all_users WHERE user_id = 69; Последний раздел отчета TKPROF для данного запроса - план выполнения. Это реальный план запроса, использованный сервером Oracle при выполнении. По этой информации можно понять, от каких шагов выполнения запроса имеет смысл отказаться либо путем изменения запроса, либо путем создания дополнительных индексов, либо с помощью подсказок оптимизатору, выбирающих более удачный план. Утилита TKPROF имеет много опций командной строки, и если ввести команду TKPROF все они будут выданы: Usage: tkprof tracefile outputfile [explains ] [tables ][prints ] [insert= ] [sys= ] [sorts ] table=имя_схемы.имя таблицы Используйте "имя_схемы.имя_таблицы" внесте с опцией "explains". explain=пользователь/пароль Подключиться к ORACLE и выполнить EXPLAIN PLAIN. print=количество Выдать только указанное "количество" операторов SQL. aggregate=yes|no iпsеrt=имя_файла Выдать в этот файл операторы SQL и данные в операторах INSERT. sys=no He выдавать информацию об операторах SQL, выполненных от имени пользователя SYS. record=имя_файла - Выдать сюда нерекурсивные операторы, имеющиеся в трассировочном файле. sort=опции - Набор из нуля или более следующих опций: prscnt - сколько раз выполнялся разбор, prscpu - процессорное время раэбора, prsela - реальное время раэбора, prsdsk - количество чтений с диска в ходе разбора, prsqry - количество буферов, прочитанных в режиме согласованного чтения в ходе разбора, prscu - количество буферов, непосредственно прочитанных в ходе разбора, prsmis - количество непопаданий в библиотечный кэше в ходе разбора, 128 execnt - сколько раз выполнялся оператор, ехесри - процессорное время выполнения, exeela - реальное время выполнения, exedsk - количество чтений с диска при выполнении, exeqry - количество буферов, прочитанных в режиме согласованного чтения в ходе выполнения, execu - количество буферов, непосредственно прочитанных при выполнении, exerow - количество обработанных при выполнении строк, exemis - количество непопаданий в библиотечный кэш в ходе выполнения, fchcnt - сколько раз выполнялось извлечение данных, fchcpu - процессорное время извлечения данных, fchela - реальное время извлечения данных, fchdsk - количество обращений к диску при извлечении данных, fchqry - количество буферов, прочитанных в режиме согласованного чтения при извлечении данных, fchcu - количество буферов, непосредственно прочитанных при извлечении данных, fchrow - количество извлеченных строк, userid - идентификатор пользователя, разобравшего оператор, Наиболее полезной, является опция sort=. Можно сортировать результаты по разным показателям процессорного и реального времени выполнения, чтобы "наихудшие" запросы оказывались в начале трассировочного файла. Сортировку можно также использовать для поиска запросов, выполняющих слишком много физического ввода/вывода и т.д. Назначение остальных опций очевидно. В 99,9 процентах случаев используется: TKPROF имя_трассировочного_файла имя_файла_отчета и ничего более. При этом операторы SQL выдаются примерно в том порядке, как они посылались серверу в ходе выполнения. Связываемые переменные Для настройки приложения большое значение имеет использование связываемых переменных. Связываемая переменная - это подставляемый параметр запроса. Например, для получения записи доя сотрудника с номером 123, можно выполнить запрос: SELECT * FROM employees WHERE employee_id = 123; Но можно задать и другой запрос: SELECT * FROM employees WHERE employee_id =:employee_id; B обычной системе информацию о сотруднике с номером 123 могут запрашивать всего один раз. В дальнейшем будут запрашивать информацию о сотрудниках с номерами 456, 789 и т.д. При использовании в запросе литералов (констант) каждый запрос является для СУБД абсолютно новым, никогда ранее не выполнявшимся. Его надо разбирать, уточнять (определять объекты, соответствующие именам), проверять права доступа, оптимизировать и т.д. - короче, каждый выполняемый уникальный оператор придется разбирать при каждом выполнении. Во втором запросе используется связываемая переменная, :employee_id, значение которой подставляется в запрос при выполнении. Этот запрос разбирается один раз, а затем 129 план его выполнения запоминается в разделяемом пуле (в библиотечном кэше), из которого его можно выбрать для повторного выполнения. Различие между этими двумя вариантами в плане производительности и масштабируемости - огромное, даже принципиальное. Таким образом, разбор оператора с явными, жестко заданными константами (так называемый жесткий разбор) выполняется дольше и требует намного больше ресурсов, чем повторное использование уже сгенерированного плана запроса (его называют мягким разбором). Менее очевидным может оказаться, насколько постоянный жесткий разбор сокращает количество пользователей, поддерживаемых системой. Отчасти это связано с повышенным потреблением ресурсов, но в гораздо большей степени - с механизмом защелок, используемых в библиотечном кэше. При жестком разборе запроса СУБД будет дольше удерживать определенные низкоуровневые средства обеспечения последовательного доступа, которые называются защелками. Защелки защищают структуры данных в разделяемой памяти сервера Oracle от одновременного изменения двумя сеансами (иначе эти структуры данных Oracle в конечном итоге были бы повреждены) и от чтения этой структуры данных по ходу изменения другим сеансом. Чем чаще и на более продолжительное время на эти структуры данных устанавливаются защелки, тем длиннее становится очередь для установки этих защелок. Точно так же происходит при использовании длинных транзакций в среде MTS, - монополизируются критические ресурсы. Временами машина может казаться минимально загруженной, а СУБД работает очень медленно. Вполне вероятно, что один из сеансов удерживает защелку и формируется очередь в ожидании ее освобождения. В результате работа с максимальной скоростью невозможна. Достаточно одного неверно работающего приложения для существенного снижения производительности всех остальных приложений. Одно небольшое приложение, не использующее связываемые переменные, приводит со временем к удалению из разделяемого пула необходимых SQL-операторов других хорошо настроенных приложений. При использовании связываемых переменных любой сеанс, выдающий тот же самый запрос, будет использовать уже разобранный план выполнения из библиотечного кэша. Это очень эффективно, и именно такую работу пользователей предполагает СУБД. При этом не только используется меньше ресурсов (мягкий разбор требует намного меньше ресурсов), но и защелки удерживаются значительно меньше времени, и нужны гораздо реже. Это повышает производительность и масштабируемость. Чтобы хоть примерно понять, насколько существенно это может сказаться на производительности, достаточно выполнить очень простой тест: ALTER SYSTEM FLUSH SHARED_POOL; Очищаем разделяемый пул. Если потребуется выполнять этот тест многократно, придется очищать разделяемый пул каждый раз, иначе представленный ниже оператор SQL, в котором не используются связываемые переменные, окажется в кэше и будет выполняться очень быстро. DECLARE TYPE rc IS REF CURSOR; l_rc rc; l_dummy all_objects.object_name%TYPE; BEGIN FOR i IN 1 . . 1000 LOOP 130 OPEN l_rc FOR 'SELECT object_name FROM all_objects WHERE object_id = ' || i; FETCH l_rc into l_dummy; CLOSE l_rc; END LOOP; END; В этом коде используется динамический SQL для запроса одной строки из таблицы ALL_OBJECTS. Он генерирует 1000 уникальных запросов со значениями 1, 2, 3, ... и так далее, жестко заданными в конструкции WHERE. Запомним время выполнения. Теперь сделаем то же самое с использованием связываемых переменных: DECLARE TYPE rc IS REF CURSOR; l_rc rc; l_dummy all_objects.object_name%TYPE; BEGIN FOR i IN 1 . . 1000 LOOP OPEN l_rc FOR 'SELECT object_name FROM all_objects WHERE object_id = :x' USING i; FETCH l_rc INTO l_dummy; CLOSE l_re; END LOOP; END; Время выполнения уменьшилось в 10 раз. В этом коде использован точно такой же алгоритм. Единственное изменение - вместо жестко заданных значений 1, 2, 3... и так далее в запросе используется связываемая переменная. Результаты весьма впечатляющи. Код не только выполняется намного быстрее (разбор запросов требовал больше времени, чем их реальное выполнение!), но и позволяет большему количеству пользователей одновременно работать с системой. Выполнение операторов SQL без связываемых переменных во многом подобно перекомпиляции подпрограммы перед каждым вызовом. Существует ряд установок, включая которые на уровне СУБД, можно снизить влияние грубых ошибок программирования. Например, параметр – CURSOR_SHARING=FORCE. Он позволяет включить автоматическое использование связываемых переменных. В результате запрос SELECT * FROM employees WHERE employees_id = 1234 автоматически переписывается в виде SELECT * FROM employees WHERE employees_id =: x. Это может существенно сократить количество жестких разборов и уменьшить ожидание защелок в библиотечном кэше, но (всегда есть но) может также иметь ряд побочных эффектов. Можно нарваться на проблему (или ошибку) при использовании этой возможности: 131 В таблице может быть столбец с весьма неравномерным распределением значений (например, 90 процентов значений в столбце - больше 100, а 10 процентов - меньше 100). Причем лишь 1 процент значений меньше 50. Хотелось бы, чтобы при выполнении запроса: SELECT * FROM t WHERE x < 50; индекс использовался, а при выполнении запроса: SELECT * FROM t WHERE x > 100; не использовался. Если установить параметр CURSOR_SHARING=FORCE, оптимизатор не сможет учесть значения 50 или 100, поэтому будет выбирать план для общего случая, когда индекс, скорее всего не будет использоваться (даже если 99,9 процентов запросов будут содержать конструкцию WHERE x < 50). Кроме того, хотя установка CURSOR_SHARING = FORCE обеспечивает большую скорость работы, чем повторный анализ и оптимизация множества одинаковых запросов как уникальных, это все равно медленнее, чем выполнение запросов, где связываемые переменные используются изначально. Это происходит не из-за неэффективности механизма совместного использования кода курсора, а из-за неэффективности самой программы. Во многих случаях приложение, не использующее связываемые переменные, также не обеспечивает эффективного анализа и повторного использования курсоров. Поскольку в приложении предполагается уникальность каждого запроса (так как для каждого из них создается уникальный оператор), то и курсор в нем не будет использоваться более одного раза. Факт в том, что если программист использует связываемые переменные, то он зачастую также разбирает запрос один раз и затем использует многократно. Именно затраты ресурсов на повторный разбор приводят к наблюдаемому снижению производительности. Итак, важно помнить, что просто добавление параметра инициализации CURSOR_SHARING = FORCE не всегда позволяет решить проблемы. Могут даже возникнуть новые. Во многих случаях параметр CURSOR_SHARING - действительно полезное средство, но это не панацея. Для хорошо продуманного приложения он не нужен. В долгосрочной перспективе обоснованное использование связываемых переменных (и при необходимости - констант) - наиболее правильно. Даже если есть соответствующие параметры, которые можно установить на уровне базы данных, а их пока немного, проблемы одновременного доступа и неэффективных запросов (неудачно сформулированных или вызванных неудачной организацией данных) нельзя решить только установкой параметров сервера. Для решения этих проблем необходимо переписать приложение (а зачастую и изменить его архитектуру). Перенос файлов данных с одного диска на другой, изменение количества блоков, читаемых подряд одной операцией ввода, и другие настройки "на уровне базы данных" часто мало влияют на общую производительность приложения. Они никак не дадут ускорения в 2, 3, ... N раз, необходимого для достижения приемлемой скорости работы приложения. Как часто требуется ускорить работу приложения на 10 процентов? Если надо ускорить работу на 10 процентов, обычно никто вообще не поднимает вопрос об этом. Пользователи начинают жаловаться, когда, по их мнению, скорость надо увеличить раз в пять. Вы не увеличите скорость работы в пять раз за счет переноса файлов данных на другие диски. Это можно сделать только путем изменения приложения, например, сократив объем вода/вывода. Часто, как и в рассматриваемом примере, - переписывание существующего кода так, чтобы использовались связываемые переменные, является единственно возможным выходом. 132 Получаемый в результате код работает на несколько порядков быстрее и во много раз увеличивается количество поддерживаемых системой одновременно работающих пользователей. Для этого, однако, требуется много времени и усилий. Дело не в том, что использовать связываемые переменные сложно или при этом часто делают ошибки, проблема в том, что с самого начала этого не делали, и поэтому пришлось пересмотреть и изменить практически весь код. Разработчикам не пришлось бы платить такую цену, если бы они с первого дня понимали принципиальную важность использования в приложении связываемых переменных. Для следующего теста будут использовать следующие таблицы. Для выполнения этого примера необходим доступ к представлению V$SESSION_EVENT, т.е. наличие привилегии SELECT для представления V$SESSION_EVENT. Кроме того, необходимо установить параметр системы (SYSTEM) или сеанса (SESSION) TIMED_STATISTICS, чтобы получать осмысленные результаты (иначе время выполнения каждого оператора будет равно нулю). Это можно сделать с помощью оператора ALTER SESSION SET TIMED_STATISTICS=TRUE. Начнем с создания глобальной временной таблицы SESS_EVENT, которая будет использоваться сеансом для хранения "предыдущих значений" событий, наступления которых ожидал сеанс. Эта таблица будет использоваться для определения ожидаемых сеансами событий, количества ожиданий и времени ожидания в сотых долях секунды. CREATE GLOBAL TEMPORARY TABLE sess_event ON COMMIT PRESERVE ROWS AS SELECT * FROM v$session_event WHERE 1=0; Теперь создадим "прикладную" таблицу для тестирования: CREATE TABLE t (c1 INT, c2 INT, c3 INT, c4 INT) STORAGE (FREELIST 10); Проверим, что будет происходить при одновременной вставке строк в эту таблицу несколькими пользователями. Наличие нескольких списков свободных мест (freelists) убыстряет одновременную вставку, поэтому соответствующая установка уже включена в оператор создания таблицы. Теперь определим, наступление каких событий будет ожидать наше "приложение". Для этого сделаем копию набора текущих ожидаемых событий сеанса, выполним блок кода, который необходимо проанализировать, а затем вычислим продолжительность ожиданий, имевших место при выполнении этого блока кода: TRUNCATE TABLE sess_event; INSERT INTO sess_event SELECT * FROM v$session_event WHERE sid = (SELECT sid FROM v$mystat WHERE rownum = 1) ; DECLARE l_number NUMBER; BEGIN FOR i IN 1 .. 10000 LOOP l_number := dbms_random.random; EXECUTE IMMEDIATE ' INSERT INTO t VALUES (' || l_number || ',' || l_number || ',' || l_number || ',' || l_number || ')' ; END LOOP; 133 COMMIT; END; SELECT a.event, (a.total_waits-nvl (b.total_waits,0)) total_waits, (a.time_waitednvl(b.time_waited,0)) time_waited FROM (SELECT * FROM v$session_event WHERE sid = (SELECT sid FROM v$mystat WHERE rownum = 1)) a, sess_event b WHERE a.event = b.event (+) AND (a.total waits-nvl (b.total_waits,0)) > 0 Результат: EVENT TOTAL_WAITS TIME_WAITED SQL*Net message from client 4 14 SQL*Net message to client 5 0 log file sync 5 2 Cоздаем уникальный оператор INSERT, который будет выглядеть примерно так: insert into t values (12323, 12323, 12323, 12323); insert into t values (632425, 632425, 632425, 632425); Представленные выше результаты получены в однопользовательском режиме. Если выполнить это одновременно в двух сеансах, увидим примерно такой отчет о времени ожидания: EVENT TOTAL_WAITS TIME_WAITED SQL*Net message from client 4 18 SQL*Net message to client 5 0 Enqueue 2 0 latch free 142 235 log file sync 2 2 Как видите, сеанс много раз ждал освобождения защелки (причем суммарное время ожидания - достаточно большое). Кроме того, наблюдалось ожидание следующих событий: SQL*Net message from client. Сервер ждал, пока клиент пошлет ему сообщение. В данном случае клиент - SQL*Plus. В большинстве случаев ожидание этого события можно игнорировать, но если при выполнении приложения предполагаются длительные раздумья пользователя, это число неизбежно будет большим. В нашем случае сеанс SQL*Plus постоянно выдавал операторы на сервер, поэтому время ожидания должно быть небольшим. Если бы оно было большим, проблема была бы связана с клиентом (узким местом был бы клиент, неспособный достаточно часто обращаться к базе данных). SQL*Net message to client. Сколько времени потребовалось на передачу сообщений с сервера клиенту (SQL*Plus). Enqueue. Ожидание той или иной блокировки. Log file sync. Время ожидания сброса буфера журнала повторного выполнения на диск процессом LGWR при фиксации. latch free. Ожидание события освобождения защелки. Эта защелка предотвращает одновременный доступ к разделяемой области SQL. 134 Стабилизация плана оптимизатора Сервер Oracle позволяет разработчику сохранить набор "подсказок серверу", описывающих, как выполнять определенные SQL-операторы в базе данных. Эта возможность называется стабилизацией плана оптимизатора (Optimizer Plan Stability) и реализуется с помощью хранимого шаблона плана выполнения запроса. Для выполняемого запроса или набора SQL-операторов стабилизация плана оптимизатора позволяет сохранить оптимальный набор подсказок, избавляя от необходимости задавать подсказки в приложении. Это позволяет: • разработать приложение; • протестировать и настроить его запросы; • сохранить соответствующие хорошо настроенные планы выполнения в базе данных для использования оптимизатором в дальнейшем. Стабилизация плана оптимизатора позволяет защититься от многих изменений используемой базы данных. Существенно изменить планы выполнения запросов могут, в частности, следующие типичные изменения базы данных: • повторный анализ таблицы после изменения количества данных; • повторный анализ таблицы после изменения распределения данных; • повторный анализ таблицы с помощью других методов или параметров; • изменение различных параметров в файле init.ora, влияющих на поведение оптимизатора; • добавление индексов; • обновление версии ПО Oracle. Благодаря стабилизации плана запроса, однако, можно сохранить существующие планы выполнения запросов и изолировать приложение от этих изменений. Следует отметить, что в большинстве случаев желательно, чтобы планы выполнения запросов со временем изменялись в ответ на события из приведенного выше списка. Если распределение данных по столбцу существенно изменяется, оптимизатор соответственно изменяет план выполнения запроса. Если добавлен индекс, оптимизатор выявит и использует его, если это даст преимущество. Стабилизацию плана оптимизатора можно использовать для предотвращения подобных изменений в среде, где изменения должны делаться постепенно, после тщательного тестирования. Например, прежде чем разрешать использование индекса, можно последовательно протестировать запросы, для которых он используется, чтобы убедиться, что добавление индекса не повлияет отрицательно на другие компоненты системы. То же самое справедливо и в отношении изменения параметров инициализации или обновления ПО сервера. Стабилизация плана оптимизатора реализуется с помощью подсказок. Подсказки - это не команды и не правила. Хотя механизм подсказок, лежащий в основе стабилизации плана оптимизатора, мощнее, чем в случае обычных подсказок в тексте запроса, оптимизатор может по ходу работы им и не следовать. Это - палка о двух концах. Кажется, что это недостаток, но это - полезное свойство. Если в базе данных сделаны такие изменения, что набор подсказок неприменим (например, удален соответствующий индекс), то сервер Oracle будет игнорировать подсказки и генерировать лучший план из возможных. Продемонстрируем возможности стабилизации плана оптимизатора на примере: 135 Создадим тестовую таблицу: CREATE TABLE emp AS SELECT ename, empno FROM scott.emp GROUP BY ename, empno; ALTER TABLE emp ADD CONSTRAINT emp_pk PRIMARY KEY (empno); Зададим режим оптимизации CHOOSE. ALTER SESSION SET OPTIMIZER_GOAL = CHOOSE; Получим план выполнения запроса: SET AUTRACE ON EXPLAIN SELECT empno, ename FROM emp WHERE empno > 0; Предположим, такой запрос приходит от интерактивного приложения, в котором пользователю желательно получить начальные данные как можно быстрее, и доступ по индексу для этого прекрасно подходит. Нас устраивает этот план выполнения запроса, и желательно, чтобы он использовался всегда, поэтому мы создадим для запроса соответствующий шаблон: CREATE OR REPLACE OUTLINE myoutline FOR CATEGORY mycategory ON SELECT empno, ename FROM emp WHERE empno > 0; Оператор CREATE OR REPLACE OUTLINE создал шаблон запроса и сохранил его в базе данных. Поскольку мы явно создали шаблон, можно задать ему имя (myoutline). Кроме того, мы отнесли этот шаблон запроса к определенной категории (mycategory). Следует отметить, что при выполнении оператора CREATE OUTLINE можно получить сообщение об ошибке: ORA-18005: create any outline privilege is required for this operation Если выдается такое сообщение, необходимо, чтобы администратор базы данных предоставил соответствующему пользователю привилегию CREATE ANY OUTLINE. Давайте теперь изменим базу данных - просто проанализируем таблицу: ANALYZE TABLE emp COMPUTE STATISTICS; Посмотрим, каким теперь будет план выполнения запроса: SET AUTRACE ON EXPLAIN SELECT empno, ename FROM emp WHERE empno > 0; Вместо использования индекса, оптимизатор, срабатывающий благодаря наличию статистической информации, выбирает полный просмотр таблицы. Оптимизатор, основанный на стоимости, выбрал правильный план. В таблице всего 14 строк, и оптимизатор определил, что все они удовлетворяют условию. Однако в нашем приложении все-таки желательно использовать индекс. Чтобы снова использовать предпочтительный план, надо воспользоваться возможностью стабилизации плана оптимизатора. Для этого достаточно выполнить следующую команду: ALTER SESSION SET use_stored_outlines = mycategory Это обеспечивает применение хранимых шаблонов категории mycategory. Если теперь посмотреть план выполнения запроса: SET AUTRACE ON EXPLAIN SELECT empno, ename FROM emp WHERE empno > 0; 136 Оказывается, что снова используется исходный план с доступом но индексу. В этом цель стабилизации плана оптимизатора: "заморозить" планы выполнения запросов для хорошо настроенного приложения. Приложение изолируется от изменений планов оптимизатора, происходящих на уровне базы данных (в результате анализа таблиц, выполненного администратором базы данных, изменения параметров инициализации или обновления версии сервера). Как и большинство средств, стабилизация плана оптимизатора палка о двух концах. То, что внешние изменения не сказываются на приложении, может оказаться как положительным, так и отрицательным. Хорошо это потому, что позволяет добиться предсказуемой производительности в долгосрочной перспективе (поскольку план никогда не изменяется). Однако так можно пропустить новый план, ускоряющий выполнение запроса, и это плохо. С шаблонами запросов связаны два представления, между которыми есть отношение главное/подчиненное. Главное представление - OUTLINES (как обычно, есть три его версии: DBA_, ALL_ и USER_. Подчиненное представление - OUTLINE_HINTS (оно тоже доступно в трех версиях). В представлениях _OUTLINES находятся хранимые шаблоны. В представлении DBA_OUTLINES есть записи для всех хранимых шаблонов в системе, тогда как в представлениях ALL_ и USER_OUTLINES присутствуют только строки, имеющие отношение к текущему пользователю (шаблоны, доступные или созданные пользователем, соответственно). Поскольку представления DBA_OUTLINES и USER_OUTLINES отличаются только одним столбцом (в представлении DBA есть столбец OWNER, содержащий имя схемы, в которой создан шаблон), рассмотрим представление DBA_OUTLINES: • NAME. Имя шаблона, заданное в операторе CREATE OUTLINE (в представленном выше примере использовалось имя MYOUTLINE). Следует заметить, что имя шаблона - уникально (имя шаблона является первичным ключом). Нельзя создать шаблон с одним и тем же именем в двух категориях или у различных пользователей. • OWNER. Схема, в которой создан шаблон. Шаблоны не "принадлежат" никому, так что имя столбца - несколько неправильное. Правильно было бы назвать столбец CREATOR, создатель. • CATEGORY. Категория, к которой отнесена схема (в примере - MYCATEGORY). Шаблоны запросов могут принадлежать к категории, указанной по имени, либо к общей категории DEFAULT, которая используется, если имя категории не задано. В ходе работы пользователь или приложение выполняет оператор ALTER SESSION SET USE_STORED_OUTLINES = <TRUE|имя_категории>, чтобы указать, какой набор хранимых шаблонов надо использовать. При установке значения TRUE будут использоваться шаблоны стандартной категории, DEFAULT. В каждый момент времени может использоваться только одна категория шаблонов. • USED. Этот атрибут показывает, был ли указанный шаблон хоть раз использован. Он будет иметь значение unused до первого использования шаблона для изменения плана выполнения запроса; при этом атрибут получает значение used. • TIMESTAMP. Дата и время создания исходного шаблона. • VERSION. Версия СУБД, в которой был создан исходный шаблон. 137 • SQL_TEXT. Фактический (дословный) SQL-запрос, использованный для генерации шаблона. Этот шаблон может использоваться только для запросов, текст которых полностью совпадает. В представлении _OUTLINE_HINTS находятся реальные подсказки, которые надо применять на разных внутренних стадиях плана выполнения запроса. Сервер по ходу работы переписывает переданный запрос, встраивая эти подсказки в соответствующие места, что и дает необходимый план выполнения. В тексте запроса эти подсказки не появляются, - они добавляются во внутренние структуры плана выполнения запроса. Единственное структурное отличие между представлением DBA_OUTLINE_HINTS, USER_OUTLINE_HINTS и ALL_OUTLINE_HINTS - добавление столбца OWNER, идентифицирующего пользователя, создавшего шаблон. • NAME. Имя хранимого шаблона. Если шаблон создан с помощью оператора CREATE OUTLINE, это будет имя, заданное в операторе. • OWNER. Имя пользователя, создавшего шаблон запроса. • NODE. Запрос или подзапрос, к которому применяется подсказка. Запрос верхнего уровня получает значение 1 в столбце NODE, а последующие подзапросы, встроенные в основной запрос, получают последовательно увеличивающиеся значения. • STAGE. Стадия выполнения, на которой применяются подсказки в ходе обработки запроса. Это число представляет стадию, на которой подсказка будет "вписана" в запрос. Речь идет о внутренних стадиях обработки, выполняемых оптимизатором Oracle, которые обычно пользователям недоступны. • JOIN_POS. Задает таблицу, к которой будет применяться эта подсказка. Для всех подсказок, не задающих метод доступа, в этом столбце будет значение ноль. Для подсказок, задающих метод доступа (например, доступ к таблице по индексу), столбец JOIN_POS задает таблицу. • HINT. Подсказка, которая должна быть встроена в запрос. Есть два способа генерации планов. Один из способов подразумевает использование оператора ЯОД, а второй - установку параметра сеанса. Рассмотрим оба способа и опишем, когда имеет смысл использовать каждый из них. В любом случае, однако, надо убедиться, что пользователь, создающий шаблоны, имеет соответствующие привилегии для создания и управления шаблонами. Создавать и использовать хранимые шаблоны могут пользователи, обладающие следующими четырьмя привилегиями. • CREATE ANY OUTLINE. Позволяет создавать шаблоны в базе данных. При отсутствии этой привилегии будет выдаваться сообщение об ошибке ORA-18005:create any outline privilege is required for this operation. • ALTER ANY OUTLINE. Позволяет изменять (переименовывать, изменять категорию или пересоздавать план) шаблон запроса. • DROP ANY OUTLINE. Позволяет удалять существующий шаблон с указанным именем. • EXECUTE ON OUTLN_PKG. Позволяет выполнять подпрограммы пакета OUTLINE. Обратите внимание, что это привилегии класса ANY. Это означает, что при наличии привилегии CREATE OR REPLACE ANY OUTLINE можно переписать шаблон другого пользователя, не спрашивая у него разрешения. Шаблоны, в отличие от большинства других объектов базы данных не принадлежат никому. У шаблона есть создатель, но нет владельца в обычном смысле. Если можно удалять собственные шаблоны, то можно (ненамеренно) 138 удалить и шаблон любого другого пользователя, поэтому при использовании этих привилегий надо быть внимательным. Для создания хранимых шаблонов использовался оператор следующей структуры: CREATE [OR REPLACE] OUTLINE имя_шаблона [FOR CATEGORY имя_категории] ON оператор_для_которого_сохраняется_шаблон В этом операторе: • Имя_шаблона - имя, присвоенное шаблону. Оно должно иметь смысл для создателя и разработчика приложения. При этом на имя налагаются те же ограничения, что и для любого объекта базы данных (не более 30 символов, начинается с буквы и т.д.). Кроме того, имя_шаблона должно быть уникальным для базы данных, а не для пользователя или категории, как можно было бы подумать, поэтому будьте особенно внимательны при использовании конструкции OR REPLACE, поскольку оператор перезапишет любой существующий шаблон с таким именем. • Имя_категории - имя, используемое для группировки шаблонов. Эта часть оператора CREATE - не обязательная, и если категория не задана, шаблон будет отнесен к категории DEFAULT. Рекомендуется явно указывать имя категории и не использовать категорию DEFAULT. Поскольку сеанс в каждый момент времени может использовать только одну категорию шаблонов, в ней надо сохранить шаблоны планов для всех существенных запросов. • Оператор_для_которого_сохраняется_шаблон - любой допустимый SQL-оператор. Генерация шаблонов с помощью операторов больше всего подходит для приложений, в которых все SQL-операторы хранятся вне приложения. Другими словами, есть файл ресурсов, в котором записаны все потенциально выполняемые SQL-операторы. В этом случае по такому файлу очень легко сгенерировать сценарий с операторами CREATE OUTLINE и выполнить его на сервере. Это гарантирует создание шаблонов для всех запросов (если запросы указаны в этом файле ресурсов). Кроме того, такой подход предохраняет от случайной генерации шаблонов для лишних запросов. Например, если используется триггер ON LOGON, после регистрации в SQL*Plus окажется, что для автоматически выполняемых утилитой SQL*Plus запросов тоже сохранены шаблоны. Кроме того, эти операторы используются, если надо сгенерировать шаблоны только для небольшого количества запросов. Например, этот подход пригоден при использовании шаблонов как средства настройки. Итак, если надо сгенерировать хранимые шаблоны только для небольшой части запросов приложения, это имеет смысл делать с помощью операторов CREATE. Использование оператора ALTER SESSION более универсальный метод генерации шаблонов запросов. Он применяется аналогично установке SQL_TRACE при трассировке программ. С момента выполнения соответствующего оператора ALTER SESSION и до отключения создания хранимых шаблонов для каждого выполняемого запроса будет сохраняться шаблон. Этот метод можно применять для любого приложения, если требуется стабилизировать все планы. Другими словами, когда необходимо точно знать, какими будут планы выполнения SQL-операторов, независимо от версии сервера, на котором будет установлено приложение, независимо от значений параметров инициализации экземпляра и т.п. Чтобы добиться этого для приложения, можно использовать триггер ON LOGON, а затем полностью протестировать приложение, выполнив все возможные запросы. Это надо сделать 139 на тестовом сервере в процессе окончательного тестирования перед поставкой приложения клиентам. После сбора всех планов необходимо извлечь их с помощью утилиты ЕХР, а затем устанавливать с помощью утилиты IMP в процессе инсталляции приложения. Этот метод также используется, если на уровне сервера включена автоматическая подстановка связываемых переменных (auto binding). Синтаксис соответствующих версий оператора ALTER SESSION несложен: ALTER SESSION SET CREATE_STORED_OUTLINES = TRUE; ALTER SESSION SET CREATE_STORED_OUTLINES = FALSE; ALTER SESSION SET CREATE_STORED_OUTLINES = категория_шаблонов; Если параметр CREATE_STORED_OUTLINES получает значение TRUE, сервер Oracle будет генерировать хранимые шаблоны для категории DEFAULT. Категория DEFAULT - самая обычная категория с соответствующим именем; ее использование надо включать так же, как и любой другой категории. После установки параметру CREATE_STORED_OUTLINES значения FALSE сервер Oracle перестанет генерировать хранимые шаблоны для соответствующего сеанса. Если параметру CREATE_STORED_OUTLINES задано значение категория_шаблонов, сервер Oracle будет генерировать шаблоны для всех выполняемых запросов и сохранять их в указанной категории. Именно так предпочтительнее использовать этот метод. Для ясности рекомендуется, чтобы каждое приложение использовало собственную категорию шаблонов, особенно если предполагается его установка в базе данных, с которой работает множество других приложений, тоже использующих стабилизацию плана оптимизатора. Это предотвратит конфликты между приложениями и упростит поиск приложения, которому принадлежит шаблон. Кроме оператора CREATE, для управления шаблонами запросов можно также использовать операторы ALTER и DROP. Оператор ALTER позволяет: • переименовать (RENAME) хранимый шаблон; • пересоздать (REBUILD) план для хранимого шаблона; • изменить (CHANGE) категорию хранимого шаблона; Оператор DROP удаляет указанный по имени хранимый шаблон. Оператор ALTER имеет три версии, и мы рассмотрим их поочередно. Чтобы разобраться, как работает этот оператор, создадим хранимый шаблон, а потом будем изменять его различными способами: CREATE OR REPLACE outline my_outline FOR CATEGORY my_category ON SELECT * FROM all_objects; SELECT name, category, sql_text FROM user_outlines; SELECT count (*) FROM user_outline_hints; Итак, мы работаем с шаблоном MY_OUTLINE в категории MY_CATEGORY, с которым сейчас связано 138 подсказок (результат может быть другим, в зависимости от установок оптимизатора). Прежде всего, оператор ALTER OUTLINE позволяет переименовать хранимый шаблон. Эта версия оператора имеет следующий синтаксис: ALTER OUTLINE имя_шаблона RENAME TO новое_имя Итак, применим этот оператор для переименования шаблона с MY_OUTLINE в PLAN_FOR_ALL_OBJECTS следующим образом: 140 ALTER OUTLINE my_outline RENAME TO plan_for_all_objects; Простой запрос позволяет проверить, сработало ли все, как предполагалось: SELECT name, category, sql text FROM user_outlines; Следующий шаг - изменить с помощью оператора ALTER OUTLINE категорию, в которой хранится шаблон. Эта версия оператора имеет следующий синтаксис: ALTER OUTLINE имя_штаблона CHANGE CATEGORY TO новое_имя_категории; Переведем наш хранимый шаблон из категории MY_CATEGORY в категорию DICTIONARY_PLANS: ALTER OUTLINE plan_for_all_objects CHANGE CATEGORY TO dictionary_plans; SELECT name, category, sql_text FROM user_outlines; Оператор ALTER OUTLINE просто изменяет имя категории в пользовательской схеме OUTLN. Чтобы продемонстрировать последний вариант использования оператора ALTER OUTLINE, пересоздадим план выполнения запроса в текущей среде. Синтаксис оператора в этом случае: ALTER OUTLINE имя_таблона REBUILD; В настоящий момент в используемом сеансе SQL*Plus параметр OPTIMIZER_GOAL имеет значение CHOOSE. Поскольку объекты словаря не проанализированы, для запроса используется оптимизатор, основанный на правилах (если режим оптимизации CHOOSE и объекты в запросе не проанализированы, используется оптимизатор, основанный на правилах). Установим цель оптимизации ALL_ROWS, что требует использовать оптимизатор, основанный на стоимости, и перестроим план. ALTER SESSION SET optimizer_goal = all_rows; ALTER OUTLINE plan_for_all_objects REBUILD; Получив количество подсказок в шаблоне, можно убедиться, что сгенерированный план перестроен и отличается от исходного: SELECT count (*) FROM user_ourline_hints WHERE name = 'PLAN_FOR_ALL_OBJECTS' План, несомненно, отличается: теперь подсказок 139 и запрос оптимизирован в режиме ALL_ROWS, а не CHOOSE. Оператор удаления шаблона имеет следующий синтаксис: DROP OUTLINE имя_игаблона Используем этот оператор для удаления существующего хранимого шаблона: DROP OUTLINE plan_for_all_objects; SELECT * FROM user_outlines; Для работы с шаблонами существует пакет OUTLN_PKG. Этот пакет создавался: • Для поддержки множественных операций с шаблонами, таких как удаление неиспользуемых хранимых шаблонов, удаление шаблонов определенной категории и т.д. Это можно сделать и с помощью операторов ALTER и DROP, но только по одному шаблону. Пакет OUTLN_PKG предлагает набор процедур для работы с несколькими шаблонами одним оператором. 141 • Чтобы предоставить набор процедур для утилит ЕХР и IMP, обеспечивающих экспорт и импорт хранимых шаблонов. Продемонстрируем использование некоторых процедур пакета OUTLN_PKG для множественных операций. Пакет OUTLN_PKG создается сценариями dbmsol.sql и prvtol.plb, которые находятся в каталоге [ORACLE_HOME]/rdbms/admin. Эти сценарии вызываются сценарием catproc.sql (который находится в том же каталоге) и создают пакет в базе данных по умолчанию. Помимо создания пакета OUTLN_PKG, эти сценарии вставляют необходимые строки в соответствующие таблицы словаря данных, чтобы зарегистрировать его функции для использования утилитами EXP/IMP. Пакет должен устанавливаться пользователем SYS. Поскольку пакет автоматически устанавливается при обновлении или установке сервера, выполнять сценарий установки вручную не понадобится. В пакете OUTLN_PKG есть процедуры: • DROP_UNUSED. Удаляет все шаблоны, в столбце USED которых находится значение UNUSED. Это хранимые шаблоны, сгенерированные, но ни разу не использовавшиеся для переписывания запроса. • DROP_BY_CAT. Удаляет все шаблоны указанной категории. Если оказалось, что вся категория хранимых шаблонов больше не нужна, можно удалить их одной командой, а не выполнять оператор DROP OUTLINE для каждого шаблона по очереди. • UPDATE_BY_CAT. Переименовывает категорию глобально, изменяя все входящие в нее шаблоны. Процедура OUTLN_PKG.DROP_UNUSED, не имеющая параметров, удаляет все не использованные шаблоны из всех категорий. Она находит шаблоны, в столбце USED для которых хранится значение UNUSED, и применяет к ним аналог оператора DROP OUTLINE имя_шаблона. Пример использования этой процедуры: EXEC outln_pkg.drop_unused; Поскольку эта процедура работает со всеми категориями, надо ее использовать осторожно. Можно ненамеренно удалить хранимый шаблон, который не следовало удалять. Этим можно свести на нет работу другого пользователя, создавшего шаблоны, но не успевшего их использовать. Процедура DROP_BY_CAT удаляет все хранимые шаблоны указанной категории. Ее можно использовать, например, при тестировании для удаления категорий шаблонов, не оправдавших ожидания. Можно также использовать эту процедуру для удаления категории шаблонов по ходу работы. Это позволяет приложению использовать планы, генерируемые оптимизатором, вместо планов, сохраненных в шаблонах. Пример использования этой процедуры: SELECT category FROM user_outlines; EXEC outln_pkg.drop_by_cat ('DICTIONARY_PLANS'); SELECT category FROM user_outlines; Процедура OUTLN_PKG.UPDATE_BY_CAT позволяет переименовать существующую категорию или объединить категории. Синтаксис вызова этой процедуры: 142 outln_pkg.update_by_cat (старое_имя_категории, новое_имя_категории); Эта процедура работает следующим образом: • Если категории новое_имя_категории в базе данных еще нет, все существующие шаблоны из категории старое_имя_категории переводятся в категорию новое_имя_категории. • Если категория новое_имя_категории существует, все хранимые шаблоны из категории старое_имя_категории переносятся в категорию новое_имя_категории. • Если в столбце SQL_TEXT хранимого шаблона в категории старое_имя_категории хранится текст, совпадающий с текстом одного из шаблонов в категории новое_имя_категории, то шаблон в новую категорию не переносится. Рассмотрим пример, демонстрирующий эту возможность: CREATE outline outline_l FOR CATEGORY cat_1 ON SELECT * FROM dual; CREATE outline outline_2 FOR CATEGORY cat_2 ON SELECT * FROM dual; CREATE outline outline_3 FOR CATEGORY cat_2 ON SELECT * FROM dual A; Итак, имеется три хранимых шаблона в двух категориях. Для запроса SELECT * FROM DUAL есть два хранимых шаблона, а для запроса SELECT * FROM DUAL A - один. Посмотрим, что имеется сейчас: SELECT category, name, sql_text FROM user_outlines ORDER BY category, name: В категории САТ_1 - 1 шаблон, а в категории САТ_2 - 2 шаблона. Более того в категории САТ_2 есть шаблон с таким же значением в столбце SQL_TEXT, что и в шаблоне в категории САТ_1. Теперь объединим категории: EXEC outln_pkg.update_by_cat('CAT_2', 'САТ_1'); SELECT category, name, sql_text FROM user_outlines ORDER BY category, name: Шаблон из категории САТ_2 для запроса, не входящего в категорию САТ_1, был перенесен. Хранимый шаблон для дублирующегося запроса, однако, не перенесен. Дело в том, что все шаблоны должны быть уникальны по столбцу NAME и паре столбцов (CATEGORY, SIGNATURE). В пределах категории значения в столбце SQL_TEXT должны быть уникальны. Это обеспечивается путем генерации уникальной сигнатуры для значения SQL_TEXT. Если необходимо перенести шаблон OUTLINE_2 из категории САТ_2 в категорию САТ_1, придется удалить шаблон OUTLINE_1 из категории САТ_1 перед выполнением процедуры UPDATE_BY_CAT. DROP OUTLINE outline_l; EXEC outln_pkg.update_by_cat (‘CAT_2', 'CAT_1’); SELECT category, name, sql_text FROM user_outlines ORDER BY category, name; 143 Список литературы 1. Томас Кайт. Oracle для профессионалов. Книга 1-2. Архитектура и основные особенности. - К: DiaSoft, 2005. - С.1- 652. 2-806. - ISBN 5-93772-072-5 2. Сэм Р. Алапати. Oracle 11g: руководство администратора баз данных. - М.: Вильямс, 2009. С. 1341. - ISBN 978-5-8459-1592-4 3. Рик Гринвальд, Роберт Стаковьяк, Гэри Додж, Дэвид Кляйн, Бен Шапиро, Кристофер Дж. Челья. Программирование баз данных Oracle для профессионалов. - М.: Диалектика, 2007. - С. 784. - ISBN 0-7645-7482-5 144