додатків Оптимізація С + + Builder в архітектурі
клієнт/сервер h2>
Наталія Єлманова p>
Одним з головних факторів, що впливають на прийняття
рішення про перенесення інформаційних систем в архітектуру клієнт/сервер, є
потенційна можливість підвищення продуктивності роботи користувачів,
особливо в тих випадках, коли знаходяться в експлуатації додатки не
задовольняють вимогам, що пред'являються до швидкості обробки даних з урахуванням їх
великого об'єму, а також високої інтенсивності і складності запитів. Відомо,
що інформаційні системи, засновані на архітектурі клієнт/сервер, можуть
мати істотні переваги перед інформаційними системами,
що базуються на мережевих версіях настільних СУБД, такими, як істотно
менший мережевий трафік, менший час обробки запитів, менша
ресурсомісткість клієнтських додатків і менші трудовитрати при їх розробці. p>
Однак сам по собі факт перенесення наявної бази даних
з настільної СУБД на який-небудь сервер баз даних з відповідною
коригуванням установки BDE (або інших засобів доступу до даних) аж ніяк не
гарантує підвищення продуктивності інформаційної системи в цілому.
Уявіть собі, наприклад, базу даних, яка містить одну-єдину таблицю
із сотні записів і п'яти цілочисельних полів, який міститься в Oracle Workgroup
Server, що функціонує під управлінням Windows NT на персональному комп'ютері з
16 Мб оперативної пам'яті, і однокористувацькому додаток, що використовує
навігаційні методи для її редагування. У цьому випадку, безумовно, простіше
зберігати дані в таблиці формату dBase або Paradox - продуктивність системи
буде в цьому випадку, швидше за все, набагато вище, тому що такий сервер, як
Oracle, вимагає сам по собі чимало ресурсів, а обсяг оброблюваних даних і
технологія їх обробки не виправдовують витрат, пов'язаних з придбанням,
установкою і експлуатацією серверної СКБД такого класу. Даний приклад,
звичайно, дещо перебільшує реальну ситуацію, але іноді на практиці відбуваються
і більше екзотичні випадки: p>
Отже, які кроки треба зробити для того, щоб
дійсно підвищити ефективність роботи користувачів і продуктивність
системи в цілому? Першим кроком у цьому напрямку є, звичайно, вибір
сервера. У цьому випадку, на жаль, не можна давати однозначних рекомендацій
типу "візьміть Oracle, він надійний" або "візьміть IB, він недорого
стоїть ". Вибір сервера, що управляє ним операційної системи і
відповідного апаратного забезпечення повинен здійснюватися з урахуванням
реальних і потенційно очікуваних умов експлуатації системи, таких, як
швидкість росту обсягу даних (наприклад, в мегабайтах на місяць), інтенсивність
транзакцій, ймовірність багатокористувацького доступу до однієї або сусіднім
записів у таблицях (при високій імовірності бажано вибрати сервер, при
використанні якого можна уникнути сторінкових блокувань), потенційний
зростання інтенсивності роботи користувачів, наявність підвищених вимог до
безпеки і захисту даних (деякі серверні СУБД випускаються в різних
виконань, що відрізняються один від одного ступенем захищеності даних),
необхідність використання продуктів сторонніх виробників (таких, як
ODBC-драйвери, додаткові бібліотеки і утиліти та ін), наявність пов'язаних з
цим проблем (типовий приклад з недавньої реальної практики була, наприклад,
проблема пошуку ODBC-драйвера до сервера Centura SQLBase 6.0, що підтримує
використання збережених процедур). Не менш, ніж технічні, важливі і фінансові
аспекти цієї проблеми. Чи планується використовувати для установки серверної СКБД
вже наявні обчислювальні потужності і операційну систему чи слід
придбати нові? В яку суму обійдеться придбання серверної СКБД,
клієнтських ліцензій, апаратного забезпечення? Скільки буде коштувати
адміністрування цієї СУБД і керуючої їй операційної системи, а також
навчання майбутніх адміністраторів і програмістів? Скільки підключень до сервера
допускається при придбанні однієї ліцензії - одне, два, чотири? Які
умови, що накладаються ліцензійними угодами при використанні
мультиплексування з'єднань за рахунок експлуатації серверів додатків, якщо в
Надалі можливий перехід до триланкової архітектурі? Прийняття рішення про
виборі серверної СКБД істотно залежить від відповіді на всі ці питання, і не
завжди технічні аспекти або думку розробників визначають в кінцевому підсумку
вибір сервера. Непоодинокими є також випадки, коли передбачається використання вже
наявної серверної СКБД (або навіть готової бази даних). p>
Припустимо, що сервер обраний (виходячи з
вищевикладених або будь-яких інших міркувань). Яким чином слід
використовувати надані їм можливості? Ефективність експлуатації
інформаційної системи з точки зору продуктивності залежить від
узгодженої роботи трьох її складових частин - сервера баз даних, клієнтського
програми та клієнтської частини серверної СКБД, що функціонують на робочій
станції, і мережі, і неоптимальна робота однієї з цих частин може звести до
нулю результат всіх зусиль, спрямованих на оптимізацію роботи інших
частин. Таким чином, проблема оптимізації роботи інформаційної системи
досягається шляхом вирішення кількох завдань: оптимізації клієнтської частини,
оптимізації серверної частини, зниження мережевого трафіку. Нижче ми розглянемо
деякі прийоми, які сприятимуть у тій чи іншій мірі вирішення цих завдань.
Однак перед цим вивчимо один з найпростіших способів контролю вмісту
запитів, що пересилаються на сервер баз даних бібліотекою BDE, та результатів їх
виконання, за допомогою утиліти SQL Monitor, що входить в комплект поставки
С + + Builder. p>
Контроль запитів за допомогою SQL Monitor. h2>
SQL Monitor використовується для контролю запитів,
пересилаються клієнтським додатком сервера баз даних за допомогою BDE, і їх
результатів, а також вимірювання часу між ними. Для його запуску слід
вибрати пункт SQL Monitor з меню Database C + + Builder. Головне вікно SQL Monitor
складається з двох частин. У верхній частині відображаються послідовно
генеруються SQL-пропозиції та відомості про відгуки сервера, а також порядковий
номер і час їх настання, а в нижній частині - повний текст SQL-запиту.
Список, що відображається у верхньому вікні, можна зберегти у файлі для подальшого
аналізу. На рис.1 представлений типовий висновок відомостей при роботі програми,
розглянутого в попередній статті цього циклу. p>
При використанні SQL Monitor можливий вибір типів
відображених відомостей. Їх можна вибрати в діалозі Trace Options, що викликається з
меню Options. p>
SQL Monitor дозволяє відображати відомості про наступні
дії: p>
Prepared Query Statements - SQL-пропозиції,
передаються на сервер p>
Executed Query Statements - SQL-пропозиції, готові до
виконання сервером p>
Statement Operations - дії, що виконуються сервером
(FETCH, EXECUTE та ін) p>
Connect/Disconnect - дії, пов'язані з встановленням
або розривом з'єднання з сервером. p>
Transactions - дії, пов'язані з виконанням
транзакцій (BEGIN, COMMIT, ROLLBACK) p>
Blob I/O - дії, пов'язані з передачею Blob-полів p>
Miscellaneous - інші дії p>
Vendor Errors - повідомлення про помилки, що повертаються
сервером p>
Vendor Calls - виклики функцій API клієнтської частини,
пов'язаних із зверненням до сервера p>
Використання SQL Monitor є найпростішим (хоча і
не єдиним) засобом тестування продуктивності інформаційних
систем в архітектурі клієнт/сервер, і ефективність застосування більшості
розглянутих нижче прийомів їх оптимізації можна проконтролювати з його
допомогою.
p>
Мінімізація звернень до сервера та мережі h2>
Мінімізація зв'язків з сервером впливає на продуктивність
всіх складових частин інформаційної системи - клієнта, сервера та мережі. Зайві
зв'язку з сервером призводять до створення додаткових об'єктів (таких, як
TDatabase) в клієнтському додатку, створення додаткових запитів до сервера
для з'ясування прав користувача на доступ до тих чи інших об'єктів бази даних,
а також до непродуктивних використання ресурсів сервера. Для мінімізації
зв'язків з сервером можна використовувати такі прийоми, як використання в явному
вигляді компонента TDatabase замість неявного їх створення, використання
кешування даних і структури, зберігання відомостей про метадані в клієнтському
додатку, використання локальних фільтрів та ін p>
Використання компонента TDatabase h2>
При використанні декількох компонентів TDataSet
слід мати на увазі, що кожен з них прагне під час виконання створити
неявно свій TDatabase об'єкт для зв'язку з сервером. Якщо ж помістити компонент
TDatabase на форму або в модуль даних на етапі проектування програми, і
пов'язати з ним всі компоненти TDataSet, вказавши його ім'я в якості значення
властивості DatabaseName цих компонентів, всі вони будуть використовувати одну загальну
зв'язок, забезпечену цим компонентом. p>
Використання параметра SQLPASSTHRU MODE p>
Ще один спосіб мінімізації зв'язків з сервером полягає
у зміні значення параметра SQLPASSTHRU MODE компонента TDatabase (або
псевдоніма, створеного утилітою конфігурації BDE). Цей параметр визначає,
чи можуть використовуватися загальні з'єднання з базою даних запитами,
згенерований додатком (наприклад, за допомогою компонента TQuery), і
запитами, згенерована самої бібліотекою BDE (наприклад, при реалізації
навігаційних методів компонента TTable). Значення цього параметра по
умовчанням є NOT SHARED, що дозволяє уникнути можливих конфліктів при
многопользовательском оновлення даних, але що створює окремі з'єднання з
базою даних для обох типів запитів. p>
Найбільш ефективним з точки зору мінімізації
з'єднань з базою даних значенням цього параметра в більшості випадків
є значення SHARED AUTOCOMMIT. При використанні цього значення зміни
кожного запису в таблицях негайно фіксуються сервером незалежно від типу
що з'єднав їх запиту, але при цьому обидва типи запитів можуть використовувати один і
той же з'єднання з базою даних. Цей режим найбільш близький до режиму, в якому
використовуються мережеві версії настільних СУБД. Однак оскільки сервер в цьому
випадку повинен негайно фіксувати результати зміни записів, він
ініціює і завершує окрему транзакцію при зміні кожного запису, що
може призвести до перевантаження сервера та мережі і до зниження продуктивності
замість очікуваного її підвищення. Тому ефективність використання такого
режиму повинна бути обов'язково перевірена шляхом тестування. p>
Третє можливе значення цього параметра - SHARED
NOAUTOCOMMIT. У цьому випадку обидва типи запитів можуть також використовувати один і
той же з'єднання з базою даних, причому без завершення транзакцій після
редагування кожного запису. Однак у цьому випадку контроль за завершенням транзакцій
слід здійснювати в клієнтському додатку. Подібний режим може бути вельми
ефективний, так як перенавантажують сервер транзакції автоматично не
ініціюються після редагування кожного запису, але при його використанні можуть
виникати конфлікти і непередбачувані зміни даних при спробі
одночасного редагування однієї і тієї ж записи різними користувачами.
Тому цей режим слід використовувати тільки в тому випадку, якщо ймовірність
таких колізій мала. p>
Кешування метаданих на робочій станції h2>
Ще один спосіб мінімізації зв'язків з сервером
полягає у використанні кешування структури таблиць на робочій станції. У
цьому випадку знижується число звернень до сервера з метою визначення метаданих,
тобто кількості стовпців у використовуваних в додатку таблицях, їхніх імен і типів
даних. Для цієї мети використовуються наступні параметри псевдоніма бази даних
(або компонента TDatabase): p>
ENABLE SCHEMA CACHE - дозволено чи кешування
метаданих; p>
SCHEMA CACHE SIZE - кількість таблиць, структура яких
кешується; p>
SCHEMA CACHE TIME - час зберігання інформації в кеші в
секундах; значення -1 відповідає часу зберігання даних в кеші до закриття
додатки; p>
SCHEMA CACHE DIR - каталог для кешування метаданих.
p>
Застосування кешування метаданих може істотно
підвищити продуктивність клієнтських додатків і знизити навантаження на мережу.
Однак застосовувати його можна тільки в тому випадку, якщо структура таблиць не
змінюється протягом роботи програми. Якщо ж у процесі роботи програми
проводиться додавання або видалення стовпців, створення або видалення індексів
(не обов'язково цим же додатком), створення і видалення тимчасових таблиць,
інформація в кеші може виявитися не відповідає дійсності, що
може призвести до помилок, пов'язаних з недопустимими типами даних,
неприпустимими перетвореннями типів і ін У цьому випадку застосовувати кешування
метаданих не рекомендується. p>
Використання нащадків TField в клієнтському додатку p>
Іншим способом зберігання на робочій станції додатку
відомостей про метадані є використання компонентів - нащадків TField. Так
як відповідні об'єкти зберігають відомості про структуру таблиць безпосередньо
у додатку, на етапі виконання не проводиться звернень на сервер з метою
отримання метаданих. Використання нащадків TField краще, ніж
використання методів FieldByName () або властивості Fields, так як останні
використовують звернення до сервера для отримання відомостей про типи полів.
Обмеження на застосування компонентів - нащадків TField такі ж, як і в
попередньому випадку - їх використання рекомендується при стабільній структурі
таблиць. Крім цього, зміна структури даних на сервері може зажадати
модифікації програми і, як наслідок, встановлення його нової версії на робочі
станції. p>
Кешування даних на робочій станції p>
Крім кешування метаданих нерідко застосовується і
кешування на робочій станції самих даних. Для цієї мети слід встановити
рівним true значення властивості CachedUpdates відповідного компонента
TDataSet. У цьому випадку всі внесені користувачем зміни зберігаються в
локальному кеші. Збереження даних на сервері проводиться за допомогою методу
ApplyUpdates () компонента TDataSet, а метод CommitUpdates () очищає кеш. У
Загалом такий метод знижує мережевий трафік і сумарне число з'єднань з
сервером, тому що, по-перше, при редагуванні даних в кеші не потрібно
наявності з'єднання з сервером, а по-друге, збереження декількох записів з
кешу на сервері може бути здійснено шляхом виконання однієї-єдиної транзакції.
Крім цього, знижується сумарна кількість блокувань записів на сервері, так як
в процесі редагування даних в кеші необхідності в блокування немає. p>
використанням місцевих фільтрів при невеликих обсягах
даних p>
Якщо компонент TDataSet доставляє на робочу станцію
невеликий за обсягом набір даних, який можна порівняти з розміром кеша робочої станції
(визначеного параметрами MINBUFSIZE і MAXBUFSIZE системних налаштувань BDE), він
буде повністю кешуватися на робочій станції. У цьому випадку застосування локальних
фільтрів більш переважно, ніж використання запитів з пропозицією
WHERE, що направляються на сервер, так як в першому випадку не потрібно звернення до
сервера.
p>
Оптимізація використання сервера h2>
Використання збережених процедур p>
При виконанні багаторазово повторюваних дій,
використовують дані з сервера (наприклад, при статистичній обробці
що містяться в таблицях даних) продуктивність інформаційної системи можна
підвищити, використовуючи процедури, що зберігаються сервера замість SQL-запитів, що генеруються
клієнтським додатком. Справа в тому, що переданий з клієнтського застосування
SQL-запит сервером оптимізується, компілюється і лише потім виконується, а
збережені процедури сервера вже містяться в оптимізованому і скомпільованому
вигляді, тому обробка даних з їх використанням вимагає менших витрат
часу, особливо при невеликому числі і сумарному обсязі переданих
параметрів процедури. p>
Проте слід мати на увазі, що процедури, що зберігаються
пишуться на процедурному розширення SQL використовуваного сервера. Існують
офіційні стандарти непроцедурного мови SQL ANSI/ISO SQL-86, SQL-89 і
SQL-92, але на сьогоднішній день не існує стандартів на процедурні
розширення цієї мови. Кожна серверна СУБД має свій набір процедурних
розширень, що відрізняється від відповідних розширень інших СУБД. Деякі
сервера, наприклад Borland IB Database, підтримують створення і використання в
процедурах функцій, визначених користувачем (UDF - User Defined Functions),
а деякі не підтримують. Тому при зміні платформи процедури, що зберігаються,
швидше за все, буде потрібно переписувати. Відзначимо також, чт?? найчастіше серверні
збережені процедури створюються шляхом ручного кодування, і для їх створення, як
правило, не існує зручних візуальних засобів розробки і налагодження
на зразок наявних в C + + Builder. Тому при прийнятті рішення про створення тих
чи інших збережених процедур не заважає оцінити можливі трудовитрати - іноді
може виявитися, що вони не стоять очікуваного ефекту. p>
Якщо ж процедури, що зберігаються застосовуються активно, ще
більшого підвищення продуктивності при їх використанні можна досягти,
мінімізуючи число і обсяг переданих на сервер параметрів. Очевидно, що
передати на сервер ціле число набагато простіше, ніж переслати довгу символьну
рядок, тому при плануванні збережених процедур з подібними параметрами є
сенс подумати про перепроектуванні бази даних і створення, наприклад,
таблиць-довідників або, при невеликих обсягах таких таблиць, про зберігання їх на
робочої станції або організації відповідних масивів. p>
Використання попередньої підготовки запитів p>
При використанні компонентів TQuery нерідко буває
корисно використовувати метод Prepare (), особливо якщо компонент TQuery містить
параметризрвані запит. Метод Prepare () здійснює пересилання запиту на
сервер, де він оптимізується і компілюється, а при відкритті запиту на сервер
в цьому випадку надсилаються тільки його параметри. Особливо помітним підвищення
продуктивності може виявитися тоді, коли параметризрвані запити з
різними значеннями параметрів повторюються часто - в цьому випадку повторна
підготовка запиту не буде потрібно. Якщо ж метод Prepare () не викликається явно,
він буде автоматично викликатися неявно кожного разу при пересиланні параметрів,
ініціюючи пересилання всього тексту запиту на сервер. p>
Що каcается що передаються на сервер параметрів
запиту, їхня кількість і обсяг рекомендується мінімізувати точно так само, як і в
випадку параметрів збережених процедур. p>
Використання уявлень (View) і параметрезованих
запитів. p>
Нерідко починаючі програмісти використовують
динамічне створення запитів на етапі виконання, змінюючи вміст
строкового масиву, що міститься у властивості SQL компонента TQuery (наприклад,
періодично модифікуючи пропозицію WHERE). При часто повторюваних запитах
такого типу це не самий оптимальний спосіб пересилання запитів на сервер, так
що в цьому випадку обов'язково здійснюється попередня підготовка
запитів, що полягає у пересиланні всього тексту на сервер, а також оптимізації
та компіляції його сервером. Більш кращим у цьому випадку є
використання параметрезованих запитів і методу Prepare (), або використання
уявлень (View) сервера, що представляють собою не що інше як що зберігається на
сервер заздалегідь скомпільований запит. В останньому випадку можна уникнути не
тільки зайвих повторних компіляцій запиту сервером, але і зайвого перевантаження
клієнта генерацією запитів. p>
Використання властивості UpdateMode p>
Властивість UpdateMode компонентів TDBDataSet визначає
склад оператора WHERE, що генерується BDE при оновленні даних. Розглянемо,
яким вийде оператор WHERE при редагуванні поля SYMBOL що міститься на
сервері Oracle Workgroup Server копії таблиці HOLDINGS з вхідної в комплект
поставки C + + Builder бази даних BCDEMOS при різних значеннях цього властивості.
Згенеровані SQL-пропозиції можна поспостерігати за допомогою SQL Monitor. p>
За замовчуванням значенням властивості UpdateMode є
UpWhereAll, і в цьому випадку BDE генерує пропозицію WHERE, що містить всі
поля таблиці. При цьому згенерований оператор SQL, якщо тільки він не
перевизначений за допомогою компонента TUpdateSQL, буде виглядати наступним
так: p>
UPDATE
"HOLDINGS" SET "SYMBOL" =: 1 WHERE "ACCT_NBR" =: 2
AND "SYMBOL" =: 3 AND "SHARES" =: 4 AND
"PUR_PRICE" =: 5 AND "PUR_DATE" =: 6 AND "ROWID" =: 7. p>
Цей спосіб визначення змінних рядків таблиці
є самим повільним (особливо у випадку таблиць з великим числом полів), але
і найбільш надійним, тому що практично гарантує достовірну ідентифікацію
записи в будь-якій ситуації, навіть у разі відсутності ключових полів (якщо,
звичайно, таблиця задовольняє вимогу реляційної моделі, що свідчить, що
кожна запис має бути унікальна і, отже, повинна володіти
унікальним набором полів). p>
Одним з інших можливих значень цієї властивості
є UpWhereChanged, при якому в реченні WHERE містяться тільки
поля, змінені в даному запиті, і ключові поля. У цьому випадку запит має
наступний вигляд: p>
UPDATE "HOLDINGS" SET "SYMBOL" =: 1
WHERE "ROWID" =: 2 AND "SYMBOL" =: 3 p>
Такий запит виконується швидше, але в цьому випадку
можливі колізії при багато користувачів роботі. Наприклад, один користувач
зчитує запис для редагування в клієнтську програму, інший відразу після
цього її видаляє, а третій створює нову з тими ж значеннями змінних полів
і тими ж значеннями ключових полів. Саме ця нова запис і буде
модифікуватися замість зчитаної. Проте такий випадок малоймовірний, особливо
якщо що стали непотрібними первинні ключі видалених записів якийсь час не
використовуються (наприклад, при створенні ключів за допомогою генераторів
послідовностей). p>
Третім можливим значенням властивості UpdateMode
є UpWhereKeyOnly. У цьому випадку пропозиція WHERE містить тільки
ключове поле: p>
UPDATE "HOLDINGS" SET "SYMBOL" =: 1
WHERE "ROWID" =: 2 p>
Хоча це найшвидший спосіб оновлення даних
порівняно з двома випадками, він в загальному випадку небезпечний. У цьому
випадку виникнення ситуації, коли модифікуються поле виявиться зміненим
іншим користувачем, ніяк не контролюється, що може призвести до
непередбачуваних результатів при многопользовательском редагуванні даних.
Тому застосування значення UpWhereKeyOnly можливе лише у тому випадку, коли
ймовірність одночасної модифікації однієї і тієї ж записи декількома
користувачами вкрай мала. p>
Підвищення ефективності SQL-запитів p>
Ефективне програмування на SQL - тема вельми
велика, гідна окремої статті (і навіть не однієї). Можливість і
результативність використання багатьох прийомів оптимізації нерідко залежить від
особливостей використовуваного сервера баз даних і управляє його роботою
операційної системи. Тому тут ми лише коротко перерахуємо найбільш часто
вживані прийоми оптимізації SQL-пропозицій. p>
Якщо потрібно визначити наявність в таблиці записів,
задовольняють будь-якому умовою, слід віддати перевагу використання
предиката EXIST запиту, обчислюється число таких записів. Запит виду p>
SELECT * FROM <ім'я таблиці> WHERE (SELECT COUNT
(*) FROM <ім'я таблиці> WHERE <умова>)> 0 p>
змусить сервера при виконанні внутрішнього підзапит
перебрати всі рядки таблиці, перевіряючи відповідність кожного запису вказаною
умові, тоді як запит виду p>
SELECT * FROM <ім'я таблиці> WHERE EXISTS (SELECT *
FROM <ім'я таблиці> WHERE <умова>) p>
змусить сервер перебирати запису до знаходження першого
запису, що задовольняє вказаною умові. Зайвий перебір записів на сервері,
природно, займає деякий час - чудес не буває. p>
Багато прийоми оптимізації пов'язані з використанням
індексів. Якщо будь-яке поле таблиці часто використовується в реченні WHERE,
порівнюємо його значення з будь-якою константою або параметром, що наявність
індексу для цього поля прискорює подібні операції. З цієї ж причини
рекомендується індексувати зовнішні ключі у таблиць з великою кількістю записів.
Проте слід мати на увазі, що підтримка індексів уповільнює операції вставки
записів, тому при проектуванні даних слід зважити всі "за"
і "проти" створення індексів, а ще краще - провести відповідне
тестування, заповнивши таблиці випадковими даними (для цієї мети можна написати
відповідної програми, а ще краще - скористатися готовими засобами
тестування типу SQA Suite). p>
Говорячи про використання індексів, слід також
звернути увагу на те, що при використанні індексованих полів в якості
аргументів функцій наявність індексу не впливає на швидкість виконання запиту --
індекс у цьому випадку не використовується. p>
Особливо слід відзначити проблеми, пов'язані з
використанням вкладених запитів. Справа в тому, що швидкість виконання запиту
істотно залежить від числа рівнів вкладеності підзапитів (час виконання
приблизно пропорційно добутку числа записів у таблицях, що використовуються в
підзапит). Фактично перевірка відповідності умові WHERE кожного запису з
зовнішнього підзапит ініціює виконання внутрішнього підзапит, що особливо
помітно позначається при великій кількості записів. У практиці автора трохи більше
роки тому був випадок, коли у разі приведення в порядок однієї з використовуваних
корпоративних інформаційних систем після виконання кількох звичайних
запитів на оновлення даних в таблиці з декількома десятками тисяч записів,
що виконувалися протягом декількох секунд, був ініційований вкладений запит на
оновлення даних до цієї ж таблиці. Цей запит виконувався більше двох годин
(чого, взагалі кажучи, і слід було очікувати). Тому використовувати вкладені
запити слід тільки в тих випадках, коли без них не можна обійтися.
Альтернативою використання вкладених запитів може служити фільтрація результатів
звичайного запиту в клієнтському додатку або послідовне виконання
декількох запитів зі створенням тимчасових таблиць на сервері.
p>
Оптимізація клієнтського додатку h2>
Методи оптимізації клієнтського застосування мало чим
відрізняються від методів оптимізації звичайних програм C + + Builder. Зазвичай
оптимізація полягає в підвищенні швидкодії програми і в зниженні
обсягу ресурсів, що використовуються операційної системи. p>
Зниження кількості споживаних ресурсів можливо
різними способами. Основний принцип їх економії - не використовувати ресурси
даремно. Саме тому рекомендується в програмах, що використовують велику
кількість форм, створювати їх динамічно та знищувати, як тільки вони
стають непотрібними (що відрізняється від установок менеджера проектів з
замовчуванням, які припускають автоматичне створення всіх форм відразу ж).
Однак при цьому слід пам'ятати, що модуль даних, що містить компоненти
доступу до даних, що використовуються інтерфейсними елементами динамічно створюваної
форми, повинен бути створений до створення самої форми, щоб уникнути виключної
ситуації, пов'язаної зі зверненням до неіснуючого об'єкту. p>
Уникати зайвих зв'язків з сервером слід не тільки
з-за зайвої перевантаження мережі і сервера, а й через те, що вони поглинають
деяку кількість ресурсів і сповільнюють роботу програми. p>
Ще одним способом економії ресурсів клієнтського
програми є використання більш економічних інтерфейсних елементів у
випадках, де це можливо (наприклад, TDBText або TLabel замість TDBEdit, TLabel
замість TDBMemo при відображенні полів, редагування яких не передбачається,
TDBGrid замість TDBControlGrid і т.д.). p>
Ще один прийом, що підвищує швидкодію клієнтського
додатки, полягає в скороченні кількості операцій, пов'язаних з виведенням даних
з таблиць на екран, наприклад, при "гортання" великої кількості
строк для компонентів типу TDBGrid або TDBCtrlGrid в процесі навігації по набору
даних або будь-якої їх обробки. У цьому випадку рекомендується на час
відключати зв'язок інтерфейсних елементів з компонентом TDataSource, встановивши
значення його властивості Enabled рівним false (приклад використання цього прийому
буде наведено нижче). p>
Про навігаційних методи та "кліпперном" стилі
програмування p>
Говорячи про оптимізацію клієнт-серверних інформаційних
систем, хотілося б окремо зупинитися на одній дуже поширеною
помилку, яку здійснюють програмістами, які мають великий досвід роботи з настільними
СУБД і засобами розробки, що базуються на xBase-мовами, такими, як
Clipper, dBase, FoxPro та ін При використанні засобів розробки такого роду
будь-яка зміна даних в таблиці згідно з будь-яким правилам
здійснюється звичайно шляхом створення циклу типу: p>
USE HOLDINGS p>
GO TOP p>
DO WHILE! EOF () p>
PUR_PRICE = PUR_PRICE 10
p>
SKIP p>
ENDDO p>
CLOSE p>
У наведеному фрагменті xBase-коду PUR_PRICE - ім'я
поля таблиці HOLDINGS, схильної до зміни. p>
При переході до архітектури клієнт/сервер і засобам
розробки, що підтримує SQL, спочатку виникає природне бажання
продовжувати писати подібний код, використовуючи цикли і навігацію по таблиці. Це не
так страшно у випадку використання C + + Builder з настільними СУБД - локальний
SQL, здатний бути альтернативою в цьому випадку, в кінцевому підсумку також
ініціює перебір записів таблиці. Взагалі кажучи, те ж саме відбувається і при
виконання запиту типу UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE 10 на сервері
баз даних, але такий цикл є внутрішнім процесом сервера, в якому
не задіяні ні клієнт, ні мережу. Однак при використанні "кліпперного"
стилю програмування бібліотека BDE зовсім не зобов'язана здогадуватися, що мав на
увазі програміст, який написав подібний цикл, і генерує зовсім не такі
запити! p>
Розглянемо простий приклад. Створимо копію таблиці
HOLDINGS.DBF з вхідної в комплект поставки C + + Builder бази даних DBDEMOS на
будь-якому сервер баз даних, наприклад, Personal Oracle (скориставшись,
наприклад, утилітою Data Migration Wizard, що постачається Borland
C + + Builder). Потім створимо новий додаток, що складається з однієї форми,
що включає компоненти TDBGrid, TTable, TDataSource, TQuery, TDBNavigator і три
кнопки (мал. 3). p>
Встановимо наступні значення властивостей використовуваних
компонентів (табл. 1): p>
Таблиця 1. p>
Компонент p>
Властивість p>
Значення p>
DBNavigator1 p>
DataSource p>
DataSource1 p>
DBGrid p>
DataSource p>
DataSource1 p>
Button1 p>
Caption p>
'Use SQL' p>
Button2: p>
Caption p>
'Update records' p>
Button3: p>
Caption p>
'Exit' p>
DataSource1 p>
DataSet p>
Table1 p>
Table1 p>
DatabaseName p>
ORACLE7 p>
TableName p>
HOLDINGS p>
UpdateMode p>
UpWhereKeyOnly p>
Table1PUR_PRICE p>
FieldName p>
'PUR_PRICE' p>
Query1 p>
DatabaseName p>
ORACLE7 p>
SQL p>
'UPDATE
HOLDINGS SET PUR_PRICE = PUR_PRICE +10' p>
Тепер створимо обробники подій, пов'язані з
натисканням на кнопки. Кнопка Update records реалізує аналог фрагмента
xBase-коду, наведеного вище: p>
void __fastcall
TForm1:: Button2Click (TObject * Sender) p>
( p>
Table1-> First (); p>
DataSource1-> Enabled = false;// Не будемо знущатися над відеоадаптером! p>
while
(! Table1-> Eof) p>
( p>
Table1-> Edit (); p>
Table1PUR_PRICE-> Value = Table1PUR_PRICE-> Value 10; p>
Table1-> Next (); p>
) p>
DataSource1-> Enabled = true;
//Подивимося, що вийшло ... p>
) p>
Тимчасове відключення зв'язку між DataSource1 і Table1
в даному обробнику подій зроблено для того, щоб виключити перемальовування
компонента DBGrid1 при зміні кожного запису. p>
Кнопка Use SQL реалізує виконання одиночного
SQL-запиту UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE 10: p>
void __fastcall
TForm1:: Button1Click (TObject * Sender) p>
( p>
Query1-> Prepare (); p>
Query1-> ExecSQL (); p>
Table1-> Refresh ();
//Подивимося на результат ... p>
) p>
скомпілювавши додаток, запустимо SQL Monitor і
подивимося, які запити генеруються BDE при натисканні на ці кнопки. p>
При використанні кнопки Update records log-файл має
наступний вигляд: p>
14:37:08 SQL
Prepare: ORACLE - UPDATE "HOLDINGS" SET "PUR_PRICE" =: 1
WHERE "ROWID" =: 2 p>
14:37:08 SQL
Execute: ORACLE - UPDATE "HOLDINGS" SET "PUR_PRICE" =: 1
WHERE "ROWID" =: 2 p>
14:37:08 SQL Stmt:
ORACLE - Close p>
14:37:08 SQL
Prepare: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE "ACCT_NBR" =: 1 p>
14:37:08 SQL
Execute: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE "ACCT_NBR" =: 1 p>
14:37:08 SQL Misc:
ORACLE - Set rowset size p>
14:37:08 SQL Stmt:
ORACLE - Fetch p>
14:37:08 SQL Stmt:
ORACLE - EOF p>
14:37:08 SQL Stmt:
ORACLE - Close p>
14:37:08 SQL
Prepare: ORACLE - UPDATE "HOLDINGS" SET "PUR_PRICE" =: 1
WHERE "ROWID" =: 2 p>
І так далі, поки не закінчаться всі записи: p>
14:37:10 SQL
Prepare: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE "ACCT_NBR" =: 1 p>
14:37:10 SQL
Execute: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE "ACCT_NBR" =: 1 p>
14:37:10 SQL Misc:
ORACLE - Set rowset size p>
14:37:10 SQL Stmt:
ORACLE - Fetch p>
14:37:10 SQL Stmt:
ORACLE - EOF p>
14:37:10 SQL Stmt: ORACLE - Close p>
Відзначимо, що це ще не найбільший набір запитів
для даного випадку, тому що при оновленні таблиці було використано значення
UpWhereKeyOnly властивості UpdateMode компонента Table1, при якому запити на
оновлення одного запису мають мінімальний набір перевіряються параметрів. p>
При використанні кнопки Use SQL log-файл має
зовсім інший вигляд: p>
14:35:51 SQL
Prepare: ORACLE - UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE-10 p>
14:35:51 SQL
Transact: ORACLE - Set autocommit on/off p>
14:35:51 SQL
Execute: ORACLE - UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE-10 14:35:51 SQL Stmt:
ORACLE - Close p>
Решта SQL-запити, що містяться в log-файлі,
генеруються BDE при виконанні методу Refresh () компонента Table1: p>
14:35:51 SQL
Prepare: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE "ACCT_NBR" =: 1 p>
14:35:51 SQL
Execute: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE "ACCT_NBR" =: 1 p>
14:35:51 SQL Misc:
ORACLE - Set rowset size p>
14:35:51 SQL Stmt:
ORACLE - Fetch p>
14:35:51 SQL Stmt:
ORACLE - EOF p>
14:35:51 SQL Stmt:
ORACLE - Close p>
14:35:51 SQL
Prepare: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE (( "ACCT_NBR" IS
NULL OR "ACCT_NBR">: 1)) ORDER BY "ACCT_NBR" ASC p>
14:35:51 SQL
Execute: ORACLE - SELECT "ACCT_NBR", "SYMBOL"
, "SHARES", "PUR_PRICE", "PUR_DATE"
, "ROWID" FROM "HOLDINGS" WHERE (( "ACCT_NBR" IS
NULL OR "ACCT_NBR">: 1)) ORDER BY "ACCT_NBR" ASC p>
14:35:51 SQL Misc:
ORACLE - Set rowset size p>
14:35:51 SQL Stmt: ORACLE - Fetch p>
Якщо з тексту обробника події Button1Click
видалити рядок p>
Table1-> Refresh ();, p>
то дії з 5-го по 14-е виконуватися не будуть. Крім
того, при натисненні на цю ж кнопку кілька разів поспіль log-файл буде мати
наступний вигляд: p>
14:11:36 SQL
Prepare: ORACLE - UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE-10 p>
14:11:36 SQL
Execute: ORACLE - UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE-10 p>
14:11:40 SQL Stmt:
ORACLE - Reset p>
14:11:40 SQL
Execute: ORACLE - UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE-10 p>
14:14:17 SQL Stmt:
ORACLE - Reset p>
14:14:17 SQL
Execute: ORACLE - UPDATE HOLDINGS SET PUR_PRICE = PUR_PRICE-10 p>
14:14:19 SQL Stmt: ORACLE - Reset p>
Як бачимо, компіляція запиту сервером здійснюється
в цьому випадку тільки один раз. p>
Отже, ми бачимо, що "кліпперний" стиль
програмування при роботі з SQL-серверами абсолютно неприйнятний - він наводить
до перевантажень сервера, мережі і робочої станції одночасно, а різниця в
швидкості виконання помітна навіть при невеликому обсязі таблиці та використанні
локального сервера, тому, аналізуючи причини низької продуктивності
додатків, варто подивитися - а чи немає в клієнтському додатку подібних
фрагментів коду? p>
На закінчення хотілося б відзначити, що оптимізація
клієнт-серверних інформаційних систем повинна проводитися з урахуванням
результатів аналізу продуктивності і ретельного тестування, можливо, не
тільки за допомогою SQL Monitor, але і за допомогою спеціальних засобів тестування,
що володіють додатковими функціональними можливостями. p>
Список літератури h2>
Для підготовки даної роботи були використані
матеріали з сайту http://www.realcoding.net/
p>