Смекни!
smekni.com

Оптимизация приложений С++Builder в архитектуре клиент/сервер (стр. 3 из 4)

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.

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

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

UPDATE "HOLDINGS" SET "SYMBOL"=:1 WHERE "ROWID"=:2 AND "SYMBOL"=:3

Такой запрос выполняется быстрее, но в этом случае возможны коллизии при многопользовательской работе. Например, один пользователь считывает запись для редактирования в клиентское приложение, другой сразу после этого ее удаляет, а третий создает новую с теми же значениями изменяемых полей и теми же значениями ключевых полей. Именно эта новая запись и будет модифицироваться вместо считанной. Однако такой случай маловероятен, особенно если ставшие ненужными первичные ключи удаленных записей какое-то время не используются (например, при создании ключей с помощью генераторов последовательностей).

Третьим возможным значением свойства UpdateMode является UpWhereKeyOnly. В этом случае предложение WHERE содержит только ключевое поле:

UPDATE "HOLDINGS" SET "SYMBOL"=:1 WHERE "ROWID"=:2

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

Повышение эффективности SQL-запросов

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

Если требуется определить наличие в таблице записей, удовлетворяющих какому-либо условию, следует предпочесть использование предиката EXIST запросу, вычисляющему число таких записей. Запрос вида

SELECT * FROM <имя таблицы> WHERE (SELECT COUNT (*) FROM <имя таблицы> WHERE <условие>) >0

заставит сервер при выполнении внутреннего подзапроса перебрать все строки таблицы, проверяя соответствие каждой записи указанному условию, тогда как запрос вида

SELECT * FROM <имятаблицы> WHERE EXISTS (SELECT * FROM <имятаблицы> WHERE <условие>)

заставит сервер перебирать записи до нахождения первой записи, удовлетворяющей указанному условию. Лишний перебор записей на сервере, естественно, занимает некоторое время - чудес не бывает.

Многие приемы оптимизации связаны с использованием индексов. Если какое-либо поле таблицы часто используется в предложении WHERE, сравнивающем его значение с какой-либо константой или параметром, наличие индекса для этого поля ускоряет подобные операции. По этой же причине рекомендуется индексировать внешние ключи у таблиц с большим числом записей. Однако следует иметь в виду, что поддержка индексов замедляет операции вставки записей, поэтому при проектировании данных следует взвесить все "за" и "против" создания индексов, а еще лучше - провести соответствующее тестирование, заполнив таблицы случайными данными (для этой цели можно написать соответствующее приложение, а еще лучше - воспользоваться готовыми средствами тестирования типа SQA Suite).

Говоря об использовании индексов, следует также обратить внимание на то, что при использовании индексированных полей в качестве аргументов функций наличие индекса не влияет на скорость выполнения запроса - индекс в этом случае не используется.

Особо следует отметить проблемы, связанные с использованием вложенных запросов. Дело в том, что скорость выполнения запроса существенно зависит от числа уровней вложенности подзапросов (время выполнения примерно пропорционально произведению числа записей в таблицах, используемых в подзапросах). Фактически проверка соответствия условию WHERE каждой записи из внешнего подзапроса инициирует выполнение внутреннего подзапроса, что особенно заметно сказывается при большом числе записей. В практике автора чуть более года назад был случай, когда при приведении в порядок одной из используемых корпоративных информационных систем после выполнения нескольких обычных запросов на обновление данных в таблице с несколькими десятками тысяч записей, выполнявшихся в течение нескольких секунд, был инициирован вложенный запрос на обновление данных к этой же таблице. Этот запрос выполнялся более двух часов (чего, вообще говоря, и следовало ожидать). Поэтому использовать вложенные запросы следует только в тех случаях, когда без них нельзя обойтись. Альтернативой использования вложенных запросов может служить фильтрация результатов обычного запроса в клиентском приложении либо последовательное выполнение нескольких запросов с созданием временных таблиц на сервере.

Оптимизация клиентского приложения

Методы оптимизации клиентского приложения мало чем отличаются от методов оптимизации обычных приложений C++Builder. Обычно оптимизация заключается в повышении быстродействия приложения и в снижении объема используемых ресурсов операционной системы.

Снижение количества потребляемых ресурсов возможно разными способами. Основной принцип их экономии - не использовать ресурсы впустую. Именно поэтому рекомендуется в приложениях, использующих большое количество форм, создавать их динамически и уничтожать, как только они становятся ненужными (что отличается от установок менеджера проектов по умолчанию, которые предполагают автоматическое создание всех форм сразу же). Однако при этом следует помнить, что модуль данных, содержащий компоненты доступа к данным, используемые интерфейсными элементами динамически создаваемой формы, должен быть создан до создания самой формы, дабы избежать исключительной ситуации, связанной с обращением к несуществующему объекту.

Избегать лишних связей с сервером следует не только из-за лишней перегрузки сети и сервера, но и из-за того, что они поглощают некоторое количество ресурсов и замедляют работу приложения.

Еще одним способом экономии ресурсов клиентского приложения является использование более экономичных интерфейсных элементов в случаях, где это возможно (например, TDBText или TLabel вместо TDBEdit, TLabel вместо TDBMemo при отображении полей, редактирование которых не предполагается, TDBGrid вместо TDBControlGrid и т.д.).

Еще один прием, повышающий быстродействие клиентского приложения, заключается в сокращении числа операций, связанных с выводом данных из таблиц на экран, например, при "пролистывании" большого количества строк в компонентах типа TDBGrid или TDBCtrlGrid в процессе навигации по набору данных или какой-либо их обработки. В этом случае рекомендуется на время отключать связь интерфейсных элементов с компонентом TDataSource, установив значение его свойства Enabled равным false (пример использования этого приема будет приведен ниже).

О навигационных методах и "клипперном" стиле програмирования

Говоря об оптимизации клиент-серверных информационных систем, хотелось бы отдельно остановиться на одной очень распространенной ошибке, совершаемой программистами, имеющими большой опыт работы с настольными СУБД и средствами разработки, базирующимися на xBase-языках, такими, как Clipper, dBase, FoxPro и др. При использовании средств разработки такого рода какое-либо изменение данных в таблице согласно каким-либо правилам осуществляется обычно путем создания цикла типа:

USE HOLDINGS

GO TOP

DO WHILE !EOF()

PUR_PRICE=PUR_PRICE+10

SKIP

ENDDO

CLOSE

В приведенном фрагменте xBase-кода PUR_PRICE - имя поля таблицы HOLDINGS, подверженного изменению.

При переходе к архитектуре клиент/сервер и средствам разработки, поддерживающим SQL, поначалу возникает естественное желание продолжать писать подобный код, используя циклы и навигацию по таблице. Это не так страшно в случае использования C++Builder с настольными СУБД - локальный SQL, способный быть альтернативой в этом случае, в конечном итоге также инициирует перебор записей таблицы. Вообще говоря, то же самое происходит и при выполнении запроса типа UPDATE HOLDINGS SET PUR_PRICE=PUR_PRICE+10 на сервере баз данных, но подобный цикл является внутренним процессом сервера, в котором не задействованы ни клиент, ни сеть. Однако при использовании "клипперного" стиля программирования библиотека BDE вовсе не обязана догадываться, что имел в виду программист, написавший подобный цикл, и генерирует вовсе не такие запросы!

Рассмотрим простой пример. Создадим копию таблицы HOLDINGS.DBF из входящей в комплект поставки C++Builder базы данных DBDEMOS на каком-либо сервере баз данных, например, Personal Oracle (воспользовавшись, например, утилитой Data Migration Wizard из комплекта поставки Borland C++Builder). Затем создадим новое приложение, состоящее из одной формы, включающей компоненты TDBGrid, TTable, TDataSource, TQuery, TDBNavigator и три кнопки (рис.3).

Установим следующие значения свойств используемых компонентов (табл.1):