Zeos ZQuery - чудовищный расход памяти в Windows
Модератор: Модераторы
Zeos ZQuery - чудовищный расход памяти в Windows
Добрый день!
В потоке (на каждый коннект в Indy) создаю ZConnection, потом ZQuery, потом выборка 10 строк (с лимитом) из таблицы без BLOB полей в MySQL. И тут же добавление записи. Потом ZQuery и ZConnection уничтожаются.
Все работает стабильно, но после вставленных 20 000 строк приложение стало занимать в памяти до 200мб, с 10 мб при запуске.
Получается, ZQuery.Close; ZQuery.Free и ZConnection.Connected:=False; ZConnection.Free недостаточно?
В порядке бреда делаю еще ZQuery.EmptyDataSet - но это похоже никак не помогает.
Да, еще наблюдение - память, похоже, уходит в ZQuery. Оставил в обработчике только создание/подключение/отключение/уничтожение ZConnection - память не теряется.
В чем может быть дело?
P.S. Сейчас в таблице более 80 000 строк и продолжает работать без вылетов.
Приложение в памяти (по диспетчеру задач) стало занимать с 10 мб при запуске 903 мб теперь !!!
В потоке (на каждый коннект в Indy) создаю ZConnection, потом ZQuery, потом выборка 10 строк (с лимитом) из таблицы без BLOB полей в MySQL. И тут же добавление записи. Потом ZQuery и ZConnection уничтожаются.
Все работает стабильно, но после вставленных 20 000 строк приложение стало занимать в памяти до 200мб, с 10 мб при запуске.
Получается, ZQuery.Close; ZQuery.Free и ZConnection.Connected:=False; ZConnection.Free недостаточно?
В порядке бреда делаю еще ZQuery.EmptyDataSet - но это похоже никак не помогает.
Да, еще наблюдение - память, похоже, уходит в ZQuery. Оставил в обработчике только создание/подключение/отключение/уничтожение ZConnection - память не теряется.
В чем может быть дело?
P.S. Сейчас в таблице более 80 000 строк и продолжает работать без вылетов.
Приложение в памяти (по диспетчеру задач) стало занимать с 10 мб при запуске 903 мб теперь !!!
что говорит netstat? есть ли много TIME-WAIT?
какая логика при работе с БД и Инди. Где уничтожается объекты?
Какой инди 9 или 10?
Добавлено спустя 2 минуты 30 секунд:
ZQuery может быт не главным виновником
какая логика при работе с БД и Инди. Где уничтожается объекты?
Какой инди 9 или 10?
Добавлено спустя 2 минуты 30 секунд:
ZQuery может быт не главным виновником
Добрый день!
Про Indy я, признаюсь, написал для простоты. Строго говоря, это Intraweb, основанный на Indy (10 версии видимо). Обработка в событии IWServerControllerBaseBeforeDispatch. Все на одной машине. В MySQL Workbench отлично видно, что записи исправно поступают в базу. А тестовый код вот, если его просто поместить под кнопку (можно и таймер наверное) после нескольких сот нажатий отлично видно, как растет объем памяти, потребляемой программой.
Про Indy я, признаюсь, написал для простоты. Строго говоря, это Intraweb, основанный на Indy (10 версии видимо). Обработка в событии IWServerControllerBaseBeforeDispatch. Все на одной машине. В MySQL Workbench отлично видно, что записи исправно поступают в базу. А тестовый код вот, если его просто поместить под кнопку (можно и таймер наверное) после нескольких сот нажатий отлично видно, как растет объем памяти, потребляемой программой.
Код: Выделить всё
Try
ZConnection:=TZConnection.Create(Nil);
ZConnection.User:='user';
ZConnection.Password:='11112222';
ZConnection.Database:='db_video';
ZConnection.HostName:='192.168.1.152';
ZConnection.LibraryLocation:='C:\Program Files (x86)\MySQL\MySQL Server 5.7\lib\libmysql.dll';
ZConnection.Port:=3307;
ZConnection.Protocol:='mysqld-5';
ZConnection.Connected:=True;
ZQuery:=TZQuery.Create(Nil);
ZQuery.Connection:=ZConnection;
ZQuery.SQL.Clear;
ZQuery.SQL.Add('Select * from record_table limit 0,1');
ZQuery.Open;
CreateGUID(MyGUID);
Str_MyGUID:=GUIDToString(MyGUID); // P_Key
Delete(Str_MyGUID,1,1);
Delete(Str_MyGUID,37,1);
ZQuery.Append;
ZQuery.FieldByName('record_id').AsString:=Str_MyGUID;
ZQuery.FieldByName('unix_timestamp').AsInteger :=DateTimeToUnix(Now)-10800;
ZQuery.FieldByName('time_begin').AsInteger :=DateTimeToUnix(Now)-10800;
ZQuery.FieldByName('room_id').AsString:='6D4E0BC7-4265-41B9-8DE6-A6C54B28805E';
ZQuery.Post;
Str_MyGUID:='';
Finally
ZQuery.EmptyDataSet;
ZQuery.Close;
ZQuery.Free;
ZConnection.Connected:=False;
ZConnection.Free;
End;
Indy держит сокет даже при завершения подключения, пока сама система не утилизирует, а утилизировать она может и через 2 суток. это плохо если на сокет понавешана логика, Вот весит кусок в памяти логики, только потому что система не утилизировала сокет.
но это на стороне сервера, Спрятанная логика работы с сокетом. Там сделано все чтобы компонентами можно было подключать и отключать модули. SSL, IdMessage. и др.
Так что делать нагруженные приложения на Indy это рискованно.
попробуйтe FreeAndNil(ZQuery); FreeAndNil(ZConnection) вместо ZQuery.Free и ZConnection.Free; я точно не помню, это меня спасало при написании при высоконагруженных сервисов. Потери в памяти свелись к нулю, когда я стал FreeAndNil вместо Free делать, вроде в куче не совсем утилизируются объекты, либо сборщик мусора очень ленивый, не поспевает убирать
Кстати потери памяти хорошо тестятся при проверки кучи, есть такая галка при компиляции и отладки. Если программа завершилась с текстом в каких местах есть потери. то значит прога не сбалансированная. Хорошо помогает этот check heap.
Сам рост памяти возможен даже из-за системы, линух этим страдает. Если работаете с переменным массивом данных то рост обеспечен, линух сильно не стремится утилизировать мусор и освобождать память, он лучше возьмет из новых участков. Возможно в Windows лучше работа идёт.
Кстати в Java еще хуже обстоят дела. У меня Android Studio при 4Гб оперативы плохо вел себя, только при 8Гб стал лучше работать.
Так что лучше чекайте Heap - 100% гарантия на текучесть памяти. Правда есть еще libmysql.dll там может быть "открытия чудные" ждут.
Добавлено спустя 6 минут 9 секунд:
я не осилил Valgrind, у меня логи не читались Valgrind
Добавлено спустя 2 минуты 25 секунд:
Heap чем хорош, тем что если вы хотите видить действинную чистоту то нужно позаботится об правильном завершении проги. Он вынуждает делать корректный выход из программы..
но это на стороне сервера, Спрятанная логика работы с сокетом. Там сделано все чтобы компонентами можно было подключать и отключать модули. SSL, IdMessage. и др.
Так что делать нагруженные приложения на Indy это рискованно.
Aleks69 писал(а): А тестовый код вот, если его просто поместить под кнопку (можно и таймер наверное) после нескольких сот нажатий отлично видно, как растет объем памяти, потребляемой программой.
попробуйтe FreeAndNil(ZQuery); FreeAndNil(ZConnection) вместо ZQuery.Free и ZConnection.Free; я точно не помню, это меня спасало при написании при высоконагруженных сервисов. Потери в памяти свелись к нулю, когда я стал FreeAndNil вместо Free делать, вроде в куче не совсем утилизируются объекты, либо сборщик мусора очень ленивый, не поспевает убирать
Кстати потери памяти хорошо тестятся при проверки кучи, есть такая галка при компиляции и отладки. Если программа завершилась с текстом в каких местах есть потери. то значит прога не сбалансированная. Хорошо помогает этот check heap.
Сам рост памяти возможен даже из-за системы, линух этим страдает. Если работаете с переменным массивом данных то рост обеспечен, линух сильно не стремится утилизировать мусор и освобождать память, он лучше возьмет из новых участков. Возможно в Windows лучше работа идёт.
Кстати в Java еще хуже обстоят дела. У меня Android Studio при 4Гб оперативы плохо вел себя, только при 8Гб стал лучше работать.
Так что лучше чекайте Heap - 100% гарантия на текучесть памяти. Правда есть еще libmysql.dll там может быть "открытия чудные" ждут.
Добавлено спустя 6 минут 9 секунд:
debi12345 писал(а):Соберите Вашу прогу с отладочной инфой и запустите в ее Valgrind
я не осилил Valgrind, у меня логи не читались Valgrind
Добавлено спустя 2 минуты 25 секунд:
Heap чем хорош, тем что если вы хотите видить действинную чистоту то нужно позаботится об правильном завершении проги. Он вынуждает делать корректный выход из программы..
- serbod
- постоялец
- Сообщения: 449
- Зарегистрирован: 16.09.2016 10:03:02
- Откуда: Минск
- Контактная информация:
1. Connection достаточно одного на всех и не надо его туда-сюда создавать и удалять.
2. Лучше использовать ZROQuery с опцией OneDirection.
3. Для одинаковых запросов с разными параметрами лучше иметь постоянные ZQuery, а не создавать каждый раз.
4. Возможно, это проблема в сочетании с конкретной БД. Можно попробовать "родные" лазаревские компоненты.
2. Лучше использовать ZROQuery с опцией OneDirection.
3. Для одинаковых запросов с разными параметрами лучше иметь постоянные ZQuery, а не создавать каждый раз.
4. Возможно, это проблема в сочетании с конкретной БД. Можно попробовать "родные" лазаревские компоненты.
У меня были похожие проблемы со стандартными компонентами и я решил их так:
Добавлено спустя 4 минуты 28 секунд:
Данную идею взял по моему отсюда https://www.gunsmoker.ru/2009/04/freeandnil-free.html
Код: Выделить всё
// отключаемся
FreeAndNil(SQLiteQuery);
FreeAndNil(SQLiteTransaction);
FreeAndNil(SQLiteConnection);
Добавлено спустя 4 минуты 28 секунд:
Данную идею взял по моему отсюда https://www.gunsmoker.ru/2009/04/freeandnil-free.html
Поделюсь кодом, который работает уже много лет 24/7, и нет никаких проблем. Каждый подпилит под себя при необходимости:
Код: Выделить всё
unit usqladapter;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, LCLIntf, LCLType, LMessages, ZConnection, ZAbstractConnection,
ZDataset, uconfig, ulogger;
type
{ TSqlAdapter }
TSqlAdapter = class
private
FWindowHandle: THandle;
public
constructor Create(WndH: THandle = -1);
destructor Destroy; override;
function CreateConnection(const Hostname: string; const Port: integer;
const Login, Password, DbName: string): TZConnection; overload;
function CreateConnection: TZConnection; overload;
function CreateQuery: TZQuery;
procedure FieldsToList(Fields: array of string; List: TStringList;
const Prefix: string = '');
function InsertFields(Fields: array of string): string; overload;
function InsertFields(Fields: TStringList): string; overload;
function UpdateFields(Fields: array of string; const Prefix: string = ''): string;
overload;
function UpdateFields(Fields: TStringList; const Prefix: string = ''): string;
overload;
procedure DeleteRecord(const Table, Field, Value: string; RaiseException: boolean);
procedure ImportData(const SqlData: string);
procedure ErrorHandler(sMethodName: string; E: Exception);
procedure FreeQuery(Query: TZQuery);
procedure Log(sLogString: string);
procedure Send(Msg: cardinal; W: WParam; L: LParam);
end;
implementation
{ TSqlAdapter }
constructor TSqlAdapter.Create(WndH: THandle = -1);
begin
FWindowHandle := WndH;
end;
destructor TSqlAdapter.Destroy;
begin
inherited Destroy;
end;
function TSqlAdapter.CreateConnection(const Hostname: string;
const Port: integer; const Login, Password, DbName: string): TZConnection;
begin
Result := TZConnection.Create(nil);
Result.LibraryLocation := 'libmysql.dll';
Result.Protocol := 'mysql';
Result.HostName := Hostname;
Result.Port := Port;
Result.User := Login;
Result.Password := Password;
Result.Database := DbName;
end;
function TSqlAdapter.CreateConnection: TZConnection;
begin
Result := CreateConnection(Config.Server, Config.Port, Config.Login,
Config.Password, Config.DataBaseName);
end;
function TSqlAdapter.CreateQuery: TZQuery;
var
Conn: TZConnection;
begin
Conn := CreateConnection;
Conn.UseMetadata := False;
Result := TZQuery.Create(nil);
Result.Connection := Conn;
end;
function TSqlAdapter.InsertFields(Fields: array of string): string;
var
strList: TStringList;
begin
strList := TStringList.Create;
try
FieldsToList(Fields, strList);
Result := InsertFields(strList);
finally
strList.Free;
end;
end;
function TSqlAdapter.InsertFields(Fields: TStringList): string;
var
Count: integer;
begin
Result := '(';
for Count := 0 to Fields.Count - 1 do
begin
if Count > 0 then
Result := Result + ', ';
Result := Result + '`' + Fields[Count] + '`';
end;
Result := Result + ') VALUES (';
for Count := 0 to Fields.Count - 1 do
begin
if Count > 0 then
Result := Result + ', ';
Result := Result + ':' + Fields[Count];
end;
Result := Result + ')';
end;
function TSqlAdapter.UpdateFields(Fields: array of string; const Prefix: string): string;
var
Count: integer;
begin
Result := '';
for Count := 0 to Length(Fields) - 1 do
begin
if Count > 0 then
Result := Result + ', ';
Result := Result + '`' + Prefix + Fields[Count] + '`=' + Prefix + Fields[Count];
end;
end;
function TSqlAdapter.UpdateFields(Fields: TStringList; const Prefix: string): string;
var
Count: integer;
begin
Result := '';
for Count := 0 to Fields.Count - 1 do
begin
if Count > 0 then
Result := Result + ', ';
Result :=
Result + '`' + Prefix + Fields[Count] + '`=' + ':' + Prefix + Fields[Count];
end;
end;
procedure TSqlAdapter.DeleteRecord(const Table, Field, Value: string;
RaiseException: boolean);
var
Query: TZQuery;
begin
Query := CreateQuery;
try
try
Query.SQL.Text := 'DELETE FROM `' + Table + '` WHERE `' + Field + '`=' + Value;
Query.ExecSQL;
if (RaiseException) and (Query.RowsAffected = 0) then
raise Exception.Create('ErrRecordNotFound');
except
on E: Exception do
begin
ErrorHandler('DeleteRecord: ' + Table, E);
raise;
end;
end;
finally
FreeQuery(Query);
end;
end;
procedure TSqlAdapter.ImportData(const SqlData: string);
var
Query: TZQuery;
begin
Query := CreateQuery;
try
Query.SQL.Text := SqlData;
try
Query.ExecSQL;
except
on E: Exception do
begin
ErrorHandler('ImportData', E);
raise;
end;
end
finally
// Send(WM_IMPORTED_DATA, 0, integer(@CDR));
FreeQuery(Query);
end;
end;
procedure TSqlAdapter.FieldsToList(Fields: array of string; List: TStringList;
const Prefix: string);
var
Count: integer;
begin
for Count := 0 to Length(Fields) - 1 do
List.Add(Prefix + Fields[Count]);
end;
procedure TSqlAdapter.FreeQuery(Query: TZQuery);
var
Conn: TZAbstractConnection;
begin
Conn := Query.Connection;
if Conn.Connected then
Conn.Disconnect;
if Query <> nil then
Query.Free;
if Conn <> nil then
Conn.Free;
end;
procedure TSqlAdapter.ErrorHandler(sMethodName: string; E: Exception);
begin
Log(sMethodName + ': ' + E.Message);
end;
procedure TSqlAdapter.Log(sLogString: string);
var
filename: string;
logFile: TextFile;
begin
filename := 'log_' + FormatDateTime('DD_MM_YYYY', Now) + '.txt';
AssignFile(logFile, GetCurrentDir + '\logs\' + filename);
if not FileExists(GetCurrentDir + '\logs\' + filename) then
begin
rewrite(logFile);
end
else
begin
append(logFile);
end;
writeln(logFile, sLogString);
CloseFile(logFile);
end;
procedure TSqlAdapter.Send(Msg: cardinal; W: WParam; L: LParam);
begin
if (FWindowHandle > -1) then
SendMessage(FWindowHandle, Msg, W, L);
end;
end.