Oracle для професіоналів. Глава 1. Частина 2, Інші СУБД, Бази даних, статті

Частина 1

Отже, в нашому прикладі Oracle отримує результат наступним чином:








































Час

Запит

Транзакція з перекладу з рахунку на рахунок

T1 Читає рядок 1, sum отримує значення 500 $
T2 Змінює рядок 1, встановлює виключну блокування на рядок 1, запобігаючи інші зміни. У рядку 1 тепер зберігається значення 100 $
T3 Читає рядок 2, sum отримує значення 750 $
T4 Читає рядок 3, sum отримує значення 1150 $
T5 Змінює рядок 4, встановлює виключну блокування на рядок 4, запобігаючи інші зміни (але не читання). У рядку 4 тепер зберігається значення 500 $.
T6 Читає рядок 4, визначає, що вона була змінена. Виконується відкат блоку до того стану, який він мав на момент часу T1. Запит потім прочитає значення 100 $ з цього блоку
T7 Транзакція фіксується
T8 Видає 1250 $ в якості результату підсумовування

У момент часу T6 СУБД Oracle фактично “читає поверх” блокування, встановленої транзакцією на рядку 4. Саме так реалізується неблокіруемой читання: СУБД Oracle просто перевіряє, чи змінилися дані, ігноруючи той факт, що вони в даний момент заблоковані (тобто виразно змінені). Вона витягне старе значення з сегменту відкоту і перейде до наступного блоку даних.

Це ще одна переконлива демонстрація Багатоверсійність: в базі даних є декілька версій однієї і тієї ж інформації, станом на різні моменти часу. СУБД Oracle використовує ці зроблені в різний час “моментальні знімки” даних для підтримки узгодженості з читання та неблокіруемой запитів.

Це узгоджене з читання уявлення даних завжди виконується на рівні оператора SQL, – результати виконання будь-якого оператора SQL завжди узгоджені на момент його початку. Саме ця властивість дозволяє одержувати передбачуваний набір даних в результаті, наприклад, таких вставок:

for x in (select * from t)
loop
insert into t values (x.username, x.user_id, x.created);
end loop;

Результат виконання оператора SELECT * FROM T зумовлений в момент початку виконання запиту. Оператор SELECT не буде “бачити” нових даних, що генеруються операторами INSERT. Уявіть собі, що було б в іншому випадку: оператор перетворився б на нескінченний цикл. Якби в міру генерації оператором INSERT додаткових рядків у таблиці T, Оператор SELECT міг “бачити” ці вставляються рядки, представлений вище фрагмент коду створив би невідома кількість рядків. Якби в таблиці T спочатку було 10 рядків, в результаті могло б вийти 20, 21, 23 або нескінченна кількість рядків. Точно передбачити результат було б неможливо. Узгодженість з читання забезпечується для всіх операторів, так що оператори INSERT, На кшталт представленого нижче, теж працюють передбачувано:

insert into t select * from t;

Оператор INSERT отримає узгоджене з читання уявлення таблиці T – Він не “побачить” рядки, які сам же тільки що вставив, і буде вставляти тільки рядки, що існували на момент початку його виконання. У багатьох СУБД подібні рекурсивні оператори просто не дозволені, оскільки вони не можуть визначити, скільки рядків взагалі буде вставлено.

Тому якщо ви звикли до реалізації узгодженості і одночасності запитів в інших СУБД або просто ніколи не стикалися з такими поняттями (не маєте реального досвіду роботи з СУБД), то тепер розумієте, наскільки важливо для вашої роботи їх розуміння. Щоб максимально використовувати потенційні можливості СУБД Oracle, необхідно розуміти ці проблеми і способи їх вирішення саме в Oracle, а не в інших СУБД.

Незалежність від СУБД?


Ви, напевно, вже зрозуміли напрямок моєї думки. Я посилався на інші СУБД і описував відмінності реалізації одних і тих же можливостей в кожній з них. Я переконаний: за винятком деяких додатків, виключно читаючих з бази даних, створити повністю незалежну від СУБД і при цьому масштабоване додаток вкрай складно і навіть практично неможливо, не знаючи особливостей роботи всіх СУБД.

Наприклад, давайте повернемося до першого прикладу планувальника ресурсів (до додавання конструкції FOR UPDATE). Припустимо, цей додаток було розроблено на СУБД з моделлю блокування / забезпечення одночасного доступу, повністю відрізняється від прийнятої в Oracle. Я збираюся продемонструвати, що при перекладі додатки з однієї СУБД на іншу необхідно перевіряти, чи працює воно коректно в новому середовищі.

Припустимо, що спочатку додаток з планування ресурсів працювало в СУБД, що використовує блокування на рівні сторінок і блокування читання (читання блокується при зміні зчитувальних даних), і для таблиці SCHEDULES був створений індекс:

create index schedules_idx on schedules(resource_name, start_time);

Припустимо також, що бізнес-правило було реалізовано за допомогою тригера (після виконання оператора INSERT, Але перед фіксацією транзакції ми перевіряємо, що для зазначеного тимчасового інтервалу в базі даних є тільки наша, тільки що вставлена ​​рядок). В системі з блокуванням на рівні сторінок, через зміни сторінки індексу за стовпцями RESOURCE_NAME і START_TIME, Дуже ймовірно, що транзакції будуть виконуватися строго послідовно. Система буде виконувати вставки по черзі, оскільки сторінка індексу блокується (усі близькі значення по полю START_TIME для одного ресурсу RESOURCE_NAME будуть знаходитися на тій же сторінці). У такій СУБД з блокуванням на рівні сторінок наш додаток, ймовірно, працюватиме нормально, так як перекриття виділених ресурсів буде перевірятися послідовно, а не одночасно.

Якщо просто перенести це додаток в СУБД Oracle, виходячи з припущення, що вона працює точно так само, можна отримати шок. В СУБД Oracle, що виконує блокування на рівні рядків і не блокує читання, воно виявиться некоректним. Як уже було показано, необхідно використовувати конструкцію FOR UPDATE для впорядкування доступу. Без цієї конструкції два користувачі можуть зарезервувати ресурс на один і той же час. Це буде прямим наслідком нерозуміння особливостей роботи використовуваної СУБД в багатокористувацької середовищі.

З подібними проблемами я стикався багато разів при перенесенні додатків з СУБД А в СУБД Б. Коли додаток, без проблем працювало в СУБД А, не працює або працює досить дивно в СУБД Б, відразу ж виникає думка, що “СУБД Б – погана”. Правда, однак, в тому, що СУБД Б працює інакше. Жодна з СУБД не помиляється і не є “поганий” – вони просто різні. Знання і розуміння особливостей їх роботи допоможе успішно вирішити подібні проблеми.

Зовсім недавно я допомагав перевести код з мови Transact SQL (мова створення збережених процедур для СУБД SQL Server) на PL / SQL. Розробник, який займався перекладом, скаржився, що SQL-запити в Oracle повертають “Неправильний” відповідь. Запити виглядали наступним чином:

declare
l_some_variable varchar2(25);
begin
if ( some_condition )
then
l_some_variable := f( … );
end if;
for x in ( select * from T where x = l_some_variable )
loop


Метою є отримання всіх рядків таблиці T, Які в стовпці X мають пусте значення, якщо деякий умова не виконана, або певне значення, якщо ця умова виконана.

Суть скарги полягала в тому, що, в Oracle цей запит не повертав даних, якщо змінна L_SOME_VARIABLE не отримувала значення явно (коли у неї залишалося значення NULL). В СУБД Sybase або SQL Server все було не так – запит знаходив рядки з невизначеним (NULL) Значенням у стовпці X. Я зустрічався з цим практично при будь-якому перекладі додатки з СУБД Sybase або SQL Server на Oracle. Мова SQL передбачає використання тризначної логіки, і СУБД Oracle реалізує невизначені значення так, як того вимагає стандарт ANSI SQL. За цими правилами порівняння стовпця X зі значенням NULL не дає ні True, Ні False – Результат фактично невідомий. Наступний приклад показує, що я маю на увазі:

ops$tkyte@ORA8I.WORLD> select * from dual;
D

X
ops$tkyte@ORA8I.WORLD> select * from dual where null=null;
no rows selected
ops$tkyte@ORA8I.WORLD> select * from dual where null<>null;
no rows selected

У перший раз це може здатися дивним: в Oracle NULL не дорівнює і не не дорівнює NULL. СУБД SQL Server за замовчуванням веде себе не так: в SQL Server і Sybase NULL дорівнює NULL. Ні Oracle, ні Sybase, ні SQL Server не виконує оператори SQL неправильно – вони просто роблять це по-різному. Всі ці СУБД нібито відповідають стандарту ANSI, але все одно працюють по-різному. Є неоднозначності, проблеми сумісності з колишніми версіями і так далі, які необхідно вирішувати. Наприклад, СУБД SQL Server підтримує метод порівняння зі значенням NULL, що диктуються стандартом ANSI, але не за замовчуванням (Це порушило б роботу тисяч вже існуючих програм, створених для цієї СУБД).

Одним з рішень проблеми могло бути переформулювання запиту наступним чином:

select *
from t
where ( x = l_some_variable OR
(x is null and l_some_variable is NULL ))

Однак це призвело б до ще однієї проблеми. В СУБД SQL Server при виконанні цього запиту використовувався б індекс за стовпцем X. В СУБД Oracle індекс на основі B *-дерева (докладніше про методи індексування читайте в розділі 7) не дозволяє індексувати значення ключа NULL. Тому, якщо необхідно знайти невизначені значення, індекси на основі B *-дерев не сильно допоможуть.

В даному випадку, щоб звести до мінімуму зміни в коді, стовпцю X присвоювалося значення, якого не могло бути в реальних даних. Так, X, За визначенням, був числом позитивним, тому було вибрано значення -1. Запит придбав такий вигляд:

select * from t where nvl(x,-1) = nvl(l_some_variable,-1)

Ми створили індекс по функції:

create index t_idx on t( nvl(x,-1) );

З мінімальними змінами ми добилися того ж результату. Звідси можна зробити наступні важливі висновки.


Розробники часто запитують мене, як зробити в СУБД щось конкретне. Наприклад, мене запитують: “Як створити тимчасову таблицю в збереженій процедурі?”. На такі питання я не даю прямої відповіді – я завжди відповідаю питанням: “А для чого вам це потрібно?”. Неодноразово у відповідь я чув: “Ми створювали тимчасові таблиці в збережених процедурах в SQL Server, і тепер нам треба це зробити в Oracle”. Саме це я і припускав почути. В такому випадку моя відповідь проста: “Ви помиляєтесь, думаючи, що треба створювати тимчасові таблиці в збереженій процедурі в Oracle”. Насправді в СУБД Oracle це буде вкрай невдалим рішенням. При створенні таблиць в збережених процедурах в Oracle незабаром виявиться, що:


Отже, не треба робити в точності так, як в SQL Server (якщо тимчасова таблиця в Oracle взагалі знадобиться). Робити випливає те, що є найбільш оптимальним для Oracle. При зворотному переході з Oracle в SQL Server теж не варто створювати одну велику таблицю з тимчасовими даними для всіх користувачів (як це робиться в Oracle). Це призведе до зниження масштабованості і можливостей одночасного доступу в даній СУБД. Кожна СУБД має істотні відмінності.

Вплив стандартів


Якщо все СУБД відповідають стандарту SQL92, вони повинні бути однакові. Так вважають багато хто. Зараз я розвію цей міф.

SQL92 – це стандарт ANSI / ISO для СУБД. Він є розвитком стандарту ANSI / ISO SQL89. Цей стандарт задає мову (SQL) і поведінка (транзакції, рівні ізоляції і т.д.) для СУБД. Чи знаєте ви, що багато комерційні СУБД відповідають стандарту SQL92? А чи знаєте, як трохи це означає для переносимості запитів та програм?

Починаючи читати стандарт SQL92, виявляєш, що він має чотири рівні.


У стандарт початкового рівня не входять такі конструкції, як зовнішні з’єднання, новий синтаксис для внутрішніх з’єднань і т.д. Перехідний рівень вимагає підтримки відповідного синтаксису зовнішнього та внутрішнього сполучення. Проміжний рівень додає нові можливості, а повний і являє собою, власне, SQL92. У більшості книг по SQL92 не розрізняються ці рівні підтримки, що збиває з пантелику. У них демонструється, як повинна працювати “ідеальна” СУБД, повністю реалізує стандарт SQL92. В результаті не можна взяти книгу по SQL92 і застосувати представлені в ній прийоми до СУБД, відповідної стандарту SQL92. Наприклад, в СУБД SQL Server пропонований стандартом синтаксис “внутрішнього сполучення” в SQL-операторах підтримується, а в СУБД Oracle – ні. Але обидві ці СУБД відповідають стандарту SQL92. В СУБД Oracle можна виконувати зовнішні і внутрішні з’єднання, але робити це треба не так, як в SQL Server. В результаті початковий рівень стандарту SQL92 мало що дає, а при використанні коштів більше високих рівнів можливі проблеми при переносі на іншу СУБД.

Не треба боятися використовувати специфічні засоби конкретної СУБД, – за них заплачено немало грошей. У кожній СУБД є свій набір унікальних можливостей, і в будь СУБД можна знайти спосіб виконати необхідне дію. Використовуйте в поточній СУБД краще і реалізуйте нові компоненти при переході на інші СУБД. Використовуйте відповідні прийоми програмування, максимально ізолюючі іншу частину програми від цих змін. Ці ж прийоми програмування застосовуються розробниками переносимих програм, що підтримують кілька ОС. Мета в тому, щоб повною мірою використати наявні кошти, але при цьому мати можливість змінювати реалізацію в кожному конкретному випадку.

Наприклад, типова функція багатьох додатків баз даних – генерація унікального ключа для кожного рядка. При вставці рядка система повинна автоматично згенерувати ключ. В Oracle для цього пропонується об’єкт бази даних – послідовність (SEQUENCE). В Informix є тип даних SERIAL. Sybase і SQL Server підтримують тип даних IDENTITY. У кожній СУБД є спосіб вирішити цю задачу. Однак методи вирішення різні, різні і можливі наслідки їх застосування. Тому що знає розробник може вибрати один з двох варіантів:


Теоретичне перевагу першого підходу полягає в тому, що при переході з одного СУБД на іншу нічого міняти не доведеться. Я назвав цю перевагу “теоретичним”, оскільки недоліки такого рішення настільки великі, що роблять його практично неприйнятним. Для створення повністю незалежної від СУБД процесу доведеться створити таблицю види:

create table id_table (id_name varchar(30), id_value number);
insert into id_table values (“MY_KEY”, 0);

Потім для отримання нового ключа необхідно виконати наступний код:

update id_table set id_value = id_value + 1 where id_name = “MY_KEY”;
select id_value from id_table where id_name = “MY_KEY”;

Виглядає він дуже просто, але виконувати подібну транзакцію в кожен момент часу може тільки один користувач. Необхідно змінити відповідний рядок, щоб збільшити значення лічильника, а це призведе до почергового виконання операцій. Не більше одного сеансу в кожен момент часу буде генерувати нове значення ключа. Проблема ускладнюється тим, що реальні транзакції набагато більше транзакції, показаної вище. Показані в прикладі оператори UPDATE і SELECT – Лише два з багатьох операторів, що входять в транзакцію. Необхідно ще вставити в таблицю рядок із щойно згенерованих ключем і виконати необхідні дії для завершення транзакції. Це впорядкування доступу буде величезним обмежуючим фактором для масштабування. Подумайте про наслідки, якщо цей метод застосувати для генерації номерів замовлень в додатку для обробки замовлень на Web-сайті. Одночасна робота декількох користувачів стане неможливою, – замовлення будуть оброблятися послідовно.

Правильне рішення цієї проблеми полягає у використанні для кожної СУБД відповідного коду. В Oracle (передбачається, що унікальний ключ необхідно генерувати для таблиці T) Кращим способом буде:

create table t ( pk number primary key, … );
create sequence t_seq;
create trigger t_trigger before insert on t for each row
begin
select t_seq.nextval into :new.pk from dual;
end;

В результаті кожна вставляється рядок автоматично і непомітно для програми отримає унікальний ключ. Той же ефект можна отримати і в інших СУБД за допомогою їх типів даних – синтаксис оператора створення таблиці зміниться, а результат буде тим же. Ми використовували другий варіант – специфічні засоби кожної СУБД для неблокіруемой, високопараллельних генерації унікального ключа, що, однак, не зажадало реальних змін в коді програми – всі необхідні дії виконані операторами DDL.

Наведу ще один приклад безпечного програмування, що забезпечує переносимість. Якщо зрозуміло, що кожна СУБД реалізує одні й ті ж можливості по-різному, можна при необхідності створити додатковий рівень доступу до бази даних. Припустимо, ви програмуєте з використанням інтерфейсу JDBC. Якщо використовуються тільки прості оператори SQL, SELECT, INSERT, UPDATE і DELETE, Додатковий рівень абстракції швидше за все не потрібен. Можна включати код SQL безпосередньо в додаток, якщо використовувати конструкції, підтримувані у всіх СУБД, з якими має працювати додаток. Інший підхід, одночасно спрощує перенесення і підвищує продуктивність, полягає у використанні збережених процедур, які повертають результуючі безлічі. Якщо розібратися, виявиться, що все СУБД можуть повертати результуючі безлічі з збережених процедур, але способи при цьому використовуються абсолютно різні. Для кожної СУБД доведеться написати свій вихідний код.

Тепер з’являється вибір – або не використовувати збережені процедури, які повертають результуючі безлічі, або писати окремий вихідний код для кожної СУБД. Я, безперечно, обрав би метод “окремий код для кожної СУБД “і активно використовував би збережені процедури. Здавалося б, що при цьому для переходу на іншу СУБД потрібно більше часу. Однак виявляється, що цей підхід спрощує створення додатків, переносите на різні СУБД. Замість пошуків ідеального коду SQL, що працює у всіх СУБД (причому, як правило, в одних краще, а в інших – гірше), використовується код SQL, максимально ефективний в конкретній СУБД. Його можна винести з програми, що дає додаткові можливості налаштування. Можна виправити запит з низькою продуктивністю безпосередньо в СУБД, і ця зміна буде негайно враховано, без виправлень у додатку. Крім того, при використанні цього методу можна вільно і в повному обсязі використовувати переваги пропонованих виробником СУБД розширень мови SQL. Наприклад, СУБД Oracle підтримує ієрархічні запити за допомогою конструкції CONNECT BY в операторах SQL. Ця унікальна можливість дуже допоможе при створенні рекурсивних запитів. В Oracle ви вільно зможете використовувати це розширення SQL, оскільки воно – “поза” додатка (приховано в базі даних). В інших СУБД для досягнення аналогічних результатів, можливо, доведеться використовувати тимчасові таблиці і збережені процедури. Ви заплатили за ці можливості, так чому ж їх не використовувати.

Такі ж методи використовують розробники, створюючи код, призначений для роботи на безлічі платформ. Корпорація Oracle, наприклад, застосовує описану вище методику при розробці СУБД. Є великий фрагмент коду (що становить, однак, невелику частину всього коду СУБД), який називається OSD-код (Operating System Dependent) і створюється окремо для кожної платформи. За допомогою цього рівня абстракції в СУБД Oracle можна використовувати специфічні можливості ОС для забезпечення високої продуктивності і інтеграції, не переписуючи при цьому код самої СУБД. Саме завдяки цьому СУБД Oracle може працювати як многопотоковой додаток в Windows і як многопроцессного – в UNIX. Механізми взаємодії між процесами абстраговані до такого рівня, що можуть втілюватися по-різному для кожної ОС; при цьому забезпечується така ж продуктивність, як і в додатках, написаних спеціально для даної платформи.

Крім синтаксичних відмінностей в мові SQL, розрізняються реалізації операторів, різної буде і продуктивність виконання одного і того ж запиту, є проблеми управління одночасним доступом, рівнів ізольованості транзакцій, узгодженості запитів і т.д. Все це більш детально буде розглянуто в розділах 3 і 4, – ми побачимо, як позначаються ці відмінності. У стандарті SQL92 спробували дати чіткі визначення того, як повинна виконуватися транзакція, як повинні забезпечуватися рівні ізоляції, але в кінцевому підсумку в різних СУБД результати виходять різними. Все це пов’язано з реалізацією. В одній СУБД додаток буде викликати взаємні блокування і заблокує все, що можна. В іншій СУБД це ж програма не викликає жодних проблем і працює відмінно. В одній СУБД блокування (фізично впорядкує обігу) навмисно використовувалося в додатку, а при його перенесенні в іншу СУБД, де блокування немає, виходить невірна відповідь. Щоб перенести готове додаток в іншу СУБД, потрібно багато праці і зусиль, навіть якщо при первісній розробці неухильно дотримувався стандарт.

Можливості та функції


Противники обов’язкового забезпечення “незалежності від СУБД” наводять наступний аргумент: треба добре розуміти, що саме пропонує конкретна СУБД, і повністю використовувати її можливості. У цьому розділі не описуються всі унікальні можливості Oracle 8i, – для цього знадобилася б окрема велика книга. Новим можливостям СУБД Oracle 8i присвячена спеціальна книга в наборі документації по СУБД Oracle. Якщо врахувати, що разом з СУБД Oracle поставляється документація загальним обсягом близько 10000 сторінок, детальний розгляд кожної можливості і функції практично нереально. У цьому розділі просто показано, чому навіть поверхневе уявлення про наявні можливості дає величезні переваги.

Як вже було сказано, я відповідаю на питання про СУБД Oracle на Web-сайті. Якщо чесно, відсотків 80 моїх відповідей – посилання (URL) на документацію. Мене запитують, як реалізувати ті чи інші складні функціональні можливості в базі даних (або поза нею). А я просто даю посилання на відповідне місце в документації, де написано, як це вже реалізовано в СУБД Oracle і як цими можливостями користуватися. Часто такі випадки бувають з реплікацією. Я отримую запитання: “Хотілося б зберігати копію даних в іншому місці. Ця копія повинна бути доступна тільки для читання. Оновлення повинно виконуватися раз на добу, опівночі. Як написати відповідний код? “. Відповідь проста: див. опис команди CREATE SNAPSHOT. Ось що таке вбудовані можливості СУБД.

Можна, звичайно, для інтересу написати власний механізм реплікації, але це буде не найрозумніше дію. СУБД робить багато чого і, як правило, краще, ніж створювані нами програми. Реплікація, наприклад, вбудована в ядро, написане на мові C. Вона працює швидко, порівняно проста у використанні і надійна. Працює в різних версіях, на різних платформах. При виникненні проблем служба підтримки Oracle допоможе їх вирішити. Після оновлення версії реплікація буде підтримуватися з новими, додатковими можливостями. Тепер припустимо, що ви розробили власний механізм реплікації. Вам доведеться зайнятися його підтримкою у всіх версіях СУБД, які ви збираєтеся підтримувати. Однакове функціонування у версії 7.3, 8.0, 8.1 і 9.0 і так далі ви повинні будете забезпечувати самі. Якщо відбудеться збій, звертатися буде не до кого. Принаймні, поки не вдасться отримати маленький тестовий приклад, що демонструє основну проблему. При виході нової версії вам доведеться самостійно переносити в неї код механізму реплікації.

Недостатнє розуміння того, що пропонує СУБД, може серйозно перешкодити в майбутньому. Нещодавно розробники демонстрували мені створене ними “дуже потрібний” програмне забезпечення. Це була система обміну повідомленнями, вирішувала проблему черг в базі даних. Вона зазвичай виникає при необхідності використання таблиці декількома сеансами в якості “черги”. Необхідно, щоб декілька користувачів могли заблокувати чергову запис, пропустивши всі вже заблоковані записи (вони вже обробляються). Проблема в тому, що немає задокументованої можливості СУБД для пропуску заблокованих рядків. Тому, не знаючи про існування коштів, наданих СУБД Oracle, можна взятися за реалізацію підтримки черг самостійно (або придбати готове рішення).

Саме це і зробила згадана група розробників. Вони створили набір процесів і придумали функціональний інтерфейс для організації черг повідомлень в СУБД. Вони витратили на це чимало часу і сил і були впевнені, що зробили щось дійсно унікальне. Коли я побачив систему в дії і дізнався її функціональні можливості, мені залишилося сказати лише одне: це аналог розширеної підтримки черг, Advanced Queues. Ця можливість давно вбудована в СУБД. Вона вирішує завдання “отримати першу незаблокованих запис в черзі і заблокувати її”. Все, що потрібно, вже зроблено. Розробники, не знаючи про існування такої можливості, витратили на її реалізацію багато часу і сил. Крім того, їм доведеться витрачати чимало часу і на її підтримку в подальшому. Їх керівник не дуже зрадів, дізнавшись, що замість унікального програмного забезпечення вийшла емуляція вбудованої можливості СУБД.

Я бачив, як розробники в СУБД Oracle 8i створювали процеси-демони, які читають повідомлення з програмних каналів (це механізм взаємодії між процесами в СУБД). Процеси-демони виконували оператори SQL, містилися в прочитаних з програмного каналу повідомленнях, і фіксували зроблене. Це робилося для перевірки транзакцій, щоб записи перевірки відкочувалися при відкат основний транзакції. Зазвичай якщо для перевірки доступу до даних використовувалися тригери і основний оператор згодом виконати не вдавалося, всі зміни відкочувалися (див. главу 4, де неподільність операторів розглядається більше детально). Посилаючи ж повідомлення іншому процесу, можна записувати інформацію в іншій транзакції і фіксувати її незалежно. Запис перевірки при цьому залишалася, навіть якщо основна транзакція відкочувалася. У версіях Oracle до Oracle 8i це був прийнятний (і практично єдиний) спосіб реалізації описаної функції. Коли я розповів розробникам про автономні транзакції, які підтримуються СУБД (ми їх детально розглянемо в главі 15), вони дуже засмутилися. Автономні транзакції, реалізовані додаванням єдиного рядка коду, робили те саме, що вся їх система. Позитивним моментом виявилося те, що можна було викинути істотну частину коду і не підтримувати його надалі. Крім того, система запрацювала швидше і стала простіше для розуміння. Але їх це все одно мало тішило, – дуже вже багато часу було витрачено на винахід велосипеда. Особливо засмутився творець процесів-демонів, плоди праць якого були відправлені в сміттєву корзину.

З подібними випадками я стикаюся постійно: витрачаються величезні зусилля на вирішення проблем, уже давно вирішених в самій СУБД. Якщо ви не витратите час на вивчення того, що пропонується, рано чи пізно будете покарані, винаходячи велосипед. У другій частині книги, “Структури і утиліти бази даних”, ми детально розглянемо окремі функціональні можливості, пропоновані СУБД. Я вибрав ті можливості і функції, які часто використовують розробники або які варто було б використовувати набагато частіше. Описана буде, однак, лише вершина айсберга. В СУБД Oracle набагато більше коштів і можливостей, ніж можна описати в одній книзі.

Вирішуйте проблеми просто


Завжди є два способи вирішення будь-якої проблеми: простий і складний. Але люди чомусь завжди вибирають складний. Це не завжди робиться навмисно, частіше – через незнання. Розробники просто не припускають, що СУБД може робити “це”. Я ж припускаю, що СУБД може робити все, і пишу щось власноруч, тільки якщо виявляється, що цього вона не робить.

Наприклад, мене часто запитують: “Як зробити, щоб користувач міг підключитися до бази даних тільки один раз?”. (Є ще сотня прикладів, які я міг би тут навести як ілюстрації.) Напевно, це вимога багатьох додатків; правда, в моїй практиці розробки такі програми не зустрічалися – я не бачу вагомої причини для того, щоб обмежувати користувачів подібним чином. Проте іншим розробникам це потрібно, і вони зазвичай придумують складне рішення. Наприклад, створюють пакетне завдання, що виконується операційною системою і переглядали виставу V $ SESSION, а потім довільно припиняє сеанси користувачів, які підключилися до бази даних більше одного разу. Або створюють власні таблиці, в які додаток вставляє рядок при реєстрації користувача і видаляє її по завершенні роботи. Подібна реалізація неминуче призводить до численних звернень в службу підтримки, оскільки якщо додаток завершує роботу нештатно, рядок з цієї таблиці не видаляються. Я бачив ще багато “творчих” способів добитися цього, але жоден з них не був таким простим:

ops$tkyte@ORA8I.WORLD> create profile one_session limit sessions_per_user 1;
Profile created.
ops$tkyte@ORA8I.WORLD> alter user scott profile one_session;
User altered.
ops$tkyte@ORA8I.WORLD> alter system set resource_limit=true;
System altered.

От і все. Тепер будь-який користувач з профілем ONE_SESSION може підключитися тільки один раз. Простота цього рішення зазвичай призводить розробників в захват і запізнілим жалю. Витратьте час на ознайомлення з наявними засобами і їх можливостями – це дозволить заощадити багато часу і сил при розробці.

Той же принцип “роби простіше” застосовується і на більш високому, архітектурному рівні. Я рекомендую подумати двічі, перш ніж братися за складні реалізації. Чим більше “рухомих частин” в системі, тим більше компонентів, які можуть працювати не так, а при використанні складної архітектури визначити, що саме є причиною помилки, буде непросто. Може бути, використання “надцатіуровневой” архітектури – Це дійсно “круто”, але позбавлене сенсу, якщо в простій збереженої процедури можна зробити те ж саме, але краще, швидше і з використанням менших ресурсів.

Я брав участь в розробці програми, яка триває більше року. Це було Web-додаток, що використовується в масштабі компанії. Клієнт на базі HTML і з використанням технології JSP динамічно отримував сторінки з сервера проміжного рівня, який взаємодіяв з CORBA-об’єктами, в свою чергу, зверталися до СУБД. CORBA-об’єкти повинні були підтримувати “стан” і підключатися до СУБД для організації сеансу. В ході тестування цієї системи виявилося, що потрібно багато серверів додатків і дуже потужна машина для роботи СУБД, щоб поддержівать10000, як передбачалося, одночасно працюючих користувачів. Більше того, іноді виникала проблема нестабільності, пов’язана із складністю взаємодії компонентів (відповісти на запитання, де саме і чому сталася помилка в цій складній системі, було важко). Система масштабувати, але вимагала при цьому величезних ресурсів. Крім того, оскільки для реалізації використовувалося багато складних технологій, для розробки та супроводу системи було потрібно багато досвідчених програмістів. Ми розібралися в цій системі і її передбачуваних функціях і зрозуміли, що архітектура її трохи складніше, ніж необхідно для вирішення поставлених завдань. Ми побачили, що з допомогою модуля PL / SQL сервера додатків Oracle iAS і ряду процедур можна було зробити таку ж систему, що працює на істотно менш потужному обладнанні, причому зусиллями менш досвідчених розробників. Ніяких компонентів EJB, ніяких складних взаємодій між сторінками JSP і компонентами EJB – звичайне перетворення вказаної адреси URL у виклик збереженої процедури. Ця нова система працює і використовується до сих пір, підтримує більше користувачів, ніж передбачалося, і працює так швидко, що часом не віриться. Вона використовує найпростішу архітектуру, мінімум компонентів, працює на дешевому 4-процесорному сервері рівня робочих груп і ніколи не дає збоїв (ну, один раз табличний простір переповнилося, але це вже інша проблема).

Для вирішення завдання я завжди віддаю перевагу найбільш просту архітектуру. Результат часто виходить приголомшливий. Для кожної технології є відповідні інструменти – не завжди треба просто цвях забити, так що може знадобитися щось окрім молотка …

Відкритість


Є ще одна причина, чому при розробці часто вибирається складний спосіб вирішення проблеми, – склалося уявлення, що треба жертвувати всім заради “відкритості” і “незалежності від СУБД”. Розробники хочуть уникнути використання “закритих”, “специфічних” можливостей СУБД – іноді навіть таких простих, як збережені процедури або послідовності, оскільки це прив’яже їх до певної СУБД. Я наполягаю на те, що якщо створюється додаток, читає і змінює дані, воно вже в деякій мірі прив’язана до СУБД. Коли запити почнуть виконуватися одночасно зі змінами, ви відразу виявите невеликі (А іноді – і великі) відмінності в роботі СУБД. Наприклад, в одній СУБД може виявитися, що оператор SELECT COUNT(*) FROM T вступає у взаємну блокування з простим зміною двох рядків. В Oracle же запит SELECT COUNT(*) ніколи не блокує інші сеанси. Ми вже розглядали приклад, коли в одній СУБД бізнес-правило працювало як побічний ефект своєї моделі блокування, а в іншій СУБД – ні. Було показано, що при одному і тому ж порядку виконання транзакцій в різних СУБД додаток може давати різні результати. Причина – принципові відмінності в реалізаціях. Ви з часом зрозумієте, що лише дуже небагато програми можна безпосередньо перенести з однієї в іншу СУБД. Відмінності в інтерпретації (наприклад, вирази NULL = NULL) і обробці операторів SQL будуть завжди.

В одному з недавніх проектів розробники створювали Web-додаток з використанням Visual Basic, керуючих елементів ActiveX, Web-сервера IIS і СУБД Oracle 8i. Розробники висловили побоювання з приводу реалізації бізнес-логіки на мові PL / SQL – додаток стає залежним від СУБД – і запитували, чи можна це виправити.

Мене це питання дещо здивував. Переглядаючи список обраних технологій, я не міг зрозуміти, чому їм “не сподобалася” залежність від СУБД:


Всі інші технології вони вибрали так, що виявилися прив’язаними до конкретної операційній системі – фактично свобода вибору залишалася тільки відносно СУБД.

Незалежно від того, що у них, мабуть, були вагомі причини вибрати саме ці технології, розробники чомусь вирішили не використовувати в повному обсязі можливості критичного компоненту своєї архітектури і зробили це в ім’я “відкритості”. Мені здається, що потрібно спочатку вдумливо обрати технології, а потім максимально використовувати надані ними можливості. За всі ці технології заплачені чималі гроші – не у ваших інтересах чи максимально їх використовувати? Причому, складалося враження, що вони збиралися скористатися перевагами інших технологій, так чому ж для СУБД зроблено виняток? На це питання особливо складно відповісти, якщо врахувати, що для ефективності програми успішна робота з СУБД має першорядне значення.

Можна розглянути це з точки зору “відкритості”. Всі дані містяться в базу даних. СУБД, що підтримує цю базу даних, – дуже відкрите засіб. Вона забезпечує доступ до даних через SQL, за допомогою компонентів EJB по протоколах HTTP, FTP, SMB і безлічі інших протоколів і механізмів доступу. Поки все відмінно: що може бути більш відкритим?

Потім поза бази даних додаються алгоритми і, що важливіше, механізми захисту. Наприклад, в компоненти, що забезпечують доступ до даних, або в код на Visual Basic, що працює на сервері Microsoft Transaction Server (MTS). В результаті з відкритістю бази даних покінчено – вона вже “закрита”. Користувачі тепер не можуть використовувати ці дані за допомогою існуючих технологій – вони повинні використовувати запропоновані методи доступу (або звертатися до даних в обхід захисту). Сьогодні це не здається проблемою, але пам’ятайте: те, що сьогодні є “найсучаснішої” технологією, наприклад компоненти EJB, вчора було ідеєю, а завтра буде застарілою, неефективною технологією. Що залишилося незмінним за останні 20 з гаком років у світі реляційного програмування (та, власне, і об’єктно-орієнтованого) – це бази даних. Засоби для користувачів змінюються практично щорічно, і в міру цього всі програми, самостійно, а не за допомогою СУБД, які реалізують захист, стають перешкодами на шляху подальшого прогресу.

СУБД Oracle пропонує можливість детального контролю доступу (Fine Grained Access Control, FGAC, – йому присвячена глава 21). Якщо коротко, ця технологія дозволяє розробнику вбудовувати в базу даних процедури, які змінюють надходять до бази даних запити. Ця зміна запитів використовується для обмеження кількості рядків, які клієнт може отримувати або змінювати. Процедура може визначати, хто виконує запит, коли цей запит виконується, з якого терміналу і т.д., і обмежувати відповідним чином доступ до даних. За допомогою FGAC можна організувати такий захист, коли:


Ця можливість дозволяє організувати контроль доступу в СУБД, безпосередньо видає дані. Тепер вже неважливо, чи отримує користувач дані через компоненти, сторінки JSP, з програми на VB з допомогою ODBC або через SQL * Plus, – будуть застосовуватися однакові правила захисту. Ви готові сприйняти будь-яку нову технологію.

Тепер я запитаю: яка технологія більш “відкрита”? Та, що дозволяє звертатися до даних тільки з коду VB і керуючих елементів ActiveX (замініть мову VB мовою Java, а компоненти ActiveX – компонентами EJB, якщо хочете, я говорю не про конкретну технології, а про підхід)? Або та, що забезпечує доступ з будь-середовища, здатного взаємодіяти з СУБД, по настільки відрізняється протоколам, як SSL, HTTP і Net8, або за допомогою функціональних інтерфейсів ODBC, JDBC, OCI і т. д.? Покажіть мені засіб створення звітів, здатне виконувати запити до коду на VB. Я ж назву десятки таких засобів, що виконують SQL-запити.

Рішення йти на жертви заради незалежності від СУБД і повної “відкритості” може прийняти кожен, і багато хто так і роблять, але я вважаю таке рішення помилковим. Незалежно від СУБД, її функціональні можливості необхідно використовувати повною мірою. Саме це, як правило, і робиться на етапі налаштування продуктивності (цим доводиться займатися відразу ж після впровадження). Дивно, як швидко відмовляються від вимоги незалежності, якщо використання специфічних можливостей СУБД дозволяє прискорити роботу програми в п’ять разів.

Як прискорити роботу?


Винесений у назву розділу запитання мені задають постійно. Всі шукають, де б зробити установку fast = true, Припускаючи, що налаштування продуктивності бази даних виконується на рівні СУБД. Мій досвід показує, що понад 80 відсотків (часто – набагато більше, до 100 відсотків) всього підвищення продуктивності досягається на рівні програми, а не бази даних. Не можна займатися настройкою СУБД, поки не налаштований додаток, що використовує дані.

З часом з’явився ряд установок, включаючи які на рівні СУБД, можна знизити вплив грубих помилок програмування. Наприклад, в Oracle 8.1.6 доданий новий параметр – CURSOR_SHARING=FORCE. Він дозволяє включити автоматичне використання пов’язують змінних. В результаті запит SELECT * FROM EMP WHERE EMPNO = 1234 автоматично переписується у вигляді SELECT * FROM EMP WHERE EMPNO = 😡. Це може істотно скоротити кількість повних розборів і зменшити очікування засувок в бібліотечному кеші, які описані в главі про архітектуру, але (завжди є але) може також мати ряд побічних ефектів. Можна нарватися на проблему (або помилку) при використанні цієї можливості, як, наприклад, у первісній версії:

ops$tkyte@ORA8I.WORLD> alter session set cursor_sharing=force;
Session altered.
ops$tkyte@ORA8I.WORLD> select * from dual where dummy=”X”and 1=0;
select * from dual where dummy=”X”and 1=0
*
ERROR at line 1:
ORA-00933: SQL command not properly ended
ops$tkyte@ORA8I.WORLD> alter session set cursor_sharing=exact;
Session altered.
ops$tkyte@ORA8I.WORLD> select * from dual where dummy=”X”and 1=0;
no rows selected

Прийнятий спосіб переписування запиту дає некоректний результат у версії 8.1.6 (через відсутність пропусків між “X“І ключовим словом AND). У результаті запит набуває вигляду:

select * from dual where dummy=:SYS_B_0and :SYS_B_1=:SYS_B_2;

Ключове слово AND стало частиною імені пов’язують змінної:SYS_B_0. У версії 8.1.7, однак, цей запит переписується так:

select * from dual where dummy=:”SYS_B_0″and :”SYS_B_1″=:”SYS_B_2″;

Тепер на рівні синтаксису все працює, але переписування може негативно позначитися на продуктивності додатка. Наприклад, зверніть увагу, що в розглянутому раніше коді умова 1 = 0 (завжди помилкове) переписано як: “SYS_B_1” =: “SYS_B_2”. Тепер на етапі аналізу у оптимізатора немає повної інформації щоб визначити, чи поверне цей запит нуль рядків (ще до його виконання). Я розумію, що запитів з конструкціями типу 1 = 0 у вас небагато, але підозрюю, що в деяких запитах літерали використовуються навмисне. У таблиці може бути стовпець з вельми нерівномірним розподілом значень (наприклад, 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 забезпечує набагато більшу швидкість роботи, ніж повторний аналіз та оптимізація безлічі однакових запитів як унікальних, це все одно повільніше, ніж виконання запитів, де зв’язуються змінні використовуються спочатку. Це відбувається не через неефективність механізму спільного використання коду курсора, а через неефективність самої програми. У главі 10 ми розглянемо, як аналіз операторів SQL може впливати на продуктивність в цілому. У багатьох випадках додаток, не використовує зв’язуються змінні, також не забезпечує ефективного аналізу та повторного використання курсорів. Оскільки у додатку передбачається унікальність кожного запиту (так як для кожного з них створюється унікальний оператор), то і курсор в ньому не буде використовуватися більше одного разу. Факт в тому, що якщо програміст використовує зв’язуються змінні, то він часто також аналізує курсор один раз і потім використовує багато разів. Саме витрати ресурсів на повторний аналіз призводять до спостережуваного зниження продуктивності.

Отже, важливо пам’ятати, що просто додавання параметра ініціалізації CURSOR_SHARING = FORCE не завжди дозволяє вирішити проблеми. Можуть навіть виникнути нові. У багатьох випадках параметр CURSOR_SHARING – Дійсно корисний засіб, але це не панацея. Для добре продуманого програми він не потрібен. У довгостроковій перспективі обгрунтоване використання пов’язують змінних (і при необхідності – констант) – найбільш правильно.

Навіть якщо є відповідні параметри, які можна встановити на рівні бази даних, а їх поки небагато, проблеми одночасного доступу та неефективних запитів (невдало сформульованих або викликаних невдалою організацією даних) не можна вирішити тільки установкою параметрів сервера. Для вирішення цих проблем необхідно переписати додаток (а часто і змінити його архітектуру). Перенесення файлів даних з одного диска на інший, зміна кількості блоків, що читаються поспіль однією операцією введення, і інші настройки “на рівні бази даних” часто мало впливають на загальну продуктивність програми. Вони ніяк не дадуть прискорення в 2, 3, … N раз, необхідного для досягнення прийнятної швидкості роботи програми. Як часто потрібно прискорити роботу програми на 10 відсотків? Якщо треба прискорити роботу на 10 відсотків, зазвичай ніхто взагалі не піднімає питання про це. Користувачі починають скаржитися, коли, на їхню думку, швидкість треба збільшити разів у п’ять. Однак повторюю: ви не збільшите швидкість роботи в п’ять разів за рахунок перенесення файлів даних на інші диски. Це можна зробити тільки шляхом зміни програми, наприклад, скоротивши обсяг введення-виведення.

Про продуктивності необхідно думати вже на рівні проекту, а потім безперервно перевіряти в процесі розробки. Це не можна відкладати на потім. Я дивуюся, стикаючись з випадками, коли розробники передають додаток замовнику, встановлюють і тільки після цього починають налаштовувати. Я бачив програми, які поставлялися клієнтам тільки з первинними ключами, взагалі без додаткових індексів. Запити ніхто не налаштовував і взагалі не тестував їх продуктивність. З додатком ніколи не працювало більше десятка користувачів. Налаштування вважається частиною процесу установки та впровадження програмного продукту. Для мене такий підхід неприйнятний. Користувачі повинні отримувати швидко працює, добре налагоджену систему. Проблем з продуктом у них буде достатньо і без продуктивності. Користувачі готові до того, що в додатку будуть помилки, але не змушуйте їх нескінченно чекати появи повідомлень про ці помилки на екрані.

Взаємовідносини АБД і розробників


На обкладинці книги сказано, як важливо для АБД уявляти, чого намагаються домогтися розробники, а для розробників – знати стратегію, використовувану АБД для управління даними. Точно відомо, що в основі більшості успішно працюючих інформаційних систем лежить плідну взаємодію між АБД і розробниками додатків. У цьому розділі я хочу представити точку зору розробника на поділ праці між розробником і АБД (виходячи з припущення, що при будь важливою розробці необхідна підтримка групи АБД).

Розробник не зобов’язаний знати, як встановлювати і конфігурувати програмне забезпечення. Цим повинен займатися АБД і, можливо, системний адміністратор. Налаштування Net8, запуск програми прослуховування, конфігурування режиму MTS, організація пулу підключень, установка СУБД, створення бази даних і т.д. покладаються на АБД і системного адміністратора.

Не зобов’язаний розробник також вміти налаштовувати операційну систему. Особисто я зазвичай пропоную зробити це системним адміністраторам. Розробник додатків баз даних повинен бути кваліфікованим користувачем відповідної операційної системи, але не можна вимагати від нього знання тонкощів її налаштування.

Мабуть, однією з основних турбот АБД є резервне копіювання і відновлення бази даних, і я вважаю це обов’язком виключно АБД. А от знати принцип робіт і використання сегментів відкоту і журналів повторного виконання розробник повинен. Знати, як виконати відновлення табличного простору за станом на певний момент часу розробнику необов’язково. Знання того, що це в принципі можливо, може стати в нагоді, але робити це самостійно вам не доведеться.

Налаштування на рівні примірника бази даних, визначення оптимального значення параметра SORT_AREA_SIZE – Цим зазвичай займається АБД. Бувають ситуації, коли розробнику необхідно змінити ряд параметрів сеансу, але за параметри рівня бази даних відповідає АБД. Зазвичай база даних підтримує програми декількох розробників, тому тільки АБД, що займається підтримкою всіх додатків, може прийняти правильне рішення.

Виділення простору на диску і управління файлами даних – обов’язок АБД. Розробники повинні обумовити необхідний обсяг простору (скільки їм імовірно буде потрібно), але решта повинні робити АБД і системний адміністратор.

Отже, розробники можуть не знати, як запустити СУБД, але повинні вміти працювати в ній. Розробник і АБД спільно вирішують різні частини однієї головоломки. АБД зв’язується з розробником, зауваживши, що запити споживають занадто багато ресурсів, а розробник зазвичай звертається до АБД коли не знає, як прискорити роботу системи (ось коли займаються настройкою примірника – коли додаток повністю налаштоване).

Звичайно, залежно від середовища розробки можливі варіанти, але мені подобається ділити обов’язки. Хороший розробник зазвичай – дуже поганий АБД, і навпаки. У них різні навички та досвід, а також, за моїми спостереженнями, різне пристрій розуму і особистісні характеристики.

Резюме


Ми в загальних рисах розглянули, чому необхідно знати використовувану СУБД. Наведені приклади – не унікальні, подібне відбувається на практиці кожен день. Давайте коротко повторимо ключові моменти. Якщо ви розробляєте ПО для СУБД Oracle:


Програмні проекти починаються і закінчуються, мови та середовища програмування з’являються і зникають. Від нас, розробників, чекають створення працюючих систем протягом тижнів, може бути, місяців, а потім ми переходимо до наступного завдання. Якщо ми будемо кожен раз винаходити велосипед, то ніколи не перейдемо до суті розробки. Адже ніхто не створює клас, який реалізує хеш-таблицю в Java, – він входить в набір стандартних компонентів. Ось і використовуйте наявні функціональні можливості СУБД. Перший крок до цього – дізнатися їх. Читайте далі.

Глава 2

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*