{codecitation class="brush: pascal; gutter: false;" width="600px"}

Оформил: DeeCo

Автор: http://www.swissdelphicenter.ch

{

Often there are lots of duplicate strings in a program,

for example take the following database:

Author Title

aaa xxx

aaa yyy

aaa zzz

Suppose you want to read it into your program and

create one record or class per line:

}

while not Table.EOF do

begin

author:= Table['Author'];

title:= Table['Title'];

MyClass:= TMyClass (author, title);

StoreItSomewhere (MyClass);

Table.Next;

end;

{

You end up with three different strings containting the same author.

No problem with that as long as there are only three of them.

Now suppose you have got a database of 100,000 entries and lots of

duplicate author names.

If you do the above again you will end up with a huge memory overhead

due to fragmentation of the free memory on the heap.

In addition to that freeing the objects will result in a noticable delay

of the program while the memory manager is busy merging free memory blocks.

On the other hand, Delphi's AnsiStrings use reference counting so it

would be possible to assign the same string to many string variables

without copying the contents:

}

Author:= 'aaa';

for i:=0 to 100000 do

begin

MyClass:= TMyClass (Author);

StoreItSomewhere (MyClass);

end;

{

This will create 100000 strings containting 'aaa',

but store the actual contents only once,

because they are assigned the same string constant.

It would be nice to do the same with the above database example,

wouldn't it? But how do we get Delphi to realize that

the content is really the same?

Answer: A StringList

}

authors:= TStringList.Create;

authors.Sorted:= true;

authors.Duplicates:= dupIgnore;

while not Table.EOF do

begin

author:= Table['Author'];

title:= Table['Title'];

authors.Add (author);

authors.Search (author, Idx);

author:= authors[Idx];

MyClass:= TMyClass (author, title);

StoreItSomewhere (MyClass);

Table.Next;

end;

authors.free;

{

This will only keep one string of each author's name and assign

it to all other occurences, even after the StringList has been freed again.

The wonders of reference counting!

I admit that the above example looks a bit odd. Nobody would load

such a database into memory. But I had a similar problem today and by using

the trick above I managed to reduce the memory usage of my program from 120 megabytes to 10

(basically nothing, given that it started out with 8 mb).

}

{/codecitation}

Автор: Дмитрий Померанцев

Проблема обнаружена под операционной системой Windows 2000 SP3, в среде Delphi6, Delphi7 (скорее всего не зависит от версии Delphi) с использованием Microsoft Jet DB Engine версия 4, SP3.

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

Пример кода:

Допустим, есть база данных в MS Access 2000, имеющая таблицу main и в ней целочисленное (INT) поле id в качестве главного ключа. Так же есть компонент ADOQuery1: TADOQuery, для доступа к базе данных. Максимальное значение поля id может быть получено следующим кодом:
{codecitation class="brush: pascal; gutter: false;" width="600px"}
ADOQuery1.Active:= false;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add ('SELECT max (id)'); // -- Сбой здесь!!!
ADOQuery1.SQL.Add ('AS idmax');
ADOQuery1.SQL.Add ('FROM main');
ADOQuery1.Active:= true;
{/codecitation}

Как было показано в комментарии, исключение возникает в процессе добавления текста в запрос, но при этом в сообщении об ошибке указывалось, что исключение произошло внутри библиотеки Jet.

Исследование исходных текстов компонента TADOQuery показало следущее: свойство SQL, типа TStrings связано с полем FSQL: TStrings, создаваемого как экземляр класса TStringList, при этом объекту FSQL назначается обработчик события OnChange — метод QueryChanged (protected, статический), что исключает его возможную перегрузку.

Этот метод устанавливает свойство Active в False и присваивает содержимое FSQL.Text полю CommandText объекта ADO.

За отсутствием исходных текстов библиотеки Jet, дальнейшее исследование пришлось прекратить, но можно сделать несколько выводов:

Корни проблемы в невполне корректном поведении как кода от Borland, так и от Microsoft. Компонент TADOQuery передает в ADO неоконченный SQL-запрос, а Jet начинает анализировать этот запрос до того, как он полностью поступит. Возможно, Microsoft пытался реализовать упреждающее выполнение запросов, чтобы снизить время обработки запроса после получения команды на выполнение.

Теоретически и другие драйвера баз данных могут быть чувствительны к неполным запросам, так что данная ошибка может появляться и при работе с другими СУБД.

При дополнительном исследовании были выяснены интересные подробности:

Данный код не прерывает выполнения при возникновении exception, т. е. теоретически даже try..except не нужен. Похоже, это происходит из-за того, что jet является COM-объектом, а их методы вызываются как safecall. Дальнейшие тесты подтвердили это предположение — при снятии галочки Stop on Delphi Exceptions и в варианте exe-файла ошибка не проявлялась. Таким образом, ситуация несколько меняется — исключение возникает только в среде разработки, что, правда, является слабым утешением, т. к. многие програмисты работают с настройками по-умолчанию, и в случае его возникновения могут долго ломать голову, ища свою ошибку там где ее нет.

ТИПОВЫЕ РЕШЕНИЯ

1. Передавать запрос целиком — одной строкой. Пример:
{codecitation class="brush: pascal; gutter: false;" width="600px"}
ADOQuery1.Active:= false;
ADOQuery1.SQL.Text:= 'SELECT max (id) AS idmax FROM main;';
ADOQuery1.Active:= true;

{/codecitation}
2. Отключить галочку Tools->Debugger Options->Language Exceptions->Stop on Delphi Exceptions

3. Просто игнорировать это исключение (в этом случае в процессе разработки придется периодически несколько раз нажимать OK, что, конечно, менее удобно)

Напоследок: Небольшое исследование исходного кода компонент данных BDE и dbExpress показало, что в них передача SQL-запроса происходит через промежуточное текстовое поле, что, на мой взгляд, исключает в них возможность появления аналогичной ошибки.

КОММЕНТАРИЙ:

Компонент TADOQuery от Delphi 5 содержит аналогичный код (метод QueryChanged), приводящий к ошибке.

Еще один вариант решения — использовать стандартные возможности TStrings по управлению обновлением:
{codecitation class="brush: pascal; gutter: false;" width="600px"}
ADOQuery1.SQL.BeginUpdate;
try
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add ('SELECT max (id)');
ADOQuery1.SQL.Add ('AS idmax');
ADOQuery1.SQL.Add ('FROM main');
finally
ADOQuery1.SQL.EndUpdate;
end;
{/codecitation}

В этом случае событие OnChange произойдет только при выполнении EndUpdate.